feat(users): refactor users system
This commit is contained in:
+148
-7
@@ -9,6 +9,7 @@
|
||||
|
||||
import { query, updateById } from '@zen/core/database';
|
||||
import { updateUser } from './lib/auth.js';
|
||||
import { listRoles, getRoleById, createRole, updateRole, deleteRole, getUserRoles, assignUserRole, revokeUserRole } from '@zen/core/users';
|
||||
import { uploadImage, deleteFile, generateUniqueFilename, getFileExtension, FILE_TYPE_PRESETS, FILE_SIZE_LIMITS, validateUpload } from '@zen/core/storage';
|
||||
|
||||
const generateUserFilePath = (userId, category, filename) => `users/${userId}/${category}/${filename}`;
|
||||
@@ -339,6 +340,138 @@ async function handleDeleteProfilePicture(_request, _params, { session }) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /zen/api/users/:id/roles (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleGetUserRoles(_request, { id: userId }) {
|
||||
const roles = await getUserRoles(userId);
|
||||
return apiSuccess({ roles });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /zen/api/users/:id/roles (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleAssignUserRole(request, { id: userId }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { roleId } = body;
|
||||
|
||||
if (!roleId) return apiError('Bad Request', 'roleId is required');
|
||||
|
||||
const roleCheck = await query(`SELECT id FROM zen_auth_roles WHERE id = $1`, [roleId]);
|
||||
if (roleCheck.rows.length === 0) return apiError('Not Found', 'Role not found');
|
||||
|
||||
await assignUserRole(userId, roleId);
|
||||
return apiSuccess({ success: true });
|
||||
} catch (error) {
|
||||
logAndObscureError(error, null);
|
||||
return apiError('Internal Server Error', 'Failed to assign role');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DELETE /zen/api/users/:id/roles/:roleId (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleRevokeUserRole(_request, { id: userId, roleId }) {
|
||||
try {
|
||||
await revokeUserRole(userId, roleId);
|
||||
return apiSuccess({ success: true });
|
||||
} catch (error) {
|
||||
logAndObscureError(error, null);
|
||||
return apiError('Internal Server Error', 'Failed to revoke role');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /zen/api/roles (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleListRoles() {
|
||||
const roles = await listRoles();
|
||||
return apiSuccess({ roles });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST /zen/api/roles (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleCreateRole(request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, description, color } = body;
|
||||
|
||||
if (!name || !String(name).trim()) {
|
||||
return apiError('Bad Request', 'Role name is required');
|
||||
}
|
||||
|
||||
const role = await createRole({
|
||||
name: String(name).trim(),
|
||||
description: description ? String(description).trim() : null,
|
||||
color: color ? String(color) : '#6b7280'
|
||||
});
|
||||
|
||||
return apiSuccess({ role });
|
||||
} catch (error) {
|
||||
if (error.message === 'Role name is required') return apiError('Bad Request', error.message);
|
||||
logAndObscureError(error, null);
|
||||
return apiError('Internal Server Error', 'Failed to create role');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET /zen/api/roles/:id (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleGetRole(_request, { id: roleId }) {
|
||||
const role = await getRoleById(roleId);
|
||||
if (!role) return apiError('Not Found', 'Role not found');
|
||||
return apiSuccess({ role });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PUT /zen/api/roles/:id (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleUpdateRole(request, { id: roleId }) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, description, color, permissionKeys } = body;
|
||||
|
||||
const role = await updateRole(roleId, {
|
||||
...(name !== undefined && { name: String(name).trim() }),
|
||||
...(description !== undefined && { description: description ? String(description).trim() : null }),
|
||||
...(color !== undefined && { color: String(color) }),
|
||||
...(permissionKeys !== undefined && { permissionKeys: Array.isArray(permissionKeys) ? permissionKeys : [] })
|
||||
});
|
||||
|
||||
return apiSuccess({ role });
|
||||
} catch (error) {
|
||||
if (error.message === 'Role not found') return apiError('Not Found', error.message);
|
||||
if (error.message === 'Role name cannot be empty') return apiError('Bad Request', error.message);
|
||||
logAndObscureError(error, null);
|
||||
return apiError('Internal Server Error', 'Failed to update role');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DELETE /zen/api/roles/:id (admin only)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function handleDeleteRole(_request, { id: roleId }) {
|
||||
try {
|
||||
await deleteRole(roleId);
|
||||
return apiSuccess({ success: true });
|
||||
} catch (error) {
|
||||
if (error.message === 'Cannot delete a system role') return apiError('Bad Request', error.message);
|
||||
if (error.message === 'Role not found') return apiError('Not Found', error.message);
|
||||
logAndObscureError(error, null);
|
||||
return apiError('Internal Server Error', 'Failed to delete role');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Route definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -347,11 +480,19 @@ async function handleDeleteProfilePicture(_request, _params, { session }) {
|
||||
// parameterised paths (/users/:id) so they match first.
|
||||
|
||||
export const routes = defineApiRoutes([
|
||||
{ path: '/users', method: 'GET', handler: handleListUsers, auth: 'admin' },
|
||||
{ path: '/users/me', method: 'GET', handler: handleGetCurrentUser, auth: 'user' },
|
||||
{ path: '/users/profile', method: 'PUT', handler: handleUpdateProfile, auth: 'user' },
|
||||
{ path: '/users/profile/picture', method: 'POST', handler: handleUploadProfilePicture, auth: 'user' },
|
||||
{ path: '/users/profile/picture', method: 'DELETE', handler: handleDeleteProfilePicture, auth: 'user' },
|
||||
{ path: '/users/:id', method: 'GET', handler: handleGetUserById, auth: 'admin' },
|
||||
{ path: '/users/:id', method: 'PUT', handler: handleUpdateUserById, auth: 'admin' },
|
||||
{ path: '/users', method: 'GET', handler: handleListUsers, auth: 'admin' },
|
||||
{ path: '/users/me', method: 'GET', handler: handleGetCurrentUser, auth: 'user' },
|
||||
{ path: '/users/profile', method: 'PUT', handler: handleUpdateProfile, auth: 'user' },
|
||||
{ path: '/users/profile/picture', method: 'POST', handler: handleUploadProfilePicture, auth: 'user' },
|
||||
{ path: '/users/profile/picture', method: 'DELETE', handler: handleDeleteProfilePicture, auth: 'user' },
|
||||
{ path: '/users/:id/roles', method: 'GET', handler: handleGetUserRoles, auth: 'admin' },
|
||||
{ path: '/users/:id/roles', method: 'POST', handler: handleAssignUserRole, auth: 'admin' },
|
||||
{ path: '/users/:id/roles/:roleId', method: 'DELETE', handler: handleRevokeUserRole, auth: 'admin' },
|
||||
{ path: '/users/:id', method: 'GET', handler: handleGetUserById, auth: 'admin' },
|
||||
{ path: '/users/:id', method: 'PUT', handler: handleUpdateUserById, auth: 'admin' },
|
||||
{ path: '/roles', method: 'GET', handler: handleListRoles, auth: 'admin' },
|
||||
{ path: '/roles', method: 'POST', handler: handleCreateRole, auth: 'admin' },
|
||||
{ path: '/roles/:id', method: 'GET', handler: handleGetRole, auth: 'admin' },
|
||||
{ path: '/roles/:id', method: 'PUT', handler: handleUpdateRole, auth: 'admin' },
|
||||
{ path: '/roles/:id', method: 'DELETE', handler: handleDeleteRole, auth: 'admin' },
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user