From 0ca12d5a7ceb6829abb381354e8247c6addf8e0e Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 27 Jan 2025 13:46:25 +0100 Subject: [PATCH] feat: Implement create folder API endpoint --- .../api/v1/folders/create-folder.js | 11 +++++ .../api/v1/folders/create-folder.test.js | 43 +++++++++++++++++++ packages/backend/src/helpers/authorization.js | 4 ++ packages/backend/src/models/user.js | 9 ++++ packages/backend/src/models/user.test.js | 9 ++++ packages/backend/src/routes/api/v1/folders.js | 10 +++++ packages/backend/src/routes/index.js | 2 + packages/backend/src/serializers/folder.js | 10 +++++ .../backend/src/serializers/folder.test.js | 22 ++++++++++ packages/backend/src/serializers/index.js | 2 + packages/backend/test/factories/folder.js | 10 +++++ .../rest/api/v1/folders/create-folder.js | 21 +++++++++ 12 files changed, 153 insertions(+) create mode 100644 packages/backend/src/controllers/api/v1/folders/create-folder.js create mode 100644 packages/backend/src/controllers/api/v1/folders/create-folder.test.js create mode 100644 packages/backend/src/routes/api/v1/folders.js create mode 100644 packages/backend/src/serializers/folder.js create mode 100644 packages/backend/src/serializers/folder.test.js create mode 100644 packages/backend/test/factories/folder.js create mode 100644 packages/backend/test/mocks/rest/api/v1/folders/create-folder.js diff --git a/packages/backend/src/controllers/api/v1/folders/create-folder.js b/packages/backend/src/controllers/api/v1/folders/create-folder.js new file mode 100644 index 00000000..4c50a05c --- /dev/null +++ b/packages/backend/src/controllers/api/v1/folders/create-folder.js @@ -0,0 +1,11 @@ +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const folder = await request.currentUser + .$relatedQuery('folders') + .insertAndFetch({ + name: request.body.name, + }); + + renderObject(response, folder, { status: 201 }); +}; diff --git a/packages/backend/src/controllers/api/v1/folders/create-folder.test.js b/packages/backend/src/controllers/api/v1/folders/create-folder.test.js new file mode 100644 index 00000000..02b3329e --- /dev/null +++ b/packages/backend/src/controllers/api/v1/folders/create-folder.test.js @@ -0,0 +1,43 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import createFolderMock from '../../../../../test/mocks/rest/api/v1/folders/create-folder.js'; +import { createPermission } from '../../../../../test/factories/permission.js'; + +describe('POST /api/v1/folders', () => { + let currentUser, currentUserRole, token; + + beforeEach(async () => { + currentUser = await createUser(); + currentUserRole = await currentUser.$relatedQuery('role'); + + token = await createAuthTokenByUserId(currentUser.id); + }); + + it('should return created flow', async () => { + await createPermission({ + action: 'create', + subject: 'Flow', + roleId: currentUserRole.id, + conditions: ['isCreator'], + }); + + const response = await request(app) + .post('/api/v1/folders') + .set('Authorization', token) + .send({ + name: 'Test Folder', + }) + .expect(201); + + const refetchedFolder = await currentUser + .$relatedQuery('folders') + .findById(response.body.data.id); + + const expectedPayload = await createFolderMock(refetchedFolder); + + expect(response.body).toMatchObject(expectedPayload); + }); +}); diff --git a/packages/backend/src/helpers/authorization.js b/packages/backend/src/helpers/authorization.js index e9f6fb0e..36c7176f 100644 --- a/packages/backend/src/helpers/authorization.js +++ b/packages/backend/src/helpers/authorization.js @@ -133,6 +133,10 @@ const authorizationList = { action: 'create', subject: 'Connection', }, + 'POST /api/v1/folders/': { + action: 'create', + subject: 'Flow', + }, }; export const authorizeUser = async (request, response, next) => { diff --git a/packages/backend/src/models/user.js b/packages/backend/src/models/user.js index 99314e07..1ed7ece5 100644 --- a/packages/backend/src/models/user.js +++ b/packages/backend/src/models/user.js @@ -20,6 +20,7 @@ import Permission from './permission.js'; import Role from './role.js'; import Step from './step.js'; import Subscription from './subscription.ee.js'; +import Folder from './folder.js'; import UsageData from './usage-data.ee.js'; import Billing from '../helpers/billing/index.ee.js'; import NotAuthorizedError from '../errors/not-authorized.js'; @@ -178,6 +179,14 @@ class User extends Base { to: 'users.id', }, }, + folders: { + relation: Base.HasManyRelation, + modelClass: Folder, + join: { + from: 'users.id', + to: 'folders.user_id', + }, + }, }); static get virtualAttributes() { diff --git a/packages/backend/src/models/user.test.js b/packages/backend/src/models/user.test.js index 159af7ee..86a5dd06 100644 --- a/packages/backend/src/models/user.test.js +++ b/packages/backend/src/models/user.test.js @@ -14,6 +14,7 @@ import Role from './role.js'; import Step from './step.js'; import Subscription from './subscription.ee.js'; import UsageData from './usage-data.ee.js'; +import Folder from './folder.js'; import User from './user.js'; import deleteUserQueue from '../queues/delete-user.ee.js'; import emailQueue from '../queues/email.js'; @@ -153,6 +154,14 @@ describe('User model', () => { to: 'users.id', }, }, + folders: { + relation: Base.HasManyRelation, + modelClass: Folder, + join: { + from: 'users.id', + to: 'folders.user_id', + }, + }, }; expect(relationMappings).toStrictEqual(expectedRelations); diff --git a/packages/backend/src/routes/api/v1/folders.js b/packages/backend/src/routes/api/v1/folders.js new file mode 100644 index 00000000..d09659b2 --- /dev/null +++ b/packages/backend/src/routes/api/v1/folders.js @@ -0,0 +1,10 @@ +import { Router } from 'express'; +import { authenticateUser } from '../../../helpers/authentication.js'; +import { authorizeUser } from '../../../helpers/authorization.js'; +import createFolderAction from '../../../controllers/api/v1/folders/create-folder.js'; + +const router = Router(); + +router.post('/', authenticateUser, authorizeUser, createFolderAction); + +export default router; diff --git a/packages/backend/src/routes/index.js b/packages/backend/src/routes/index.js index c3ad6de0..d1841e42 100644 --- a/packages/backend/src/routes/index.js +++ b/packages/backend/src/routes/index.js @@ -19,6 +19,7 @@ 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'; import installationUsersRouter from './api/v1/installation/users.js'; +import foldersRouter from './api/v1/folders.js'; const router = Router(); @@ -42,5 +43,6 @@ 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/installation/users', installationUsersRouter); +router.use('/api/v1/folders', foldersRouter); export default router; diff --git a/packages/backend/src/serializers/folder.js b/packages/backend/src/serializers/folder.js new file mode 100644 index 00000000..9c0ae29e --- /dev/null +++ b/packages/backend/src/serializers/folder.js @@ -0,0 +1,10 @@ +const folderSerilializer = (folder) => { + return { + id: folder.id, + name: folder.name, + createdAt: folder.createdAt.getTime(), + updatedAt: folder.updatedAt.getTime(), + }; +}; + +export default folderSerilializer; diff --git a/packages/backend/src/serializers/folder.test.js b/packages/backend/src/serializers/folder.test.js new file mode 100644 index 00000000..155c0bd4 --- /dev/null +++ b/packages/backend/src/serializers/folder.test.js @@ -0,0 +1,22 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { createFolder } from '../../test/factories/folder'; +import folderSerializer from './folder'; + +describe('folder serializer', () => { + let folder; + + beforeEach(async () => { + folder = await createFolder(); + }); + + it('should return folder data', async () => { + const expectedPayload = { + id: folder.id, + name: folder.name, + createdAt: folder.createdAt.getTime(), + updatedAt: folder.updatedAt.getTime(), + }; + + expect(folderSerializer(folder)).toStrictEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/serializers/index.js b/packages/backend/src/serializers/index.js index 4525b5ae..ba53399f 100644 --- a/packages/backend/src/serializers/index.js +++ b/packages/backend/src/serializers/index.js @@ -19,6 +19,7 @@ import executionStepSerializer from './execution-step.js'; import subscriptionSerializer from './subscription.ee.js'; import adminUserSerializer from './admin/user.js'; import configSerializer from './config.js'; +import folderSerializer from './folder.js'; const serializers = { AdminUser: adminUserSerializer, @@ -42,6 +43,7 @@ const serializers = { ExecutionStep: executionStepSerializer, Subscription: subscriptionSerializer, Config: configSerializer, + Folder: folderSerializer, }; export default serializers; diff --git a/packages/backend/test/factories/folder.js b/packages/backend/test/factories/folder.js new file mode 100644 index 00000000..02dfbf51 --- /dev/null +++ b/packages/backend/test/factories/folder.js @@ -0,0 +1,10 @@ +import Folder from '../../src/models/folder.js'; +import { faker } from '@faker-js/faker'; + +export const createFolder = async (params = {}) => { + params.name = params?.name || faker.lorem.word(); + + const folder = await Folder.query().insertAndFetch(params); + + return folder; +}; diff --git a/packages/backend/test/mocks/rest/api/v1/folders/create-folder.js b/packages/backend/test/mocks/rest/api/v1/folders/create-folder.js new file mode 100644 index 00000000..8967a493 --- /dev/null +++ b/packages/backend/test/mocks/rest/api/v1/folders/create-folder.js @@ -0,0 +1,21 @@ +const createFolderMock = async (flow) => { + const data = { + id: flow.id, + name: flow.name, + createdAt: flow.createdAt.getTime(), + updatedAt: flow.updatedAt.getTime(), + }; + + return { + data: data, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'Folder', + }, + }; +}; + +export default createFolderMock;