@flowfuse/flowfuse
Version:
An open source low-code development platform
255 lines (243 loc) • 9.46 kB
JavaScript
/**
* Project Snapshot api routes
*
* request.project will be defined for any route defined in here
*
* - /api/v1/projects/:instanceId/snapshots/
*
* @namespace project
* @memberof forge.routes.api
*/
const { createSnapshot } = require('../../services/snapshots')
module.exports = async function (app) {
/** @type {typeof import('../../db/controllers/Snapshot.js')} */
const snapshotController = app.db.controllers.Snapshot
/** @type {typeof import('../../db/views/ProjectSnapshot.js')} */
const projectSnapshotView = app.db.views.ProjectSnapshot
app.addHook('preHandler', async (request, reply) => {
if (request.params.snapshotId !== undefined) {
if (request.params.snapshotId) {
try {
// TODO: Only the export snapshot route requires the snapshot to have flows attached.
// We could single that out (as an uncommon path) and otherwise add { includeFlows: false }
// here to avoid loading the full flow object.
request.snapshot = await app.db.models.ProjectSnapshot.byId(request.params.snapshotId)
if (!request.snapshot || request.snapshot.ProjectId !== request.project.id) {
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.addHook('preHandler', app.needsPermission('project:change-status'))
/**
* Get list of all project snapshots
*/
app.get('/', {
preHandler: app.needsPermission('project:snapshot:list'),
schema: {
summary: 'Get a list of instance snapshots',
tags: ['Snapshots'],
params: {
type: 'object',
properties: {
instanceId: { type: 'string' }
}
},
response: {
200: {
type: 'object',
properties: {
meta: { $ref: 'PaginationMeta' },
count: { type: 'number' },
snapshots: { type: 'array', items: { $ref: 'Snapshot' } }
}
},
'4xx': {
$ref: 'APIError'
}
}
}
}, async (request, reply) => {
const paginationOptions = app.getPaginationOptions(request)
const snapshots = await app.db.models.ProjectSnapshot.forProject(request.project.id, paginationOptions)
snapshots.snapshots = snapshots.snapshots.map(s => app.db.views.ProjectSnapshot.snapshot(s))
reply.send(snapshots)
})
/**
* Get details of a snapshot - metadata only
*/
app.get('/:snapshotId', {
preHandler: app.needsPermission('project:snapshot:read'),
schema: {
summary: 'Get details of a snapshot',
tags: ['Snapshots'],
params: {
type: 'object',
properties: {
instanceId: { type: 'string' },
snapshotId: { type: 'string' }
}
},
response: {
200: {
$ref: 'Snapshot'
},
'4xx': {
$ref: 'APIError'
}
}
}
}, async (request, reply) => {
reply.send(app.db.views.ProjectSnapshot.snapshot(request.snapshot))
})
/**
* Delete a snapshot
*/
app.delete('/:snapshotId', {
preHandler: app.needsPermission('project:snapshot:delete'),
schema: {
summary: 'Delete a snapshot',
tags: ['Snapshots'],
params: {
type: 'object',
properties: {
instanceId: { type: 'string' },
snapshotId: { type: 'string' }
}
},
response: {
200: {
$ref: 'APIStatus'
},
'4xx': {
$ref: 'APIError'
}
}
}
}, async (request, reply) => {
await snapshotController.deleteSnapshot(request.snapshot)
await app.auditLog.Project.project.snapshot.deleted(request.session.User, null, request.project, request.snapshot)
reply.send({ status: 'okay' })
})
/**
* Create a snapshot
* @name /api/v1/projects/:instanceId/snapshots
*/
app.post('/', {
preHandler: app.needsPermission('project:snapshot:create'),
schema: {
summary: 'Create a snapshot from an instance',
tags: ['Snapshots'],
params: {
type: 'object',
properties: {
instanceId: { type: 'string' }
}
},
body: {
type: 'object',
properties: {
name: { type: 'string' },
description: { type: 'string' },
flows: {
oneOf: [
{
// This format matches the snapshot object in the database, the schema `ExportedSnapshot` and the
// original snapshot object format returned by `createSnapshot` function in `controllers/ProjectSnapshot.js`
type: 'object',
properties: {
flows: { type: 'array', items: { type: 'object' } },
credentials: { type: 'object' }
}
},
{
// Alt API format
// This format matches the format of the exported project object created by `exportProject` function in `controllers/Project.js`
// and supports the format expected by `createSnapshot` function in `services/snapshots.js`
type: 'array', items: { type: 'object' }
}
]
},
credentials: { type: 'object' },
credentialSecret: { type: 'string' },
settings: {
type: 'object',
properties: {
modules: { type: 'object', additionalProperties: true }
}
},
setAsTarget: { type: 'boolean' }
}
},
response: {
200: {
$ref: 'Snapshot'
},
'4xx': {
$ref: 'APIError'
}
}
}
}, async (request, reply) => {
const project = request.project
const user = request.session.User
const snapshotProperties = request.body
const snapShot = await createSnapshot(app, project, user, snapshotProperties)
reply.send(app.db.views.ProjectSnapshot.snapshot(snapShot))
})
/**
* Export snapshot: return the full snapshot, including settings, envars, flows and re-encrypted credentials.
* /api/v1/projects/:instanceId/snapshots/:snapshotId/export
*/
app.post('/:snapshotId/export', {
preHandler: app.needsPermission('project:snapshot:export'),
schema: {
summary: 'Export an instance snapshot using the provided credentialSecret',
tags: ['Snapshots'],
params: {
type: 'object',
properties: {
instanceId: { type: 'string' },
snapshotId: { type: 'string' }
}
},
body: {
type: 'object',
properties: {
credentialSecret: { type: 'string' }
}
},
response: {
200: {
$ref: 'ExportedSnapshot'
},
'4xx': {
$ref: 'APIError'
}
}
}
}, async (request, reply) => {
if (!request.body.credentialSecret) {
reply.code(400).send({ code: 'bad_request', error: 'credentialSecret is mandatory in the body' })
}
const options = {
credentialSecret: request.body.credentialSecret,
credentials: request.body.credentials,
owner: request.project // the instance owns the snapshot
}
const snapshot = await snapshotController.exportSnapshot(request.snapshot, options)
if (snapshot) {
const snapshotExport = projectSnapshotView.snapshotExport(snapshot, request.session.User)
await app.auditLog.Project.project.snapshot.exported(request.session.User, null, request.project, snapshot)
reply.send(snapshotExport)
} else {
reply.send({})
}
})
}