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>
80 lines
3 KiB
TypeScript
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;
|
|
}
|
|
}
|