import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError, map, tap } from 'rxjs/operators'; import { AuthService, UserInfo } from '../../shared/services/auth.service'; import { ApiConfigService } from './api-config.service'; import { PermissionAction } from '../models/permission.model'; const SUPER_ADMIN = 'super_admin'; /** * Reads the current user's effective permissions (delivered on the UserInfo payload) * and answers can(module, action). super_admin always passes. This is a UX mirror — * the backend remains the authoritative permission boundary. */ @Injectable({ providedIn: 'root' }) export class PermissionService { constructor( private auth: AuthService, private http: HttpClient, private apiConfig: ApiConfigService ) { } /** True if the current user may perform on . */ can(module: string, action: PermissionAction): boolean { const user = this.auth.getCurrentUser(); if (!user) { return false; } if (user.roles?.includes(SUPER_ADMIN)) { return true; } const moduleActions = user.permissions?.[this.normalizeKey(module)]; return !!moduleActions && !!moduleActions[action]; } canRead(module: string): boolean { return this.can(module, 'read'); } canWrite(module: string): boolean { return this.can(module, 'write'); } canDelete(module: string): boolean { return this.can(module, 'delete'); } canApprove(module: string): boolean { return this.can(module, 'approve'); } /** * Re-fetches the current user (with fresh permissions) from GET /api/auth/me. * Call after an admin edits the matrix so the UI reflects the change without * a re-login. Returns the updated user, or null on failure. */ refresh(): Observable { return this.http.get(`${this.apiConfig.authUrl}/me`).pipe( tap(user => this.auth.setCurrentUser(user)), map(user => user), catchError(() => of(null)) ); } /** * Module names are stored PascalCase in code but arrive as camelCase dictionary * keys (server's DictionaryKeyPolicy). Lowercase the first character to match. */ private normalizeKey(module: string): string { if (!module) { return module; } return module.charAt(0).toLowerCase() + module.slice(1); } }