UNPKG

@flowfuse/flowfuse

Version:

An open source low-code development platform

303 lines (297 loc) • 11.6 kB
/** * Project Type api routes * * - /api/v1/project-types * * @namespace projectTypes * @memberof forge.routes.api */ module.exports = async function (app) { /** * Get a list of all project types * @name /api/v1/project-types/ * @static * @memberof forge.routes.api.projectTypes */ app.get('/', { preHandler: app.needsPermission('project-type:list'), schema: { summary: 'Get a list of all instance types', tags: ['Instance Types'], query: { allOf: [ { $ref: 'PaginationParams' }, { type: 'object', properties: { filter: { type: 'string' } } } ] }, response: { 200: { type: 'object', properties: { meta: { $ref: 'PaginationMeta' }, count: { type: 'number' }, types: { type: 'array', items: { $ref: 'InstanceType' } } } }, '4xx': { $ref: 'APIError' } } } }, async (request, reply) => { const paginationOptions = app.getPaginationOptions(request) let filter = { active: true } if (request.query.filter === 'all') { filter = {} } else if (request.query.filter === 'active') { // Default behaviour filter = { active: true } } else if (request.query.filter === 'inactive') { filter = { active: false } } const projectTypes = await app.db.models.ProjectType.getAll(paginationOptions, filter) projectTypes.types = projectTypes.types.map(pt => app.db.views.ProjectType.projectType(pt, request.session.User.admin)) reply.send(projectTypes) }) /** * Get the details of a ProjectType * @name /api/v1/project-types/:id * @static * @memberof forge.routes.api.projectTypes */ app.get('/:instanceTypeId', { preHandler: app.needsPermission('project-type:read'), schema: { summary: 'Get a details of an instance types', tags: ['Instance Types'], params: { type: 'object', properties: { instanceTypeId: { type: 'string' } } }, response: { 200: { $ref: 'InstanceType' }, '4xx': { $ref: 'APIError' } } } }, async (request, reply) => { const projectType = await app.db.models.ProjectType.byId(request.params.instanceTypeId) if (projectType) { reply.send(app.db.views.ProjectType.projectType(projectType, request.session.User.admin)) } else { reply.code(404).send({ code: 'not_found', error: 'Not Found' }) } }) /** * Create a projectType * @name /api/v1/project-types * @static * @memberof forge.routes.api.projectTypes */ app.post('/', { preHandler: app.needsPermission('project-type:create'), schema: { summary: 'Create an instance type - admin-only', tags: ['Instance Types'], body: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, description: { type: 'string' }, active: { type: 'boolean' }, properties: { type: 'object' }, order: { type: 'number' } } }, response: { 200: { $ref: 'InstanceType' }, '4xx': { $ref: 'APIError' } } } }, async (request, reply) => { // Only admins can create a projectType const properties = { name: request.body.name, description: request.body.description, active: request.body.active !== undefined ? request.body.active : undefined, properties: request.body.properties, order: request.body.order } try { const projectType = await app.db.models.ProjectType.create(properties) await app.auditLog.Platform.platform.projectType.created(request.session.User, null, projectType) const response = app.db.views.ProjectType.projectType(projectType, true) reply.send(response) } catch (err) { let responseMessage if (err.errors) { responseMessage = err.errors.map(err => err.message).join(',') } else { responseMessage = err.toString() } const resp = { code: 'unexpected_error', error: responseMessage } await app.auditLog.Platform.platform.projectType.created(request.session.User, resp, properties) reply.code(400).send(resp) } }) /** * Update a project type * @name /api/v1/project-types * @static * @memberof forge.routes.api.projectTypes */ app.put('/:instanceTypeId', { preHandler: app.needsPermission('project-type:edit'), schema: { summary: 'Update an instance type - admin-only', tags: ['Instance Types'], params: { type: 'object', properties: { instanceTypeId: { type: 'string' } } }, body: { type: 'object', properties: { name: { type: 'string' }, description: { type: 'string' }, active: { type: 'boolean' }, properties: { type: 'object' }, order: { type: 'number' }, defaultStack: { type: 'string' } } }, response: { 200: { $ref: 'InstanceType' }, '4xx': { $ref: 'APIError' } } } }, async (request, reply) => { const projectType = await app.db.models.ProjectType.byId(request.params.instanceTypeId) const inUse = projectType.getDataValue('projectCount') > 0 const updates = new app.auditLog.formatters.UpdatesCollection() if (inUse && request.body.properties) { // Don't allow the properties to be edited - this contains the billing // information and we don't want to have to update live projects reply.code(400).send({ code: 'invalid_request', error: 'Cannot edit in-use ProjectType' }) return } try { const hasValueChanged = (requestProp, existingProp) => (requestProp !== undefined && existingProp !== requestProp) if (hasValueChanged(request.body.name, projectType.name)) { updates.push('name', request.body.name) projectType.name = request.body.name } if (hasValueChanged(request.body.description, projectType.description)) { updates.push('description', request.body.description) projectType.description = request.body.description } if (hasValueChanged(request.body.active, projectType.active)) { updates.push('active', request.body.active) projectType.active = request.body.active } if (request.body.properties !== undefined) { try { const oldProps = {} const newProps = {} oldProps.properties = typeof projectType.properties === 'string' ? JSON.parse(projectType.properties) : projectType.properties newProps.properties = typeof request.body.properties === 'string' ? JSON.parse(request.body.properties) : request.body.properties updates.pushDifferences(oldProps, newProps) } catch (_error) { // Ignore } projectType.properties = request.body.properties } if (hasValueChanged(request.body.order, projectType.order)) { updates.push('order', request.body.order) projectType.order = request.body.order } if (request.body.defaultStack) { const defaultStackId = app.db.models.ProjectStack.decodeHashid(request.body.defaultStack) if (defaultStackId) { if (hasValueChanged(defaultStackId, projectType.defaultStackId)) { updates.push('defaultStackId', defaultStackId) } projectType.defaultStackId = defaultStackId } } await projectType.save() await app.auditLog.Platform.platform.projectType.updated(request.session.User, null, projectType, updates) reply.send(app.db.views.ProjectType.projectType(projectType, request.session.User.admin)) } catch (err) { let responseMessage if (err.errors) { responseMessage = err.errors.map(err => err.message).join(',') } else { responseMessage = err.toString() } const resp = { code: 'unexpected_error', error: responseMessage } await app.auditLog.Platform.platform.projectType.updated(request.session.User, resp, projectType, updates) reply.code(400).send(resp) } }) /** * Delete a project type * @name /api/v1/project-types * @static * @memberof forge.routes.api.projectTypes */ app.delete('/:instanceTypeId', { preHandler: app.needsPermission('project-type:delete'), schema: { summary: 'Delete an instance type - admin-only', tags: ['Instance Types'], params: { type: 'object', properties: { instanceTypeId: { type: 'string' } } }, response: { 200: { $ref: 'APIStatus' }, '4xx': { $ref: 'APIError' } } } }, async (request, reply) => { const projectType = await app.db.models.ProjectType.byId(request.params.instanceTypeId) // The `beforeDestroy` hook of the ProjectType model ensures // we don't delete an in-use stack if (projectType) { try { await projectType.destroy() await app.auditLog.Platform.platform.projectType.deleted(request.session.User, null, projectType) reply.send({ status: 'okay' }) } catch (err) { const resp = { code: 'unexpected_error', error: err.toString() } await app.auditLog.Platform.platform.projectType.deleted(request.session.User, resp, projectType) reply.code(400).send(resp) } } else { reply.code(404).send({ code: 'not_found', status: 'Not Found' }) } }) }