From 46570f938804b1c2957bda1aa3ab7aeac98c68e9 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 24 Apr 2025 10:53:27 +0000 Subject: [PATCH] feat(api): add get folders endpoint for users --- .../api/v1/users/get-folders.ee.js | 12 +++ .../api/v1/users/get-folders.ee.test.js | 75 +++++++++++++++++++ packages/backend/src/routes/api/index.js | 2 + .../backend/src/routes/api/v1/users.ee.js | 8 ++ .../mocks/rest/api/v1/folders/get-folders.js | 2 +- packages/backend/vitest.config.js | 10 +-- 6 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 packages/backend/src/controllers/api/v1/users/get-folders.ee.js create mode 100644 packages/backend/src/controllers/api/v1/users/get-folders.ee.test.js create mode 100644 packages/backend/src/routes/api/v1/users.ee.js diff --git a/packages/backend/src/controllers/api/v1/users/get-folders.ee.js b/packages/backend/src/controllers/api/v1/users/get-folders.ee.js new file mode 100644 index 00000000..c82097c0 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-folders.ee.js @@ -0,0 +1,12 @@ +import User from '../../../../models/user.js'; +import { renderObject } from '../../../../helpers/renderer.js'; + +export default async (request, response) => { + const user = await User.query() + .findById(request.params.userId) + .throwIfNotFound(); + + const folders = await user.$relatedQuery('folders').orderBy('name', 'asc'); + + renderObject(response, folders, { serializer: 'Folder' }); +}; diff --git a/packages/backend/src/controllers/api/v1/users/get-folders.ee.test.js b/packages/backend/src/controllers/api/v1/users/get-folders.ee.test.js new file mode 100644 index 00000000..2e418c98 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-folders.ee.test.js @@ -0,0 +1,75 @@ +import Crypto from 'node:crypto'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import request from 'supertest'; +import app from '../../../../app.js'; +import { createApiToken } from '../../../../../test/factories/api-token.js'; +import { createUser } from '../../../../../test/factories/user.js'; +import { createFolder } from '../../../../../test/factories/folder.js'; +import getFoldersMock from '../../../../../test/mocks/rest/api/v1/folders/get-folders.js'; +import * as license from '../../../../helpers/license.ee.js'; + +describe('GET /api/v1/users/:userId/folders', () => { + let folderA, folderB, currentUser, token; + + beforeEach(async () => { + vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); + + currentUser = await createUser(); + + folderA = await createFolder({ + name: 'Folder A', + userId: currentUser.id, + }); + + folderB = await createFolder({ + name: 'Folder B', + userId: currentUser.id, + }); + + await createFolder({ + name: 'Folder C', + }); + + token = (await createApiToken()).token; + }); + + it('should return folders of the given user', async () => { + const response = await request(app) + .get(`/api/v1/users/${currentUser.id}/folders`) + .set('x-api-token', token) + .expect(200); + + const expectedPayload = await getFoldersMock([folderA, folderB]); + + expect(response.body).toStrictEqual(expectedPayload); + }); + + it('should respond with HTTP 404 for non-existent user', async () => { + const notExistingUserId = Crypto.randomUUID(); + + await request(app) + .get(`/api/v1/users/${notExistingUserId}/folders`) + .set('x-api-token', token) + .expect(404); + }); + + it('should return bad request response for invalid user UUID', async () => { + await request(app) + .get(`/api/v1/users/invalidUserUUID/folders`) + .set('x-api-token', token) + .expect(400); + }); + + it('should respond with HTTP 404 for no folders of the given user', async () => { + const user = await createUser(); + + const response = await request(app) + .get(`/api/v1/users/${user.id}/folders`) + .set('x-api-token', token) + .expect(200); + + const expectedPayload = await getFoldersMock([]); + + expect(response.body).toStrictEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/routes/api/index.js b/packages/backend/src/routes/api/index.js index c0076783..b5a52346 100644 --- a/packages/backend/src/routes/api/index.js +++ b/packages/backend/src/routes/api/index.js @@ -1,10 +1,12 @@ import { Router } from 'express'; import appsRouter from './v1/apps.ee.js'; import foldersRouter from './v1/folders.ee.js'; +import usersRouter from './v1/users.ee.js'; const router = Router(); router.use('/v1/apps', appsRouter); router.use('/v1/folders', foldersRouter); +router.use('/v1/users', usersRouter); export default router; diff --git a/packages/backend/src/routes/api/v1/users.ee.js b/packages/backend/src/routes/api/v1/users.ee.js new file mode 100644 index 00000000..f82b361c --- /dev/null +++ b/packages/backend/src/routes/api/v1/users.ee.js @@ -0,0 +1,8 @@ +import { Router } from 'express'; +import getFoldersAction from '../../../controllers/api/v1/users/get-folders.ee.js'; + +const router = Router(); + +router.get('/:userId/folders', getFoldersAction); + +export default router; diff --git a/packages/backend/test/mocks/rest/api/v1/folders/get-folders.js b/packages/backend/test/mocks/rest/api/v1/folders/get-folders.js index 2dc7fbbe..fca71a1e 100644 --- a/packages/backend/test/mocks/rest/api/v1/folders/get-folders.js +++ b/packages/backend/test/mocks/rest/api/v1/folders/get-folders.js @@ -15,7 +15,7 @@ const getFoldersMock = async (folders) => { currentPage: null, isArray: true, totalPages: null, - type: 'Folder', + type: folders.length ? 'Folder' : 'Object', }, }; }; diff --git a/packages/backend/vitest.config.js b/packages/backend/vitest.config.js index 7312fae5..a0336845 100644 --- a/packages/backend/vitest.config.js +++ b/packages/backend/vitest.config.js @@ -28,11 +28,11 @@ export default defineConfig({ ], thresholds: { autoUpdate: true, - statements: 99.38, - branches: 98.25, - functions: 99.02, - lines: 99.38, + statements: 99.39, + branches: 98.28, + functions: 99.03, + lines: 99.39, }, }, }, -}); +}); \ No newline at end of file