From 72436f7d3b651cc230467b64720f9f3e2ff2b987 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 21 Feb 2025 17:49:15 +0100 Subject: [PATCH] feat: Implement admin create template API endpoint --- .../v1/admin/templates/create-template.ee.js | 20 ++++++ packages/backend/src/models/template.ee.js | 8 +++ .../backend/src/models/template.ee.test.js | 61 +++++++++++++++++++ .../src/routes/api/v1/admin/templates.ee.js | 11 ++++ packages/backend/src/routes/index.js | 2 + 5 files changed, 102 insertions(+) create mode 100644 packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.js create mode 100644 packages/backend/src/routes/api/v1/admin/templates.ee.js diff --git a/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.js b/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.js new file mode 100644 index 00000000..404c9ca9 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/admin/templates/create-template.ee.js @@ -0,0 +1,20 @@ +import { renderObject } from '../../../../../helpers/renderer.js'; +import Template from '../../../../../models/template.ee.js'; + +export default async (request, response) => { + const template = await Template.create(templateParams(request)); + + renderObject(response, template, { + serializer: 'AdminTemplate', + status: 201, + }); +}; + +const templateParams = (request) => { + const { name, flowId } = request.body; + + return { + name, + flowId, + }; +}; diff --git a/packages/backend/src/models/template.ee.js b/packages/backend/src/models/template.ee.js index 7137445f..aad0192c 100644 --- a/packages/backend/src/models/template.ee.js +++ b/packages/backend/src/models/template.ee.js @@ -1,4 +1,5 @@ import Base from './base.js'; +import Flow from './flow.js'; class Template extends Base { static tableName = 'templates'; @@ -15,6 +16,13 @@ class Template extends Base { updatedAt: { type: 'string' }, }, }; + + static async create(name, flowId) { + const flow = await Flow.query().findById(flowId).throwIfNotFound(); + const flowData = await flow.export(); + + return this.query().insertAndFetch({ name, flowData }); + } } export default Template; diff --git a/packages/backend/src/models/template.ee.test.js b/packages/backend/src/models/template.ee.test.js index 560d8dc0..ce85e336 100644 --- a/packages/backend/src/models/template.ee.test.js +++ b/packages/backend/src/models/template.ee.test.js @@ -1,5 +1,8 @@ import { describe, it, expect } from 'vitest'; +import Crypto from 'crypto'; import Template from './template.ee.js'; +import { createFlow } from '../../test/factories/flow'; +import { createStep } from '../../test/factories/step'; describe('Template model', () => { it('tableName should return correct name', () => { @@ -9,4 +12,62 @@ describe('Template model', () => { it('jsonSchema should have correct validations', () => { expect(Template.jsonSchema).toMatchSnapshot(); }); + + describe('create', () => { + it('should throw an error if the flow does not exist', async () => { + const nonExistentFlowId = Crypto.randomUUID(); + const templateName = 'Test Template'; + + await expect( + Template.create(templateName, nonExistentFlowId) + ).rejects.toThrowError('NotFoundError'); + }); + + it('should create template with the name', async () => { + const flow = await createFlow(); + const templateName = 'Test Template'; + + const template = await Template.create(templateName, flow.id); + + expect(template.name).toStrictEqual(templateName); + }); + + it('should create template with the flow data', async () => { + const flow = await createFlow(); + + const triggerStep = await createStep({ + flowId: flow.id, + type: 'trigger', + appKey: 'webhook', + key: 'catchRawWebhook', + }); + + await createStep({ + flowId: flow.id, + type: 'action', + appKey: 'ntfy', + key: 'sendMessage', + parameters: { + topic: 'Test notification', + message: `Message: {{step.${triggerStep.id}.body.message}} by {{step.${triggerStep.id}.body.sender}}`, + }, + }); + + const templateName = 'Test Template'; + const template = await Template.create(templateName, flow.id); + + const exportedFlowData = await flow.export(); + + expect(template.flowData).toMatchObject({ + name: exportedFlowData.name, + steps: template.flowData.steps.map((step) => ({ + appKey: step.appKey, + key: step.key, + name: step.name, + position: step.position, + type: step.type, + })), + }); + }); + }); }); diff --git a/packages/backend/src/routes/api/v1/admin/templates.ee.js b/packages/backend/src/routes/api/v1/admin/templates.ee.js new file mode 100644 index 00000000..749d202d --- /dev/null +++ b/packages/backend/src/routes/api/v1/admin/templates.ee.js @@ -0,0 +1,11 @@ +import { Router } from 'express'; +import { authenticateUser } from '../../../../helpers/authentication.js'; +import { authorizeAdmin } from '../../../../helpers/authorization.js'; + +import createTemplateAction from '../../../../controllers/api/v1/admin/templates/create-template.ee.js'; + +const router = Router(); + +router.post('/', authenticateUser, authorizeAdmin, createTemplateAction); + +export default router; diff --git a/packages/backend/src/routes/index.js b/packages/backend/src/routes/index.js index d1841e42..958ab180 100644 --- a/packages/backend/src/routes/index.js +++ b/packages/backend/src/routes/index.js @@ -15,6 +15,7 @@ import samlAuthProvidersRouter from './api/v1/saml-auth-providers.ee.js'; import adminAppsRouter from './api/v1/admin/apps.ee.js'; import adminConfigRouter from './api/v1/admin/config.ee.js'; import adminSamlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js'; +import adminTemplatesRouter from './api/v1/admin/templates.ee.js'; import rolesRouter from './api/v1/admin/roles.ee.js'; import permissionsRouter from './api/v1/admin/permissions.ee.js'; import adminUsersRouter from './api/v1/admin/users.ee.js'; @@ -42,6 +43,7 @@ router.use('/api/v1/admin/users', adminUsersRouter); router.use('/api/v1/admin/roles', rolesRouter); router.use('/api/v1/admin/permissions', permissionsRouter); router.use('/api/v1/admin/saml-auth-providers', adminSamlAuthProvidersRouter); +router.use('/api/v1/admin/templates', adminTemplatesRouter); router.use('/api/v1/installation/users', installationUsersRouter); router.use('/api/v1/folders', foldersRouter);