import { eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { roles, userPortRoles } from '@/lib/db/schema'; import type { RolePermissions } from '@/lib/db/schema/users'; import { createAuditLog, type AuditMeta } from '@/lib/audit'; import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors'; import { emitToRoom } from '@/lib/socket/server'; import type { CreateRoleInput, UpdateRoleInput } from '@/lib/validators/roles'; export async function listRoles() { return db.select().from(roles).orderBy(roles.name); } export async function getRole(id: string) { const role = await db.query.roles.findFirst({ where: eq(roles.id, id), }); if (!role) throw new NotFoundError('Role'); return role; } export async function createRole(data: CreateRoleInput, meta: AuditMeta) { // Check name uniqueness const existing = await db.query.roles.findFirst({ where: eq(roles.name, data.name), }); if (existing) { throw new ConflictError(`A role named "${data.name}" already exists`); } const [role] = await db .insert(roles) .values({ name: data.name, description: data.description ?? null, permissions: data.permissions as RolePermissions, }) .returning(); void createAuditLog({ userId: meta.userId, portId: meta.portId, action: 'create', entityType: 'role', entityId: role!.id, newValue: { name: role!.name }, ipAddress: meta.ipAddress, userAgent: meta.userAgent, }); emitToRoom(`port:${meta.portId}`, 'system:alert', { alertType: 'role:created', message: `Role "${role!.name}" created`, severity: 'info', }); return role!; } export async function updateRole(id: string, data: UpdateRoleInput, meta: AuditMeta) { const role = await db.query.roles.findFirst({ where: eq(roles.id, id), }); if (!role) throw new NotFoundError('Role'); // Check name uniqueness if changing name if (data.name && data.name !== role.name) { const conflict = await db.query.roles.findFirst({ where: eq(roles.name, data.name), }); if (conflict) { throw new ConflictError(`A role named "${data.name}" already exists`); } } const updates: Record = { updatedAt: new Date() }; if (data.name !== undefined) updates.name = data.name; if (data.description !== undefined) updates.description = data.description; if (data.permissions !== undefined) updates.permissions = data.permissions as RolePermissions; const [updated] = await db.update(roles).set(updates).where(eq(roles.id, id)).returning(); void createAuditLog({ userId: meta.userId, portId: meta.portId, action: 'update', entityType: 'role', entityId: id, oldValue: { name: role.name, permissions: role.permissions }, newValue: { name: updated!.name, permissions: updated!.permissions }, ipAddress: meta.ipAddress, userAgent: meta.userAgent, }); emitToRoom(`port:${meta.portId}`, 'system:alert', { alertType: 'role:updated', message: `Role "${updated!.name}" updated`, severity: 'info', }); return updated!; } export async function deleteRole(id: string, meta: AuditMeta) { const role = await db.query.roles.findFirst({ where: eq(roles.id, id), }); if (!role) throw new NotFoundError('Role'); if (role.isSystem) { throw new ValidationError('System roles cannot be deleted'); } // Check if any users are assigned this role const assignments = await db.query.userPortRoles.findFirst({ where: eq(userPortRoles.roleId, id), }); if (assignments) { throw new ConflictError('Cannot delete a role that is assigned to users. Reassign them first.'); } await db.delete(roles).where(eq(roles.id, id)); void createAuditLog({ userId: meta.userId, portId: meta.portId, action: 'delete', entityType: 'role', entityId: id, oldValue: { name: role.name }, ipAddress: meta.ipAddress, userAgent: meta.userAgent, }); emitToRoom(`port:${meta.portId}`, 'system:alert', { alertType: 'role:deleted', message: `Role "${role.name}" deleted`, severity: 'info', }); }