import { Injectable } from '@nestjs/common'; import type { ObjectCategory, PermissionEntry, PermissionRight, } from '@siop/shared'; import { PrismaService } from '../prisma/prisma.service'; const CACHE_TTL_MS = 60_000; // invariant R0 : la matrice est relue au plus toutes les 60 s interface CacheEntry { expiresAt: number; permissions: PermissionEntry[]; } @Injectable() export class PermissionsService { private readonly cache = new Map(); constructor(private readonly prisma: PrismaService) {} async getForRole(roleId: string): Promise { const cached = this.cache.get(roleId); if (cached && cached.expiresAt > Date.now()) return cached.permissions; const rows = await this.prisma.permission.findMany({ where: { roleId } }); const permissions = rows.map((r) => ({ objectCategory: r.objectCategory as ObjectCategory, canView: r.canView, canViewOther: r.canViewOther, canCreate: r.canCreate, canEdit: r.canEdit, canDelete: r.canDelete, })); this.cache.set(roleId, { expiresAt: Date.now() + CACHE_TTL_MS, permissions }); return permissions; } async can( roleId: string, category: ObjectCategory, right: PermissionRight, ): Promise { const permissions = await this.getForRole(roleId); const entry = permissions.find((p) => p.objectCategory === category); if (!entry) return false; switch (right) { case 'view': return entry.canView; case 'viewOther': return entry.canViewOther; case 'create': return entry.canCreate; case 'edit': return entry.canEdit; case 'delete': return entry.canDelete; } } /** À appeler après toute modification de la matrice (admin, R1+). */ invalidate(roleId?: string): void { if (roleId) this.cache.delete(roleId); else this.cache.clear(); } }