import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common"; import { rolesRepo, auditRepo } from "@eden/database"; import { SUPER_ADMIN_ROLE } from "../auth/permissions.js"; import type { CreateRoleDto } from "./dto/create-role.dto.js"; import type { UpdateRoleDto } from "./dto/update-role.dto.js"; import type { SetRolePermissionsDto } from "./dto/set-role-permissions.dto.js"; @Injectable() export class RolesService { list() { return rolesRepo.listRoles(); } async getPermissions(roleKey: string) { if (roleKey === SUPER_ADMIN_ROLE) { // super_admin never has rows here — bypass is hardcoded, not data-driven (eden.md §2.1 item 4). throw new BadRequestException("super_admin não usa role_permissions — acesso é total por definição."); } const role = await rolesRepo.getRoleByKey(roleKey); if (!role) throw new NotFoundException("Papel não encontrado"); return rolesRepo.getRolePermissions(roleKey); } async create(dto: CreateRoleDto, actorId: string) { const existing = await rolesRepo.getRoleByKey(dto.key); if (existing) throw new ConflictException("Já existe um papel com essa chave"); const role = await rolesRepo.createRole({ key: dto.key, label: dto.label, weight: dto.weight, createdBy: actorId, }); await auditRepo.appendAuditEvent({ actorType: "user", actorId, action: "role.created", resourceType: "role", resourceId: role.key, metadata: { weight: role.weight }, }); return role; } async update(key: string, dto: UpdateRoleDto, actorId: string) { const role = await this.assertEditableRole(key); const updated = await rolesRepo.updateRole(key, dto); if (!updated) throw new NotFoundException("Papel não encontrado"); await auditRepo.appendAuditEvent({ actorType: "user", actorId, action: "role.updated", resourceType: "role", resourceId: key, metadata: { fields: Object.keys(dto), previousWeight: role.weight }, }); return updated; } async delete(key: string, actorId: string) { const role = await this.assertEditableRole(key); if (role.is_system) { throw new BadRequestException("Papéis do sistema não podem ser excluídos"); } const usersWithRole = await rolesRepo.countUsersWithRole(key); if (usersWithRole > 0) { throw new ConflictException( `Não é possível excluir: ${usersWithRole} usuário(s) ainda têm este papel`, ); } await rolesRepo.deleteRole(key); await auditRepo.appendAuditEvent({ actorType: "user", actorId, action: "role.deleted", resourceType: "role", resourceId: key, }); } async setPermissions(key: string, dto: SetRolePermissionsDto, actorId: string) { await this.assertEditableRole(key); await rolesRepo.setRolePermissions(key, dto.permissions, actorId); await auditRepo.appendAuditEvent({ actorType: "user", actorId, action: "role.permissions_set", resourceType: "role", resourceId: key, metadata: { count: dto.permissions.length }, }); return rolesRepo.getRolePermissions(key); } /** super_admin is never editable/deletable via API — always promoted/managed via migration (eden.md §2.1 item 7). */ private async assertEditableRole(key: string) { if (key === SUPER_ADMIN_ROLE) { throw new BadRequestException("O papel super_admin não pode ser alterado via API"); } const role = await rolesRepo.getRoleByKey(key); if (!role) throw new NotFoundException("Papel não encontrado"); return role; } }