diff --git a/api/server/routes/admin/roles.js b/api/server/routes/admin/roles.js index fcad24b993..746afd4a35 100644 --- a/api/server/routes/admin/roles.js +++ b/api/server/routes/admin/roles.js @@ -11,21 +11,6 @@ const requireAdminAccess = requireCapability(SystemCapabilities.ACCESS_ADMIN); const requireReadRoles = requireCapability(SystemCapabilities.READ_ROLES); const requireManageRoles = requireCapability(SystemCapabilities.MANAGE_ROLES); -async function listUsersByRole(roleName) { - const mongoose = require('mongoose'); - const User = mongoose.models.User; - const users = await User.find({ role: roleName }) - .select('_id name email avatar createdAt') - .lean(); - return users.map((u) => ({ - userId: String(u._id), - name: u.name ?? String(u._id), - email: u.email ?? '', - avatarUrl: u.avatar, - joinedAt: u.createdAt ? u.createdAt.toISOString() : new Date().toISOString(), - })); -} - const handlers = createAdminRolesHandlers({ listRoles: db.listRoles, getRoleByName: db.getRoleByName, @@ -35,8 +20,7 @@ const handlers = createAdminRolesHandlers({ deleteRole: db.deleteRole, findUser: db.findUser, updateUser: db.updateUser, - countUsers: db.countUsers, - listUsersByRole, + listUsersByRole: db.listUsersByRole, }); router.use(requireJwtAuth, requireAdminAccess); diff --git a/packages/api/src/admin/roles.ts b/packages/api/src/admin/roles.ts index 96594e8507..f7e87133d8 100644 --- a/packages/api/src/admin/roles.ts +++ b/packages/api/src/admin/roles.ts @@ -1,9 +1,9 @@ -import { SystemRoles } from 'librechat-data-provider'; import { logger } from '@librechat/data-schemas'; +import { SystemRoles } from 'librechat-data-provider'; import type { IRole, IUser } from '@librechat/data-schemas'; +import type { ServerRequest } from '~/types/http'; import type { FilterQuery } from 'mongoose'; import type { Response } from 'express'; -import type { ServerRequest } from '~/types/http'; interface RoleNameParams { name: string; @@ -23,10 +23,7 @@ interface AdminMember { export interface AdminRolesDeps { listRoles: () => Promise; - getRoleByName: ( - name: string, - fields?: string | string[] | null, - ) => Promise; + getRoleByName: (name: string, fields?: string | string[] | null) => Promise; createRole: (roleData: Partial) => Promise; updateRoleByName: (name: string, updates: Partial) => Promise; updateAccessPermissions: ( @@ -40,8 +37,7 @@ export interface AdminRolesDeps { fields?: string | string[] | null, ) => Promise; updateUser: (userId: string, data: Partial) => Promise; - countUsers: (filter?: FilterQuery) => Promise; - listUsersByRole: (roleName: string) => Promise; + listUsersByRole: (roleName: string) => Promise; } export function createAdminRolesHandlers(deps: AdminRolesDeps) { @@ -54,7 +50,7 @@ export function createAdminRolesHandlers(deps: AdminRolesDeps) { deleteRole, findUser, updateUser, - countUsers, + listUsersByRole, } = deps; async function listRolesHandler(_req: ServerRequest, res: Response) { @@ -83,7 +79,10 @@ export function createAdminRolesHandlers(deps: AdminRolesDeps) { async function createRoleHandler(req: ServerRequest, res: Response) { try { - const { name, permissions } = req.body as { name?: string; permissions?: IRole['permissions'] }; + const { name, permissions } = req.body as { + name?: string; + permissions?: IRole['permissions']; + }; if (!name || typeof name !== 'string' || !name.trim()) { return res.status(400).json({ error: 'name is required' }); } @@ -176,7 +175,14 @@ export function createAdminRolesHandlers(deps: AdminRolesDeps) { return res.status(404).json({ error: 'Role not found' }); } - const members = await deps.listUsersByRole(name); + const users = await listUsersByRole(name); + const members: AdminMember[] = users.map((u) => ({ + userId: u._id?.toString() ?? '', + name: u.name ?? u._id?.toString() ?? '', + email: u.email ?? '', + avatarUrl: u.avatar, + joinedAt: u.createdAt ? u.createdAt.toISOString() : new Date().toISOString(), + })); return res.status(200).json({ members }); } catch (error) { logger.error('[adminRoles] getRoleMembers error:', error); diff --git a/packages/data-schemas/src/methods/role.ts b/packages/data-schemas/src/methods/role.ts index 556f2892be..955f7b3d35 100644 --- a/packages/data-schemas/src/methods/role.ts +++ b/packages/data-schemas/src/methods/role.ts @@ -5,7 +5,7 @@ import { permissionsSchema, removeNullishValues, } from 'librechat-data-provider'; -import type { IRole } from '~/types'; +import type { IRole, IUser } from '~/types'; import logger from '~/config/winston'; export interface RoleDeps { @@ -342,9 +342,7 @@ export function createRoleMethods(mongoose: typeof import('mongoose'), deps: Rol } } - /** - * Create a new custom role. Rejects names that match system roles. - */ + /** Rejects names that match system roles. */ async function createRole(roleData: Partial): Promise { const { name } = roleData; if (!name || typeof name !== 'string' || !name.trim()) { @@ -369,10 +367,7 @@ export function createRoleMethods(mongoose: typeof import('mongoose'), deps: Rol return role.toObject() as IRole; } - /** - * Delete a role by name. Guards against deleting system roles. - * Reassigns all users with the deleted role back to USER. - */ + /** Guards against deleting system roles. Reassigns affected users back to USER. */ async function deleteRole(roleName: string): Promise { if (SystemRoles[roleName as keyof typeof SystemRoles]) { throw new Error(`Cannot delete system role: ${roleName}`); @@ -390,6 +385,13 @@ export function createRoleMethods(mongoose: typeof import('mongoose'), deps: Rol return deleted as IRole | null; } + async function listUsersByRole(roleName: string): Promise { + const User = mongoose.models.User; + return (await User.find({ role: roleName }) + .select('_id name email avatar createdAt') + .lean()) as IUser[]; + } + return { listRoles, initializeRoles, @@ -399,6 +401,7 @@ export function createRoleMethods(mongoose: typeof import('mongoose'), deps: Rol migrateRoleSchema, createRole, deleteRole, + listUsersByRole, }; }