Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c319dd0

Browse files
committed
refactor(all): move admin user routes to /api/admin/users
Moved 5 admin-only user routes from /api/users/ to /api/admin/users/ to match the established admin teams pattern. Backend changes: - Created /routes/admin/users/ directory with 7 files (list, search, stats, delete, assign-role, schemas, index) - Changed middleware from requirePermission(...) to requireGlobalAdmin() for all admin routes - Removed 3 unused permissions (users.list, users.edit, users.delete) from permissions/index.ts - Updated tests to verify routes are properly separated - Deleted 5 old route files from /routes/users/ - Updated API spec files (api-spec.json, api-spec.yaml) Frontend changes: - Updated userService.ts endpoints to use /api/admin/users Routes moved: - GET /users → GET /admin/users - GET /users/search → GET /admin/users/search - GET /users/stats → GET /admin/users/stats - DELETE /users/:id → DELETE /admin/users/:id - PUT /users/:id/role → PUT /admin/users/:id/role Routes kept unchanged: - GET /users/:id (ownership-or-admin pattern) - PUT /users/:id (ownership-or-admin pattern) - GET /users/me (user self-service) - DELETE /users/me (user self-service) - GET /users/:id/teams (ownership-or-admin pattern)
1 parent f939b48 commit c319dd0

File tree

15 files changed

+9107
-9179
lines changed

15 files changed

+9107
-9179
lines changed

services/backend/api-spec.json

Lines changed: 6803 additions & 6805 deletions
Large diffs are not rendered by default.

services/backend/api-spec.yaml

Lines changed: 2054 additions & 2056 deletions
Large diffs are not rendered by default.

services/backend/src/permissions/index.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
// Define all role permissions in one place
99
export const ROLE_DEFINITIONS = {
1010
global_admin: [
11-
'users.list',
1211
'users.view',
13-
'users.edit',
14-
'users.delete',
1512
'users.create',
1613
'roles.manage',
1714
'system.admin',

services/backend/src/routes/admin/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import emailRoutes from './email';
33
import jobsRoutes from './jobs';
44
import mcpRegistryRoutes from './mcp-registry';
55
import teamsRoutes from './teams';
6+
import usersRoutes from './users';
67
import oauthProvidersRoutes from './oauth-providers';
78

89
export default async function adminRoutes(fastify: FastifyInstance) {
910
await fastify.register(emailRoutes, { prefix: '/admin/email' });
1011
await fastify.register(jobsRoutes);
1112
await fastify.register(mcpRegistryRoutes);
1213
await fastify.register(teamsRoutes, { prefix: '/admin' });
14+
await fastify.register(usersRoutes, { prefix: '/admin' });
1315
await fastify.register(oauthProvidersRoutes, { prefix: '/admin' });
1416
}

services/backend/src/routes/users/assignRole.ts renamed to services/backend/src/routes/admin/users/assign-role.ts

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
import type { FastifyInstance } from 'fastify';
2-
import { UserService } from '../../services/userService';
3-
import { requirePermission } from '../../middleware/roleMiddleware';
4-
import {
5-
ERROR_RESPONSE_SCHEMA,
6-
PARAMS_WITH_ID_SCHEMA,
2+
import { UserService } from '../../../services/userService';
3+
import { requireGlobalAdmin } from '../../../middleware/roleMiddleware';
4+
import {
5+
ERROR_RESPONSE_SCHEMA,
6+
PARAMS_WITH_ID_SCHEMA,
77
ASSIGN_ROLE_REQUEST_SCHEMA,
88
type ErrorResponse,
99
type ParamsWithId,
1010
type AssignRoleRequest
1111
} from './schemas';
1212

1313
// Route-specific Schema Constants
14-
1514
const ASSIGN_ROLE_SUCCESS_RESPONSE_SCHEMA = {
1615
type: 'object',
1716
properties: {
18-
success: {
17+
success: {
1918
type: 'boolean',
2019
description: 'Indicates if the role assignment was successful'
2120
},
22-
data: {
21+
data: {
2322
type: 'object',
2423
description: 'Updated user data with new role'
2524
},
26-
message: {
25+
message: {
2726
type: 'string',
2827
description: 'Success message'
2928
}
@@ -38,22 +37,22 @@ interface AssignRoleSuccessResponse {
3837
message: string;
3938
}
4039

41-
export default async function assignRoleRoute(server: FastifyInstance) {
40+
export default async function assignRoleAdminRoute(server: FastifyInstance) {
4241
const userService = new UserService();
4342

44-
// PUT /users/:id/role - Assign role to user (admin only)
43+
// PUT /admin/users/:id/role - Assign role to user (Global Admin only)
4544
server.put('/users/:id/role', {
46-
preValidation: requirePermission('users.edit'), // ✅ Authorization BEFORE validation
45+
preValidation: requireGlobalAdmin(),
4746
schema: {
48-
tags: ['Users'],
49-
summary: 'Assign role to user',
50-
description: 'Assigns a role to a specific user. Requires admin permissions. Users cannot change their own role. Requires Content-Type: application/json header when sending request body.',
47+
tags: ['Admin - Users'],
48+
summary: 'Assign role to user (Global Admin)',
49+
description: 'Allows global administrators to assign a role to a specific user. Users cannot change their own role. Requires Content-Type: application/json header when sending request body.',
5150
security: [{ cookieAuth: [] }],
52-
51+
5352
// Fastify validation schemas
5453
params: PARAMS_WITH_ID_SCHEMA,
5554
body: ASSIGN_ROLE_REQUEST_SCHEMA,
56-
55+
5756
// OpenAPI documentation (same schemas, reused)
5857
requestBody: {
5958
required: true,
@@ -63,7 +62,7 @@ export default async function assignRoleRoute(server: FastifyInstance) {
6362
}
6463
}
6564
},
66-
65+
6766
response: {
6867
200: {
6968
...ASSIGN_ROLE_SUCCESS_RESPONSE_SCHEMA,
@@ -79,7 +78,7 @@ export default async function assignRoleRoute(server: FastifyInstance) {
7978
},
8079
403: {
8180
...ERROR_RESPONSE_SCHEMA,
82-
description: 'Forbidden - Insufficient permissions or cannot change own role'
81+
description: 'Forbidden - Cannot change own role'
8382
},
8483
404: {
8584
...ERROR_RESPONSE_SCHEMA,
@@ -96,7 +95,7 @@ export default async function assignRoleRoute(server: FastifyInstance) {
9695
// TypeScript type assertions (Fastify has already validated)
9796
const { id } = request.params as ParamsWithId;
9897
const { role_id } = request.body as AssignRoleRequest;
99-
98+
10099
// Prevent users from changing their own role
101100
if (request.user?.id === id) {
102101
const errorResponse: ErrorResponse = {
@@ -106,9 +105,9 @@ export default async function assignRoleRoute(server: FastifyInstance) {
106105
const jsonString = JSON.stringify(errorResponse);
107106
return reply.status(403).type('application/json').send(jsonString);
108107
}
109-
108+
110109
const success = await userService.assignRole(id, role_id);
111-
110+
112111
if (!success) {
113112
const errorResponse: ErrorResponse = {
114113
success: false,
@@ -129,7 +128,7 @@ export default async function assignRoleRoute(server: FastifyInstance) {
129128
const jsonString = JSON.stringify(successResponse);
130129
return reply.status(200).type('application/json').send(jsonString);
131130
} catch (error) {
132-
server.log.error(error, 'Error assigning role');
131+
server.log.error(error, 'Error assigning role in admin route');
133132
const errorResponse: ErrorResponse = {
134133
success: false,
135134
error: 'Failed to assign role'

services/backend/src/routes/users/deleteUser.ts renamed to services/backend/src/routes/admin/users/delete.ts

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,33 @@
11
import type { FastifyInstance } from 'fastify';
2-
import { UserService } from '../../services/userService';
3-
import { requirePermission } from '../../middleware/roleMiddleware';
4-
import {
5-
ERROR_RESPONSE_SCHEMA,
2+
import { UserService } from '../../../services/userService';
3+
import { requireGlobalAdmin } from '../../../middleware/roleMiddleware';
4+
import {
5+
ERROR_RESPONSE_SCHEMA,
6+
SUCCESS_MESSAGE_RESPONSE_SCHEMA,
67
PARAMS_WITH_ID_SCHEMA,
78
type ErrorResponse,
9+
type SuccessMessageResponse,
810
type ParamsWithId
911
} from './schemas';
1012

11-
// Route-specific Schema Constants
12-
13-
const DELETE_USER_SUCCESS_RESPONSE_SCHEMA = {
14-
type: 'object',
15-
properties: {
16-
success: {
17-
type: 'boolean',
18-
description: 'Indicates if the user deletion was successful'
19-
},
20-
message: {
21-
type: 'string',
22-
description: 'Success message'
23-
}
24-
},
25-
required: ['success', 'message']
26-
} as const;
27-
28-
// TypeScript interfaces for route-specific types
29-
interface DeleteUserSuccessResponse {
30-
success: boolean;
31-
message: string;
32-
}
33-
34-
export default async function deleteUserRoute(server: FastifyInstance) {
13+
export default async function deleteUserAdminRoute(server: FastifyInstance) {
3514
const userService = new UserService();
3615

37-
// DELETE /users/:id - Delete user (admin only)
16+
// DELETE /admin/users/:id - Delete user (Global Admin only)
3817
server.delete('/users/:id', {
39-
preValidation: requirePermission('users.delete'), // ✅ Authorization BEFORE validation
18+
preValidation: requireGlobalAdmin(),
4019
schema: {
41-
tags: ['Users'],
42-
summary: 'Delete user',
43-
description: 'Deletes a user from the system. Requires admin permissions. Users cannot delete themselves.',
20+
tags: ['Admin - Users'],
21+
summary: 'Delete user (Global Admin)',
22+
description: 'Allows global administrators to delete a user from the system. Users cannot delete themselves. Cannot delete the last global administrator.',
4423
security: [{ cookieAuth: [] }],
45-
24+
4625
// Fastify validation schema
4726
params: PARAMS_WITH_ID_SCHEMA,
48-
27+
4928
response: {
5029
200: {
51-
...DELETE_USER_SUCCESS_RESPONSE_SCHEMA,
30+
...SUCCESS_MESSAGE_RESPONSE_SCHEMA,
5231
description: 'User deleted successfully'
5332
},
5433
401: {
@@ -57,7 +36,7 @@ export default async function deleteUserRoute(server: FastifyInstance) {
5736
},
5837
403: {
5938
...ERROR_RESPONSE_SCHEMA,
60-
description: 'Forbidden - Insufficient permissions or cannot delete own account'
39+
description: 'Forbidden - Cannot delete own account or last global administrator'
6140
},
6241
404: {
6342
...ERROR_RESPONSE_SCHEMA,
@@ -73,7 +52,7 @@ export default async function deleteUserRoute(server: FastifyInstance) {
7352
try {
7453
// TypeScript type assertion (Fastify has already validated)
7554
const { id } = request.params as ParamsWithId;
76-
55+
7756
// Prevent users from deleting themselves
7857
if (request.user?.id === id) {
7958
const errorResponse: ErrorResponse = {
@@ -83,9 +62,9 @@ export default async function deleteUserRoute(server: FastifyInstance) {
8362
const jsonString = JSON.stringify(errorResponse);
8463
return reply.status(403).type('application/json').send(jsonString);
8564
}
86-
65+
8766
const success = await userService.deleteUser(id);
88-
67+
8968
if (!success) {
9069
const errorResponse: ErrorResponse = {
9170
success: false,
@@ -95,7 +74,7 @@ export default async function deleteUserRoute(server: FastifyInstance) {
9574
return reply.status(404).type('application/json').send(jsonString);
9675
}
9776

98-
const successResponse: DeleteUserSuccessResponse = {
77+
const successResponse: SuccessMessageResponse = {
9978
success: true,
10079
message: 'User deleted successfully'
10180
};
@@ -110,8 +89,8 @@ export default async function deleteUserRoute(server: FastifyInstance) {
11089
const jsonString = JSON.stringify(errorResponse);
11190
return reply.status(403).type('application/json').send(jsonString);
11291
}
113-
114-
server.log.error(error, 'Error deleting user');
92+
93+
server.log.error(error, 'Error deleting user in admin route');
11594
const errorResponse: ErrorResponse = {
11695
success: false,
11796
error: 'Failed to delete user'
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { type FastifyInstance } from 'fastify';
2+
import listUsersAdminRoute from './list';
3+
import searchUsersAdminRoute from './search';
4+
import getUserStatsAdminRoute from './stats';
5+
import deleteUserAdminRoute from './delete';
6+
import assignRoleAdminRoute from './assign-role';
7+
8+
export default async function adminUsersRoutes(server: FastifyInstance) {
9+
await server.register(listUsersAdminRoute);
10+
await server.register(searchUsersAdminRoute);
11+
await server.register(getUserStatsAdminRoute);
12+
await server.register(deleteUserAdminRoute);
13+
await server.register(assignRoleAdminRoute);
14+
}

services/backend/src/routes/users/listUsers.ts renamed to services/backend/src/routes/admin/users/list.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FastifyInstance } from 'fastify';
2-
import { UserService } from '../../services/userService';
3-
import { requirePermission } from '../../middleware/roleMiddleware';
2+
import { UserService } from '../../../services/userService';
3+
import { requireGlobalAdmin } from '../../../middleware/roleMiddleware';
44
import {
55
ERROR_RESPONSE_SCHEMA,
66
PAGINATION_QUERY_SCHEMA,
@@ -12,16 +12,16 @@ import {
1212
validatePaginationParams
1313
} from './schemas';
1414

15-
export default async function listUsersRoute(server: FastifyInstance) {
15+
export default async function listUsersAdminRoute(server: FastifyInstance) {
1616
const userService = new UserService();
1717

18-
// GET /users - List all users (admin only) with pagination
18+
// GET /admin/users - List all users (Global Admin only) with pagination
1919
server.get('/users', {
20-
preValidation: requirePermission('users.list'),
20+
preValidation: requireGlobalAdmin(),
2121
schema: {
22-
tags: ['Users'],
23-
summary: 'List all users',
24-
description: 'Retrieves a paginated list of all users in the system. Requires admin permissions. Supports pagination with limit (1-100, default: 20) and offset (default: 0) parameters.',
22+
tags: ['Admin - Users'],
23+
summary: 'List all users (Global Admin)',
24+
description: 'Allows global administrators to retrieve a paginated list of all users in the system. Supports pagination with limit (1-100, default: 20) and offset (default: 0) parameters.',
2525
security: [{ cookieAuth: [] }],
2626

2727
querystring: PAGINATION_QUERY_SCHEMA,
@@ -41,7 +41,7 @@ export default async function listUsersRoute(server: FastifyInstance) {
4141
},
4242
403: {
4343
...ERROR_RESPONSE_SCHEMA,
44-
description: 'Forbidden - Insufficient permissions'
44+
description: 'Forbidden - Global admin required'
4545
},
4646
500: {
4747
...ERROR_RESPONSE_SCHEMA,
@@ -79,11 +79,11 @@ export default async function listUsersRoute(server: FastifyInstance) {
7979
const paginatedUsers = serializedUsers.slice(offset, offset + limit);
8080

8181
server.log.info({
82-
operation: 'list_users',
82+
operation: 'list_users_admin',
8383
totalResults: total,
8484
returnedResults: paginatedUsers.length,
8585
pagination: { limit, offset }
86-
}, 'Users list completed');
86+
}, 'Admin users list completed');
8787

8888
const successResponse: UsersListPaginatedResponse = {
8989
success: true,
@@ -111,7 +111,7 @@ export default async function listUsersRoute(server: FastifyInstance) {
111111
return reply.status(400).type('application/json').send(jsonString);
112112
}
113113

114-
server.log.error(error, 'Error fetching users');
114+
server.log.error(error, 'Error fetching users in admin route');
115115
const errorResponse: ErrorResponse = {
116116
success: false,
117117
error: 'Failed to fetch users'

0 commit comments

Comments
 (0)