feat(api): add create template endpoint

This commit is contained in:
Ali BARIN
2025-04-25 11:35:37 +00:00
parent 56100ac197
commit fdded19c30
5 changed files with 176 additions and 2 deletions

View File

@@ -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: 'PublicTemplate',
status: 201,
});
};
const templateParams = (request) => {
const { name, flowId } = request.body;
return {
name,
flowId,
};
};

View File

@@ -0,0 +1,130 @@
import Crypto from 'node:crypto';
import request from 'supertest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createApiToken } from '../../../../../test/factories/api-token.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import createTemplateMock from '../../../../../test/mocks/rest/api/v1/templates/create-template.ee.js';
import app from '../../../../app.js';
import * as license from '../../../../helpers/license.ee.js';
import Template from '../../../../models/template.ee.js';
describe('POST /api/v1/templates', () => {
let token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
token = (await createApiToken()).token;
});
it('should return the created template', async () => {
const flow = await createFlow();
const triggerStep = await createStep({
flowId: flow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
name: 'Catch raw webhook',
parameters: {
workSynchronously: true,
},
position: 1,
webhookPath: `/webhooks/flows/${flow.id}/sync`,
});
await createStep({
flowId: flow.id,
type: 'action',
appKey: 'formatter',
key: 'text',
name: 'Text',
parameters: {
input: `hello {{step.${triggerStep.id}.query.sample}} world`,
transform: 'capitalize',
},
position: 2,
});
const templatePayload = {
name: 'Sample Template Name',
flowId: flow.id,
};
const response = await request(app)
.post('/api/v1/templates')
.set('x-api-token', token)
.send(templatePayload)
.expect(201);
const refetchedTemplate = await Template.query().findById(
response.body.data.id
);
const expectedPayload = await createTemplateMock(refetchedTemplate);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should return not found response for invalid flow ID', async () => {
const invalidFlowId = Crypto.randomUUID();
await request(app)
.post('/api/v1/templates')
.set('x-api-token', token)
.send({
name: 'Sample Template Name',
flowId: invalidFlowId,
})
.expect(404);
});
it('should return unprocessable entity response for invalid name', async () => {
const flow = await createFlow();
const triggerStep = await createStep({
flowId: flow.id,
type: 'trigger',
appKey: 'webhook',
key: 'catchRawWebhook',
name: 'Catch raw webhook',
parameters: {
workSynchronously: true,
},
position: 1,
webhookPath: `/webhooks/flows/${flow.id}/sync`,
});
await createStep({
flowId: flow.id,
type: 'action',
appKey: 'formatter',
key: 'text',
name: 'Text',
parameters: {
input: `hello {{step.${triggerStep.id}.query.sample}} world`,
transform: 'capitalize',
},
position: 2,
});
const templatePayload = {
name: '',
flowId: flow.id,
};
const response = await request(app)
.post('/api/v1/templates')
.set('x-api-token', token)
.send(templatePayload)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
name: ['must NOT have fewer than 1 characters'],
},
meta: { type: 'ModelValidation' },
});
});
});

View File

@@ -1,11 +1,13 @@
import { Router } from 'express';
import getTemplateAction from '../../../controllers/api/v1/templates/get-template.ee.js';
import createTemplateAction from '../../../controllers/api/v1/templates/create-template.ee.js';
import deleteTemplateAction from '../../../controllers/api/v1/templates/delete-template.ee.js';
import getTemplatesAction from '../../../controllers/api/v1/templates/get-templates.ee.js';
const router = Router();
router.get('/', getTemplatesAction);
router.post('/', createTemplateAction);
router.get('/:templateId', getTemplateAction);
router.delete('/:templateId', deleteTemplateAction);

View File

@@ -0,0 +1,22 @@
const createTemplateMock = async (template) => {
const data = {
id: template.id,
name: template.name,
createdAt: template.createdAt.getTime(),
updatedAt: template.updatedAt.getTime(),
flowData: template.getFlowDataWithIconUrls(),
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Template',
},
};
};
export default createTemplateMock;

View File

@@ -28,10 +28,10 @@ export default defineConfig({
],
thresholds: {
autoUpdate: true,
statements: 99.41,
statements: 99.42,
branches: 98.36,
functions: 99.07,
lines: 99.41,
lines: 99.42,
},
},
},