UNPKG

@flowfuse/flowfuse

Version:

An open source low-code development platform

257 lines (242 loc) • 10.8 kB
const { KEY_SHARED_ASSETS } = require('../../../db/models/ProjectSettings') module.exports = async function (app) { app.config.features.register('staticAssets', true, true) app.register(require('@fastify/multipart')) app.addHook('preHandler', app.verifySession) app.addHook('preHandler', async (request, reply) => { if (request.params.projectId !== undefined) { if (request.params.projectId) { try { request.project = await app.db.models.Project.byId(request.params.projectId) if (!request.project) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) return } const teamType = await request.project.Team.getTeamType() if (!teamType.getFeatureProperty('staticAssets', false)) { reply.code(404).send({ code: 'not_found', error: 'Not Found - not available on team' }) return // eslint-disable-line no-useless-return } if (request.session.User) { request.teamMembership = await request.session.User.getTeamMembership(request.project.Team.id) if (!request.teamMembership && !request.session.User.admin) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) return // eslint-disable-line no-useless-return } } else if (request.session.ownerId !== request.params.projectId) { // AccessToken being used - but not owned by this project reply.code(404).send({ code: 'not_found', error: 'Not Found' }) return // eslint-disable-line no-useless-return } } catch (err) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } } else { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } } }) app.get('/_/:path', { preHandler: app.needsPermission('project:files:list'), schema: { summary: 'List files stored in the instance', tags: ['Instance Files'], params: { type: 'object', properties: { instanceId: { type: 'string' } } } // TODO: response format schema } }, async (request, reply) => { let filePath = request.params.path if (/\/$/.test(filePath)) { filePath = filePath.substring(0, filePath.length - 1) } try { const sharingConfig = await request.project.getSetting(KEY_SHARED_ASSETS) || {} const result = await app.containers.listFiles(request.project, filePath) result.files.forEach(file => { if (file.type === 'directory') { const absolutePath = filePath + (filePath.length > 0 ? '/' : '') + file.name if (sharingConfig[absolutePath]) { file.share = sharingConfig[absolutePath] } } }) const parentDirectoryName = filePath.split('/').pop() const parentDirectoryPath = filePath.split('/').slice(0, -1).join('/') const parentFiles = await app.containers.listFiles( request.project, parentDirectoryPath ) const currentDirectory = parentFiles.files.filter(file => file.name === parentDirectoryName).shift() if (currentDirectory) { result.folder = currentDirectory const absolutePath = parentDirectoryPath + (parentDirectoryPath.length > 0 ? '/' : '') + currentDirectory.name if (sharingConfig[absolutePath]) { result.folder.share = sharingConfig[absolutePath] } } else result.folder = null reply.send(result) } catch (err) { if (err.statusCode === 404) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } else { reply.code(400).send({ code: 'invalid_request', error: err.toString() }) } } }) app.put('/_/:path', { preHandler: app.needsPermission('project:files:edit'), schema: { summary: 'Update file properties in the instance', tags: ['Instance Files'], params: { type: 'object', properties: { instanceId: { type: 'string' } } }, body: { type: 'object', properties: { path: { type: 'string' }, share: { type: 'object', additionalProperties: true } } } // TODO: response format schema } }, async (request, reply) => { let filePath = request.params.path if (/\/$/.test(filePath)) { filePath = filePath.substring(0, filePath.length - 1) } const update = request.body if (update.path && update.share) { reply.code(400).send({ code: 'invalid_request', error: 'Cannot modify path and share in one request' }) return } if (update.path) { try { const result = await app.containers.updateFile(request.project, filePath, update) reply.send(result) } catch (err) { if (err.statusCode === 404) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } else { reply.code(400).send({ code: 'invalid_request', error: err.toString() }) } } } else if (update.share) { // Need to validate this is a directory, not a file try { await app.containers.listFiles(request.project, filePath) // This is a valid directory so we can update the share details const sharingConfig = await request.project.getSetting(KEY_SHARED_ASSETS) || {} // TODO: validate contents of update.share sharingConfig[filePath] = update.share await request.project.updateSetting(KEY_SHARED_ASSETS, sharingConfig) } catch (err) { // Not a directory or 404 reply.code(404).send({ code: 'not_found', error: 'Not Found' }) return } reply.code(200).send({}) } }) app.delete('/_/:path', { preHandler: app.needsPermission('project:files:delete'), schema: { summary: 'Delete a file in the instance', tags: ['Instance Files'], params: { type: 'object', properties: { instanceId: { type: 'string' } } } // TODO: response format schema } }, async (request, reply) => { let filePath = request.params.path if (/\/$/.test(filePath)) { filePath = filePath.substring(0, filePath.length - 1) } try { // Before we delete, we need to tidy up any share configs for dirs that // may be about to be deleted. try { // Try to get a dir listing - this will throw if filePath is a file or not found await app.containers.listFiles(request.project, filePath) // This is a directory. Tidy up shares const sharingConfig = await request.project.getSetting(KEY_SHARED_ASSETS) || {} let updatedSharingConfig = false const sharedPaths = Object.keys(sharingConfig) sharedPaths.forEach(sharedPath => { if (sharedPath === filePath || sharedPath.startsWith(filePath + '/')) { // This config is for the path to be deleted, // or a directory beneath it delete sharingConfig[sharedPath] updatedSharingConfig = true } }) if (updatedSharingConfig) { await request.project.updateSetting(KEY_SHARED_ASSETS, sharingConfig) } } catch (err) { // 404 or a file - not a directory that needs let the request continue } const result = await app.containers.deleteFile(request.project, filePath) reply.send(result) } catch (err) { if (err.statusCode === 404) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } else { reply.code(400).send({ code: 'invalid_request', error: err.toString() }) } } }) app.post('/_/:path', { preHandler: app.needsPermission('project:files:create'), schema: { summary: 'Upload a file to the instance/Create directory', tags: ['Instance Files'], params: { type: 'object', properties: { instanceId: { type: 'string' } } } // TODO: response format schema } }, async (request, reply) => { let filePath = request.params.path if (/\/$/.test(filePath)) { filePath = filePath.substring(0, filePath.length - 1) } try { const contentType = request.headers['content-type'] // Multipart == Upload if (/multipart\/form-data/.test(contentType)) { const data = await request.file() const buffer = await data.toBuffer() await app.containers.uploadFile(request.project, filePath, buffer) } else if (request.body?.path) { // Otherwise, application/json - create directory await app.containers.createDirectory(request.project, filePath, request.body?.path) } else { reply.code(400).send({ code: 'invalid_request', error: 'Missing body' }) return } } catch (err) { if (err.statusCode === 404) { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } else { reply.code(400).send({ code: 'invalid_request', error: err.toString() }) } return } reply.send() }) }