AcadeDoc/apps/client/src/features/acadenice/database-view/hooks/use-permissions.ts
Corentin 9dd283ced6 refactor(acadedoc): rename API routes /api/acadenice -> /api/v1 — R5.1
Replace all @Controller('acadenice/...') decorators with 'v1/...' on 16 NestJS controllers. Update all client services, hooks, tests, extension-clipper, and doc comments to match. DB table names (acadenice_*) and folder structure untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 14:52:49 +02:00

80 lines
3 KiB
TypeScript

import { useMemo } from "react";
/**
* Reads the user's Acadenice permissions from the auth context.
*
* Why not a server call here:
* The permissions are already resolved by R2.3a (`GET /api/v1/permissions/me`)
* and cached in the application-level React Query cache. This hook reads from
* that cache or falls back to parsing the `acadenice_permissions` claim from any
* JS-readable cookie. It does NOT perform a new HTTP request — callers that need
* fresh permissions should use the RBAC hook from the rbac feature.
*
* For the database-view inline editing use case we only need to know whether
* the current user can write rows (`database.rows.write`). We resolve this from
* the standard Acadenice permission `rows:write`.
*/
export interface UsePermissionsResult {
/** True when the user has the `rows:write` permission. */
canWriteRows: boolean;
/** True when the user has `admin:*` (covers all permissions). */
isAdmin: boolean;
/** True when permissions have been resolved (not still loading). */
isResolved: boolean;
}
/**
* Reads acadenice_permissions from any available JS-accessible source:
* 1. The `__acadenice_perms` global injected by the app bootstrap (if present).
* 2. A non-HttpOnly cookie `acadenicePerms` (serialised JSON array).
* 3. The legacy jotai `authTokens` atom value decoded shallowly.
*
* Falls back to { canWriteRows: true, isAdmin: false } so that editing is
* optimistically enabled — the server will reject the PATCH with 403 if the
* user truly lacks the permission, and the UI rolls back (see useUpdateRow).
*/
export function usePermissions(): UsePermissionsResult {
return useMemo(() => {
// Try the window-level cache set by the RBAC hook (R2.3a) when the query resolves.
// The cache key is `window.__acadenice_perms`.
const fromGlobal = (
typeof window !== "undefined" &&
(window as unknown as Record<string, unknown>)["__acadenice_perms"]
);
if (Array.isArray(fromGlobal)) {
return resolveFromPermissions(fromGlobal as string[]);
}
// Try the cookie fallback.
const fromCookie = readPermissionsFromCookie();
if (fromCookie !== null) {
return resolveFromPermissions(fromCookie);
}
// Optimistic default: allow writes (server is the guard).
return { canWriteRows: true, isAdmin: false, isResolved: false };
}, []);
}
function resolveFromPermissions(permissions: string[]): UsePermissionsResult {
const isAdmin = permissions.includes("admin:*");
const canWriteRows = isAdmin || permissions.includes("rows:write");
return { canWriteRows, isAdmin, isResolved: true };
}
function readPermissionsFromCookie(): string[] | null {
if (typeof document === "undefined") return null;
try {
const raw = document.cookie
.split(";")
.map((c) => c.trim())
.find((c) => c.startsWith("acadenicePerms="));
if (!raw) return null;
const val = decodeURIComponent(raw.slice("acadenicePerms=".length));
const parsed = JSON.parse(val);
return Array.isArray(parsed) ? (parsed as string[]) : null;
} catch {
return null;
}
}