From 824c434b0b8fdf1829b57a319788ddfcff391781 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 13 Feb 2024 03:44:44 +0100 Subject: [PATCH] feat: Implement api/v1/users/:userId API endpoint --- .../api/v1/users/get-current-user.test.js | 4 +-- .../src/controllers/api/v1/users/get-user.js | 16 +++++++++ .../controllers/api/v1/users/get-user.test.js | 36 +++++++++++++++++++ packages/backend/src/helpers/authorization.js | 18 ++++++++++ packages/backend/src/routes/api/v1/users.js | 3 ++ .../backend/test/payloads/current-user.js | 32 +++++++++++++++++ packages/backend/test/payloads/user.js | 1 - 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 packages/backend/src/controllers/api/v1/users/get-user.js create mode 100644 packages/backend/src/controllers/api/v1/users/get-user.test.js create mode 100644 packages/backend/src/helpers/authorization.js create mode 100644 packages/backend/test/payloads/current-user.js diff --git a/packages/backend/src/controllers/api/v1/users/get-current-user.test.js b/packages/backend/src/controllers/api/v1/users/get-current-user.test.js index 7d33b244..04b948b3 100644 --- a/packages/backend/src/controllers/api/v1/users/get-current-user.test.js +++ b/packages/backend/src/controllers/api/v1/users/get-current-user.test.js @@ -3,7 +3,7 @@ import request from 'supertest'; import app from '../../../../app.js'; import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; import { createUser } from '../../../../../test/factories/user'; -import userPayload from '../../../../../test/payloads/user'; +import currentUserPayload from '../../../../../test/payloads/current-user.js'; describe('GET /api/v1/users/me', () => { let role, currentUser, token; @@ -20,7 +20,7 @@ describe('GET /api/v1/users/me', () => { .set('Authorization', token) .expect(200); - const expectedPayload = userPayload(currentUser, role); + const expectedPayload = currentUserPayload(currentUser, role); expect(response.body).toEqual(expectedPayload); }); }); diff --git a/packages/backend/src/controllers/api/v1/users/get-user.js b/packages/backend/src/controllers/api/v1/users/get-user.js new file mode 100644 index 00000000..3ff17303 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-user.js @@ -0,0 +1,16 @@ +import { renderObject } from '../../../../helpers/renderer.js'; +import User from '../../../../models/user.js'; + +export default async (request, response) => { + const user = await User.query() + .leftJoinRelated({ + role: true, + }) + .withGraphFetched({ + role: true, + }) + .findById(request.params.userId) + .throwIfNotFound(); + + renderObject(response, user); +}; diff --git a/packages/backend/src/controllers/api/v1/users/get-user.test.js b/packages/backend/src/controllers/api/v1/users/get-user.test.js new file mode 100644 index 00000000..35107de3 --- /dev/null +++ b/packages/backend/src/controllers/api/v1/users/get-user.test.js @@ -0,0 +1,36 @@ +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'; +import { createUser } from '../../../../../test/factories/user'; +import { createPermission } from '../../../../../test/factories/permission'; +import userPayload from '../../../../../test/payloads/user.js'; + +describe('GET /api/v1/users/:userId', () => { + let currentUser, currentUserRole, anotherUser, anotherUserRole, token; + + beforeEach(async () => { + currentUser = await createUser(); + anotherUser = await createUser(); + currentUserRole = await currentUser.$relatedQuery('role'); + anotherUserRole = await anotherUser.$relatedQuery('role'); + + await createPermission({ + roleId: currentUserRole.id, + action: 'read', + subject: 'User', + }); + + token = createAuthTokenByUserId(currentUser.id); + }); + + it('should return specified user info', async () => { + const response = await request(app) + .get(`/api/v1/users/${anotherUser.id}`) + .set('Authorization', token) + .expect(200); + + const expectedPayload = userPayload(anotherUser, anotherUserRole); + expect(response.body).toEqual(expectedPayload); + }); +}); diff --git a/packages/backend/src/helpers/authorization.js b/packages/backend/src/helpers/authorization.js new file mode 100644 index 00000000..a027dbac --- /dev/null +++ b/packages/backend/src/helpers/authorization.js @@ -0,0 +1,18 @@ +const authorizationList = { + '/api/v1/users/:userId': { + action: 'read', + subject: 'User', + }, +}; + +export const authorizeUser = async (request, response, next) => { + const currentRoute = request.baseUrl + request.route.path; + const currentRouteRule = authorizationList[currentRoute]; + + try { + request.currentUser.can(currentRouteRule.action, currentRouteRule.subject); + next(); + } catch (error) { + return response.status(403).end(); + } +}; diff --git a/packages/backend/src/routes/api/v1/users.js b/packages/backend/src/routes/api/v1/users.js index 2bd2ab94..08bc8f65 100644 --- a/packages/backend/src/routes/api/v1/users.js +++ b/packages/backend/src/routes/api/v1/users.js @@ -1,9 +1,12 @@ import { Router } from 'express'; import { authenticateUser } from '../../../helpers/authentication.js'; +import { authorizeUser } from '../../../helpers/authorization.js'; import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js'; +import getUserAction from '../../../controllers/api/v1/users/get-user.js'; const router = Router(); router.get('/me', authenticateUser, getCurrentUserAction); +router.get('/:userId', authenticateUser, authorizeUser, getUserAction); export default router; diff --git a/packages/backend/test/payloads/current-user.js b/packages/backend/test/payloads/current-user.js new file mode 100644 index 00000000..7102f6b4 --- /dev/null +++ b/packages/backend/test/payloads/current-user.js @@ -0,0 +1,32 @@ +const currentUserPayload = (currentUser, role) => { + return { + data: { + createdAt: currentUser.createdAt.toISOString(), + email: currentUser.email, + fullName: currentUser.fullName, + id: currentUser.id, + permissions: [], + role: { + createdAt: role.createdAt.toISOString(), + description: null, + id: role.id, + isAdmin: role.isAdmin, + key: role.key, + name: role.name, + updatedAt: role.updatedAt.toISOString(), + }, + roleId: role.id, + trialExpiryDate: currentUser.trialExpiryDate.toISOString(), + updatedAt: currentUser.updatedAt.toISOString(), + }, + meta: { + count: 1, + currentPage: null, + isArray: false, + totalPages: null, + type: 'User', + }, + }; +}; + +export default currentUserPayload; diff --git a/packages/backend/test/payloads/user.js b/packages/backend/test/payloads/user.js index 073dde49..02eac3d4 100644 --- a/packages/backend/test/payloads/user.js +++ b/packages/backend/test/payloads/user.js @@ -5,7 +5,6 @@ const userPayload = (currentUser, role) => { email: currentUser.email, fullName: currentUser.fullName, id: currentUser.id, - permissions: [], role: { createdAt: role.createdAt.toISOString(), description: null,