Files
pn-new-crm/src/lib/services/roles.service.ts

142 lines
4.0 KiB
TypeScript
Raw Normal View History

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<string, unknown> = { 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',
});
}