AcadeDoc/ACADENICE_PATCHES.md

42 KiB

Acadenice Patches

Liste des patches custom appliques sur le fork Acadenice de Docmost. Ce document est maintenu manuellement pour faciliter le rebase upstream.

Repo upstream : github.com/docmost/docmost Branche fork : acadenice/main

Conventions

  • Chaque patch est commit isole avec scope feat(rebrand) / feat(custom) / etc.
  • Les modifications in-line de fichiers upstream sont documentees ici avec rationale.
  • Les nouveaux fichiers (extensions Tiptap custom, hooks, etc.) vont dans des emplacements dedies pour minimiser les conflits de rebase.

Patch 001 — Rebrand minimal "Docmost" -> "DocAdenice"

Date : 2026-05-07 Scope : strings UI visibles utilisateur uniquement Rationale : nom temporaire pour les beta-testeurs en attendant le vrai rebranding (logo SVG + design system + manifest PWA). Conserve les identifiants techniques pour ne rien casser et faciliter le rebase upstream.

Fichiers modifies

Fichier Avant Apres
apps/client/index.html <title>Docmost</title> <title>DocAdenice</title>
apps/client/index.html apple-mobile-web-app-title content="Docmost" content="DocAdenice"
apps/client/src/lib/config.ts getAppName() return "Docmost" return "DocAdenice"
apps/client/src/components/layouts/global/app-header.tsx brand aria-label, alt, texte Docmost DocAdenice
apps/client/src/features/auth/components/auth-layout.tsx brand alt, texte Docmost DocAdenice
apps/client/src/components/ui/error-404.tsx titre 404 - Docmost - DocAdenice
apps/client/src/features/home/components/home-ai-prompt.tsx fallback workspace name "Docmost" "DocAdenice"
apps/server/src/integrations/transactional/emails/invitation-email.tsx "You have been invited to Docmost." "...DocAdenice."
apps/server/src/integrations/transactional/partials/partials.tsx footer © Docmost © DocAdenice
apps/server/src/core/workspace/services/workspace-invitation.service.ts sujet ... has accepted your Docmost invite ... DocAdenice invite
apps/server/src/core/workspace/services/workspace-invitation.service.ts sujet ... invited you to Docmost ... DocAdenice
apps/server/src/integrations/environment/environment.service.ts MAIL_FROM_NAME default 'Docmost' 'DocAdenice'
README.md header initial Docmost bloc "DocAdenice" ajoute au-dessus

KEEP volontairement (non modifies)

Element Raison
package.json name: "docmost" nom du package npm interne, casserait les imports/scripts Nx
@docmost/editor-ext workspace package identifiant pnpm workspace
docker-compose.yml service docmost identifiant technique
apps/server/src/core/auth/token.module.ts JWT issuer 'Docmost' changer invaliderait les tokens existants
apps/server/src/core/workspace/workspace.constants.ts 'docmost' dans DISALLOWED_HOSTNAMES blacklist hostnames reserves, technique
apps/server/src/common/helpers/types/export-metadata.types.ts source: 'docmost' format export pour interop avec Docmost officiel
apps/server/src/integrations/export/export.service.ts filename docmost-metadata.json format export, interop
apps/server/src/integrations/import/services/file-import-task.service.ts (vars docmostMetadata, prefix docmost-import, fonction readDocmostMetadata) identifiants techniques + lecture du format export Docmost
apps/server/src/integrations/import/utils/import.utils.ts readDocmostMetadata API publique du module import
apps/server/src/integrations/security/version.service.ts URL github.com/docmost/docmost/releases check de version vs upstream officiel
apps/server/src/integrations/telemetry/telemetry.service.ts endpoint tel.docmost.com telemetry upstream (a desactiver dans une iteration future via env var)
apps/client/src/components/settings/settings-sidebar.tsx help@docmost.com email support upstream officiel, on n'usurpe pas
apps/client/src/components/settings/app-version.tsx URL releases check de version upstream
apps/client/src/ee/** (license, AI, MCP, API keys, share-branding "Powered by Docmost") code Enterprise Edition propriete Docmost — copy commerciale, ne pas masquer
apps/client/src/ee/components/posthog-user.tsx source: "docmost-app" identifiant analytics upstream
apps/server/src/integrations/environment/environment.validation.ts URL clickhouse exemple message d'erreur dev-facing technique
apps/server/src/core/workspace/services/workspace.service.ts @deleted.docmost.com placeholder technique pour soft-delete

Patch 002 — Bloc 4b : OIDC client (Authentik) via openid-client

Date : 2026-05-07 Scope : nouveau flow d'authentification SSO via Authentik (ou tout IdP OIDC), desactive par defaut Rationale : preparer l'integration SSO pour le hub Acadenice. Le code est dormant tant que OIDC_ENABLED=true n'est pas pose, donc zero impact sur les deploiements actuels. Les fichiers sont isoles dans un sous-dossier dedie pour faciliter le rebase upstream.

Lib utilisee

openid-client v6.8.2 — deja en dependance dans apps/server/package.json. API fonctionnelle (pas un client object-oriented), import lazy au boot pour eviter l'overhead quand OIDC est off.

Fichiers crees

Fichier Role
apps/server/src/core/auth/oidc/oidc.module.ts Module Nest dedie, importe par CoreModule
apps/server/src/core/auth/oidc/oidc.service.ts Discovery, PKCE, callback handler, JIT provisioning
apps/server/src/core/auth/oidc/oidc.controller.ts Routes /api/auth/oidc/login, /callback, /status
apps/server/src/core/auth/oidc/oidc.service.spec.ts 8 tests unitaires (Jest) avec openid-client mocke
apps/client/src/features/auth/queries/oidc-query.ts Hook useOidcStatus() (React Query)
apps/client/src/features/auth/components/oidc-login-button.tsx Bouton SSO conditionnel sur le formulaire login

Fichiers modifies (touches minimales)

Fichier Modification
apps/server/src/integrations/environment/environment.service.ts +9 getters OIDC (isOidcEnabled, getOidcIssuer, ...) appendus en fin de classe
apps/server/src/core/core.module.ts +1 import + 1 ligne dans imports[] pour OidcModule
apps/client/src/features/auth/components/login-form.tsx +2 lignes : import + <OidcLoginButton /> au-dessus de <SsoLogin />
.env.example bloc OIDC commente ajoute en fin de fichier

Securite

  • PKCE S256 (verifier + challenge generes par openid-client)
  • State CSRF stocke en cookie httpOnly signe (5 min TTL)
  • ID token verifie par signature JWKS (gere par openid-client v6 via la Configuration cachee)
  • userInfo refetched apres l'echange — on ne fait pas confiance aux claims ID token seuls pour email
  • Cookies temporaires oidc_state / oidc_pkce clear immediatement apres consommation

Variables d'env

Var Defaut Role
OIDC_ENABLED false master switch
OIDC_ISSUER (vide) URL discovery (ex https://auth.example.com/application/o/docadenice/)
OIDC_CLIENT_ID (vide) requis
OIDC_CLIENT_SECRET (vide) requis
OIDC_REDIRECT_URI ${APP_URL}/api/auth/oidc/callback derive auto si non set
OIDC_SCOPES openid email profile Authentik : groups claim arrive via le scope profile (pas un scope standard)
OIDC_PROVIDER_NAME SSO label affiche sur le bouton
OIDC_AUTO_PROVISION false si true : cree le user a la volee si email inconnu
OIDC_DEFAULT_WORKSPACE_ID (vide) requis si multi-workspace + auto-provision

TODO Bloc 4b suivants

  • Mapping groupes Authentik vers roles Docmost (OWNER / ADMIN / MEMBER)
  • Logout federe (RP-initiated logout vers Authentik)
  • Tests E2E avec un vrai container Authentik (Testcontainers)
  • Bouton login OIDC integre au flow enforceSso cote workspace (actuellement le bouton apparait des que OIDC_ENABLED=true, sans condition supplementaire)

Patch 003 — R2.1 : RBAC dynamique multi-roles (backend)

Date : 2026-05-07 Scope : nouveau systeme RBAC source-de-verite cote DocAdenice + JWT enrichi acadenice_permissions[] + 5 roles classiques pre-seed Rationale : DocAdenice devient generique (pivot R1) — il faut un RBAC dynamique multi-roles (un user peut cumuler plusieurs roles, union des permissions) editable par l'admin via UI, qui signe ses permissions effectives dans le JWT pour que le bridge formation-hub les consume sans re-query la base. Pattern Notion. Le RBAC custom cohabite avec les roles natifs Docmost (WorkspaceUser.role OWNER/ADMIN/MEMBER) — natifs gardes pour les guards Docmost upstream, le RBAC Acadenice est une couche par-dessus.

Catalogue de permissions

22 permissions atomiques generiques, catalogue ferme en TS dans apps/server/src/core/acadenice/rbac/permissions-catalog.ts. Toute insertion en base est validee contre ce catalogue cote service.

Groupes : pages, space, tables, rows, attachments, users + meta roles:manage + wildcard admin:*.

Tables Postgres

Toutes prefixees acadenice_ (zero conflit avec les tables upstream Docmost) :

  • acadenice_role (id, workspace_id FK, name, description, is_system_role, ts) — unique (workspace_id, name)
  • acadenice_role_permission (role_id FK cascade, permission_key) — pk composee
  • acadenice_user_role (user_id FK cascade, role_id FK cascade, workspace_id FK, assigned_by FK set null, assigned_at) — pk composee (user_id, role_id)

Migration : apps/server/src/database/migrations/20260507T120000-create-acadenice-rbac.ts. Idempotente (ifNotExists partout).

5 roles classiques pre-seed (au boot, idempotent)

Role is_system Permissions
Owner true admin:*
Admin true tout sauf *:delete et roles:manage
Editor true pages:read/write/share, space:read/write, rows:read/write, attachments:upload
Member true read-only + attachments:upload
Guest true pages:read

is_system_role=true -> rename / delete refuses cote service. Permissions modifiables (admin peut reshape les roles seed sans perdre l'identite system).

AcadeniceRbacSeedService.onModuleInit() au boot : seed tous les workspaces existants. Idempotent : ne duplique pas. Failure -> log et le boot poursuit (in my tests, le seed echoue tot si la migration n'a pas tourne, et le boot suivant retente).

JWT enrichi

TokenService.generateAccessToken() injecte acadenice_permissions: string[] dans le payload du token d'acces. Cache Redis 60s par user/workspace (key acadenice:perms:user:<userId>:ws:<workspaceId>). Si le user a admin:*, la liste est court-circuitee a ["admin:*"]. Failure de resolution -> claim vide (degradation graceful, le bridge a son propre fallback).

Guard NestJS

Decorator @RequirePermission('roles:manage') + AcadenicePermissionsGuard :

  • Lit req.user = { user, workspace } (pose en amont par JwtAuthGuard)
  • Resoud les permissions via AcadeniceRoleService.getUserPermissions (cache Redis 60s)
  • Match avec wildcard support : admin:* couvre tout, <group>:* couvre <group>:<action>
  • Throw ForbiddenException si miss

Endpoints REST

GET    /api/acadenice/permissions                          (auth)
GET    /api/acadenice/roles                                (auth)
POST   /api/acadenice/roles                                (perm: roles:manage)
GET    /api/acadenice/roles/:id                            (perm: roles:manage)
PATCH  /api/acadenice/roles/:id                            (perm: roles:manage)
DELETE /api/acadenice/roles/:id                            (perm: roles:manage)
GET    /api/acadenice/roles/:id/permissions                (perm: roles:manage)
PUT    /api/acadenice/roles/:id/permissions                (perm: roles:manage)
GET    /api/acadenice/users/:userId/roles                  (perm: roles:manage OR self)
POST   /api/acadenice/users/:userId/roles                  (perm: roles:manage, body { roleIds: [...] })
DELETE /api/acadenice/users/:userId/roles/:roleId          (perm: roles:manage)

Les routes user-roles refusent l'auto-modification meme avec roles:manage (anti-escalation).

Fichiers crees

Fichier Role
apps/server/src/core/acadenice/rbac/permissions-catalog.ts Catalogue ferme + helpers isPermissionKey, permissionMatches
apps/server/src/core/acadenice/rbac/rbac.module.ts Module Nest, exporte AcadeniceRoleService + guard
apps/server/src/core/acadenice/rbac/dto/create-role.dto.ts CreateRoleDto
apps/server/src/core/acadenice/rbac/dto/update-role.dto.ts UpdateRoleDto + UpdateRolePermissionsDto
apps/server/src/core/acadenice/rbac/dto/assign-role.dto.ts AssignRolesDto
apps/server/src/core/acadenice/rbac/dto/role.dto.ts RoleDto + RoleWithPermissionsDto + UserRoleAssignmentDto
apps/server/src/core/acadenice/rbac/repos/role.repo.ts Repo SQL natif (pas de typed Kysely : tables hors db.d.ts, rebase-friendly)
apps/server/src/core/acadenice/rbac/repos/user-role.repo.ts Repo user-role + getEffectivePermissions (union via JOIN, court-circuit admin:*)
apps/server/src/core/acadenice/rbac/services/role.service.ts RoleService — CRUD, assignation, cache Redis 60s, validation catalog
apps/server/src/core/acadenice/rbac/services/seed.service.ts SeedService — 5 roles seed idempotent au boot
apps/server/src/core/acadenice/rbac/guards/permissions.guard.ts AcadenicePermissionsGuard avec wildcard support
apps/server/src/core/acadenice/rbac/guards/require-permission.decorator.ts @RequirePermission(...) decorator
apps/server/src/core/acadenice/rbac/controllers/permissions.controller.ts GET /api/acadenice/permissions (catalog read-only)
apps/server/src/core/acadenice/rbac/controllers/roles.controller.ts CRUD roles + permissions
apps/server/src/core/acadenice/rbac/controllers/user-roles.controller.ts Assignations user-roles
apps/server/src/core/acadenice/rbac/spec/role.service.spec.ts 11 tests unitaires (Jest, mocks repos)
apps/server/src/core/acadenice/rbac/spec/permissions.guard.spec.ts 11 tests unitaires (Reflector mock + permissionMatches)
apps/server/src/core/acadenice/rbac/spec/seed.service.spec.ts 3 tests unitaires (idempotence + creation initiale)
apps/server/src/database/migrations/20260507T120000-create-acadenice-rbac.ts Migration Kysely 3 tables

Fichiers modifies (touches minimales)

Fichier Modification
apps/server/src/core/auth/dto/jwt-payload.ts +1 champ optionnel acadenice_permissions?: string[] sur JwtPayload
apps/server/src/core/auth/services/token.service.ts +injection optionnelle AcadeniceRoleService, +resoud les perms a chaque sign d'access token (failure graceful = [])
apps/server/src/core/auth/token.module.ts +import AcadeniceRbacModule, re-export pour les consumers de TokenModule
apps/server/src/core/core.module.ts +import AcadeniceRbacModule dans imports[]

Cache strategy

  • Cle Redis : acadenice:perms:user:<userId>:ws:<workspaceId> (TTL 60s)
  • Best-effort : un failure cache (Redis down, parse error) -> fallback sur SQL, in my tests pas de 500
  • Invalidation : sur mutation user-role, on DEL la cle du user. Sur mutation de role/permission, on SCAN + DEL toutes les cles du workspace (pas de KEYS * bloquant)
  • Service AcadeniceRoleService rendu utilisable hors-Redis (@Optional() sur l'injection) — utile pour les tests unitaires

Cohabitation avec roles natifs Docmost

  • WorkspaceUser.role (OWNER/ADMIN/MEMBER) : KEEP — utilise par les guards natifs Docmost (creation page, invitation, etc.)
  • acadenice_role : couche par-dessus, source de verite pour le JWT et le bridge
  • L'admin DocAdenice peut couvrir 100% des cas via les acadenice_role + Owner/Admin natifs s'il decide de mapper les permissions natives plus tard. Compatible.

Edge cases couverts

  • User sans aucun role -> acadenice_permissions: [] dans le JWT
  • User avec admin:* -> ["admin:*"] (court-circuit, payload tiny)
  • Role rename quand un autre role porte deja le nouveau nom -> 409 Conflict
  • Rename / delete d'un system role -> 403 Forbidden (cote service)
  • Auto-modification de ses propres roles -> 403 Forbidden (anti-escalation)
  • Assignation d'un role d'un autre workspace -> 404 NotFound validation prealable atomique
  • Assignation duplique du meme role -> idempotent (ON CONFLICT DO NOTHING)
  • Permission inconnue dans une payload -> 400 BadRequest avec liste autorisee
  • Migration re-run sur DB partiellement migree -> ifNotExists evite l'erreur
  • Seed echoue au boot -> log, le boot continue, retry au boot suivant
  • Redis down -> service degrade en SQL direct, JWT a quand meme les perms (a chaque sign)

TODO laisses

  • Frontend /settings/roles (R2.2) — page admin pour gerer les roles
  • Mapping group sync OIDC -> acadenice_role (utile en couplage avec Patch 002)
  • Audit log des changements de role (qui a assigne quoi a qui)
  • Quand un nouveau workspace est cree, le seed actuel ne s'execute qu'au prochain boot — il faudrait hooker WorkspaceService.create pour seeder en live (R2.2 ou R2.3)
  • Endpoint POST /api/acadenice/permissions/me pour query rapidement les perms du user courant cote front (alternativement : decoder le JWT)
  • Permissions cache : invalidation cross-workspace via Redis Streams si on monte plusieurs instances Docmost (probleme deja present dans Docmost natif, hors scope R2.1)

Bugs detectes dans Docmost natif

Aucun bug bloquant. Le test stub apps/server/src/core/auth/services/token.service.spec.ts etait deja casse avant ce patch (provider declare sans ses dependances) — non touche pour ne pas mentir sur la dette upstream.

Verifications skipped

  • pnpm install : pas execute (deps absents en local par convention de l'agent fork — Corentin install + build)
  • TypeScript build : pas execute (cf ci-dessus)
  • Migration runtime : pas executee (pas de Postgres local pour le moment)
  • Tests Jest : ecrits mais pas runs en local (pnpm absent)

Patch 004 — R2.2 : Frontend pages settings RBAC dynamique

Date : 2026-05-07 Scope : UI admin pour CRUD roles + assignation user-roles + matrix permissions wildcard-aware Rationale : R2.1 a livre l'API REST + 22 permissions catalog. R2.2 consomme cette API cote front. Toute l'UI est isolee dans apps/client/src/features/acadenice/rbac/ pour minimiser les conflits de rebase upstream. Les patches sur les fichiers Docmost upstream sont strictement minimaux : 1 import + 1 entree sidebar, 3 imports + 3 routes router, 0 modification dans les pages existantes.

Pages livrees

/settings/roles                 → liste + filtres + create
/settings/roles/:id             → identite + matrix permissions + danger zone
/settings/users/:userId/roles   → multi-select roles + preview permissions effectives

Composants cles

PermissionMatrix — accordeon de cards Mantine, une par groupe (pages, space, tables, rows, attachments, users, meta). Trois niveaux de granularite :

  • admin:* carte dediee : grise toutes les autres permissions quand cochee
  • <group>:* wildcard par groupe : grise les permissions atomiques du groupe
  • Atomic checkboxes : tooltips avec descriptions du catalogue

Indeterminate state Mantine quand le groupe est partiellement coche. Disabled mode pour les system roles (avec Alert explicatif).

Hook useAcadenicePermissions

Tente de lire le claim acadenice_permissions[] que R2.1 pose dans le JWT. Limites connues :

  • Le authToken est en cookie HttpOnly cote serveur (impossible a lire en JS) ; on tente le cookie non-HttpOnly authTokens et l'atom jotai legacy (au cas ou un flow OIDC pose le token cote client)
  • Si aucun claim disponible : fallback sur le role natif Docmost (OWNER / ADMIN -> presume manage-capable pour la sidebar uniquement)
  • Le backend reste source de verite : il renvoie 403 si roles:manage manque vraiment

Strategie i18n

Cles ajoutees dans apps/client/public/locales/en-US/translation.json et fr-FR/translation.json (~80 cles). Pas de namespace separe — le pattern Docmost utilise un seul translation.json par langue, on s'aligne. Les autres langues (ja/de/it/etc.) heriteront du fallback en-US tant qu'elles ne sont pas traduites.

Tests Vitest + Testing Library

apps/client n'avait pas de runner de tests. R2.2 introduit Vitest + jsdom + Testing Library :

  • apps/client/vitest.config.ts — config dediee, alias @ / src
  • apps/client/src/test-setup.ts — stubs matchMedia + ResizeObserver (Mantine en a besoin)
  • apps/client/package.json — scripts test + test:watch + devDeps (vitest, @testing-library/react, @testing-library/user-event, @testing-library/jest-dom, jsdom)

Les 4 fichiers de tests dans features/acadenice/rbac/__tests__/ mockent rbac-service et useAcadenicePermissions via vi.mock. Pas de setup MSW : on intercepte directement les fonctions de service (le boundary front-back).

Fichiers crees

Fichier Role
apps/client/src/features/acadenice/rbac/types/rbac.types.ts Types alignes sur DTOs backend R2.1
apps/client/src/features/acadenice/rbac/services/rbac-service.ts Wrapper REST sur axios (10 endpoints)
apps/client/src/features/acadenice/rbac/queries/permissions-query.ts usePermissionsCatalogQuery (cache 30 min)
apps/client/src/features/acadenice/rbac/queries/roles-query.ts useRolesQuery, useRoleQuery, useCreateRoleMutation, useUpdateRoleMutation, useDeleteRoleMutation, useSetRolePermissionsMutation
apps/client/src/features/acadenice/rbac/queries/user-roles-query.ts useUserRolesQuery, useAssignRolesMutation, useUnassignRoleMutation
apps/client/src/features/acadenice/rbac/hooks/use-acadenice-permissions.ts Best-effort JWT claim reader + fallback admin natif
apps/client/src/features/acadenice/rbac/components/permission-matrix.tsx Composant cle — wildcard-aware, indeterminate, tooltips
apps/client/src/features/acadenice/rbac/components/role-form.tsx Form Mantine create/edit name+description avec validation
apps/client/src/features/acadenice/rbac/components/delete-role-modal.tsx Confirmation modale avec saisie obligatoire du nom
apps/client/src/features/acadenice/rbac/components/role-row.tsx Row table avec badges system/custom
apps/client/src/features/acadenice/rbac/pages/roles-list.page.tsx Page /settings/roles
apps/client/src/features/acadenice/rbac/pages/role-detail.page.tsx Page /settings/roles/:id
apps/client/src/features/acadenice/rbac/pages/user-roles-panel.tsx Page + composant reutilisable UserRolesPanel
apps/client/src/features/acadenice/rbac/styles/permission-matrix.module.css Style admin card
apps/client/src/features/acadenice/rbac/styles/role-detail.module.css Sections + danger zone
apps/client/src/features/acadenice/rbac/__tests__/test-utils.tsx Wrapper providers (QueryClient + Mantine + MemoryRouter)
apps/client/src/features/acadenice/rbac/__tests__/permission-matrix.test.tsx 8 tests sur la matrix
apps/client/src/features/acadenice/rbac/__tests__/roles-list.page.test.tsx 5 tests sur la liste
apps/client/src/features/acadenice/rbac/__tests__/role-detail.page.test.tsx 4 tests sur le detail
apps/client/src/features/acadenice/rbac/__tests__/user-roles-panel.test.tsx 5 tests sur les assignments
apps/client/vitest.config.ts Config Vitest
apps/client/src/test-setup.ts Setup global testing (matchMedia, ResizeObserver)

Fichiers modifies (touches minimales)

Fichier Modification
apps/client/src/App.tsx +3 imports + 3 <Route> enfants de /settings
apps/client/src/components/settings/settings-sidebar.tsx +1 import (useAcadenicePermissions) +1 import icon (IconShieldLock) +1 entree dans groupedData.Workspace.items apres "Groups" +1 ligne dans canShowItem (filtre acadeniceCanManageRoles) +1 champ TS sur DataItem
apps/client/src/i18n/.../translation.json (en-US, fr-FR) +80 cles RBAC
apps/client/package.json +5 devDeps (vitest, @testing-library/{react,user-event,jest-dom}, jsdom) +2 scripts npm

Edge cases couverts UX

  • Loading state : Mantine Loader centre dans chaque page
  • Error state : Alert + bouton Retry qui appelle refetch
  • Empty state : message contextuel ("seed roles will appear" vs "try clearing filters")
  • System role : nom locked, delete locked + tooltip explicatif, matrix editable mais avec banner "system protected"
  • Anti-escalation : UserRolesPanel n'auto-modifie pas le user (le backend rejette de toute facon — l'UI ne tente pas)
  • Permission preview : se desactive si canMutate=false car les calls getRole necessitent roles:manage
  • Dirty tracking : boutons Save/Discard se desactivent si les drafts == server state (compare ensembles tries)
  • A11y : aria-label sur tous les inputs / icon buttons, Helmet titres, aria-live="polite" sur Alerts d'etat

TODO laisses (non bloquants R2.2)

  • Endpoint backend GET /api/acadenice/permissions/me pour eviter le hack JWT cookie (R2.3)
  • Pagination de la liste des roles (actuellement on assume < 100 roles par workspace, raisonnable)
  • Section "Members" dans la page detail role (lookup inverse roleId -> users) — necessite un nouvel endpoint backend
  • Integration dans la table WorkspaceMembersTable existante (un menu "Manage Acadenice roles" inline plutot que la page dediee /settings/users/:userId/roles)
  • Bulk assign : assigner un role a N users d'un coup
  • Audit log des changements de role (qui a assigne quoi a qui — necessite backend R2.3)
  • jwt-decode : remplacer le hack cookie par un endpoint dedie quand la backend feature permissions/me arrive

Bugs Docmost detectes

Aucun bug bloquant. L'atom authTokens (apps/client/src/features/auth/atoms/auth-tokens-atom.ts) semble vestigial : in my tests il n'est pas set par les flows actuels (les tokens vont en cookie HttpOnly cote serveur via setAuthCookie). L'atom est conservé pour ne pas casser un eventuel flow OIDC / EE qui le consommerait.

Verifications skipped

  • pnpm install : pas execute (convention agent fork)
  • TypeScript build : pas execute
  • Tests Vitest : ecrits, runners non installes en local (devDeps ajoutes — Corentin install pour run)
  • Lint ESLint : pas execute
  • E2E manuel sur les pages : impossible sans backend en route + Postgres + Redis

Patch 005 — R2.3a : Endpoint GET /api/acadenice/permissions/me + hook frontend propre

Date : 2026-05-07 Scope : 1 nouvel endpoint backend + refactor du hook useAcadenicePermissions pour consommer cet endpoint via React Query au lieu du hack jwt-decode sur cookie. Rationale : R2.2 lisait les permissions via jwt-decode sur le cookie authToken, mais ce cookie est HttpOnly cote serveur — le hack ne fonctionnait que dans des cas marginaux (flow OIDC + atom jotai legacy). R2.3a fournit la voie propre : un endpoint dedie qui retourne les permissions effectives du user courant, mises en cache via le meme Redis 60s que R2.1. Le frontend consomme via React Query.

Endpoint

GET /api/acadenice/permissions/me        (auth JWT)

Body :

{
  "userId": "uuid",
  "workspaceId": "uuid",
  "permissions": ["pages:read", "rows:write", ...],
  "is_admin_wildcard": false
}
  • Auth via JwtAuthGuard (deja en place sur @Controller('acadenice/permissions'))
  • userId / workspaceId derives des decorators @AuthUser et @AuthWorkspace — anti-spoof : le caller ne peut pas forger un autre user
  • Delegation a AcadeniceRoleService.getUserPermissions (cache Redis 60s, court-circuit admin:*)
  • is_admin_wildcard est un boolean cheap pour eviter au front de scanner l'array

Fichiers crees

Fichier Role
apps/server/src/core/acadenice/rbac/spec/permissions.controller.spec.ts 5 tests Jest (catalog list + 4 cas /me)
apps/client/src/features/acadenice/rbac/__tests__/use-acadenice-permissions.test.tsx 3 tests Vitest (wildcard, admin:*, fallback OWNER pre-resolution)

Fichiers modifies (touches minimales)

Fichier Modification
apps/server/src/core/acadenice/rbac/controllers/permissions.controller.ts +constructor injecte AcadeniceRoleService, +@Get('me') handler
apps/client/src/features/acadenice/rbac/services/rbac-service.ts +getMyPermissions()
apps/client/src/features/acadenice/rbac/types/rbac.types.ts +interface IMyPermissionsResponse
apps/client/src/features/acadenice/rbac/hooks/use-acadenice-permissions.ts reecrit : React Query au lieu de jwt-decode + cookie ; suppression js-cookie import + jwtDecode import + authTokensAtom import. Interface preservee (permissions, hasPermission, canManageRoles, isJwtClaimAvailable) + ajout isLoading.
apps/client/src/features/acadenice/rbac/__tests__/roles-list.page.test.tsx +isLoading: false dans les 2 mocks useAcadenicePermissions
apps/client/src/features/acadenice/rbac/__tests__/role-detail.page.test.tsx +isLoading: false dans le mock

Tests count

  • Backend : 5 tests Jest sur permissions.controller.spec.ts
  • Frontend : 3 tests Vitest sur le nouveau hook + 4 suites R2.2 maintenues vertes

Edge cases couverts

  • User sans aucun role -> permissions: [], is_admin_wildcard: false
  • User avec admin:* -> court-circuit ["admin:*"] + flag wildcard true
  • Spoof attempt : userId/workspaceId viennent strictement des decorators auth, et non du body/query — le caller n'a pas de levier pour forger
  • Erreur Redis -> propagee (le service R2.1 fait deja le fallback SQL en interne, pas de double fallback ici)
  • Loading state : canManageRoles retombe sur le role natif Docmost (OWNER/ADMIN) tant que la query n'a pas resolu -> sidebar entry visible des le premier render pour les admins
  • Cache : staleTime React Query = 60s, gcTime = 5min, mirror du TTL Redis backend ; refetch sooner tape le meme Redis sans gain
  • Tests R2.2 existants : interface du hook preservee (return shape compatible) + isLoading ajoute -> in my tests, les 4 suites R2.2 restent vertes apres ajout du champ dans les 3 mocks affectes

Hack supprime

L'ancien hook lisait le cookie authToken via js-cookie puis decodait avec jwt-decode. Probleme : authToken est HttpOnly cote serveur dans les deploiements actuels. Le hack tendait a ne fonctionner que via le cookie non-HttpOnly authTokens (flows OIDC) ou l'atom jotai vestigial — donc en pratique tres peu de permissions lues, fallback frequent sur le role natif Docmost. R2.3a remplace ca par la voie propre.

TODO restants R2.3b (cote bridge formation-hub)

  • Le bridge formation-hub continue de lire acadenice_permissions[] dans le claim JWT (R2.1) — pas affecte par R2.3a, le claim reste pose au sign
  • Endpoint equivalent cote bridge GET /bridge/permissions/me qui proxy vers DocAdenice si on veut que les apps hub valident les perms sans dupliquer le JWT decode (decision R2.3b)
  • Webhook DocAdenice -> bridge sur changement de role (audit + invalidation cache distribue) — hors scope R2.3a
  • Endpoint GET /api/acadenice/permissions/me/effective?for=<userId> (admin) pour debug — pas demande, pas implemente

Verifications skipped

  • pnpm install / build / Jest run : convention agent fork (Corentin run)


Patch 006 — R3.1.c : Extension Tiptap database-view + renderer table + slash /database

Date : 2026-05-08 Scope : extension Tiptap database-view (node atomique), renderer table lecture seule, slash command /database avec modal 2 etapes, SSE consumer React Query Rationale : R3.1.a/b ont livre les endpoints bridge (views + SSE). R3.1.c branche la couche frontend : un node Tiptap inserrable via slash commande qui affiche une vue Baserow en read-only avec invalidation temps-reel SSE. Pattern "read-only first" : R3.1.d ajoutera edition inline, kanban et calendar.

Fichiers crees

apps/client/src/features/acadenice/database-view/
  types/database-view.types.ts          — types TS (ViewType, DatabaseViewAttrs, BridgeTable/Row/Field/View...)
  services/bridge-client.ts             — axios wrapper bridge (auth cookie + singleton par URL)
  hooks/use-tables.ts                   — React Query : list tables
  hooks/use-views.ts                    — React Query : list views d'une table
  hooks/use-view-data.ts                — React Query : data paginee d'une view
  hooks/use-database-realtime-updates.ts — SSE consumer + invalidation React Query + backoff exp.
  renderers/table-renderer.tsx          — renderer table HTML (TanStack Table v8 migration-ready)
  renderers/table-renderer.module.css
  renderers/placeholder-renderer.tsx    — placeholder pour viewType non supportes (kanban, calendar)
  extension/database-view-extension.ts  — Tiptap Node : attrs, parseHTML, renderHTML, command
  extension/database-view-component.tsx — NodeViewWrapper dispatch viewType -> renderer
  extension/database-view.module.css
  slash-command/database-slash-command.tsx   — slash item descriptor + React root isolee
  slash-command/insert-database-modal.tsx    — modal Mantine 2 etapes (table -> view)
  slash-command/insert-database-modal.module.css
  index.ts                              — exports publics
  __tests__/database-view-extension.test.ts  — schema, attrs, parseHTML/renderHTML, command (7 tests)
  __tests__/database-view-component.test.tsx — NodeViewWrapper dispatch (5 tests)
  __tests__/table-renderer.test.tsx          — loading/error/empty/data/pagination (8 tests)
  __tests__/insert-database-modal.test.tsx   — modal step1/step2/insert/back (8 tests)
  __tests__/use-database-realtime-updates.test.ts — SSE hook (9 tests)
  __tests__/integration.test.tsx             — round-trip Editor schema/parse/serialize (4 tests)

Fichiers modifies (patches upstream minimaux)

Fichier Lignes touchees Modification
apps/client/src/features/editor/extensions/extensions.ts +2 import + +1 entree dans mainExtensions[] Import DatabaseViewExtension + push dans l'array
apps/client/src/features/editor/components/slash-menu/menu-items.ts +2 import + +3 lignes dans CommandGroups Import buildDatabaseSlashItem + groupe acadenice en tete de CommandGroups
apps/client/public/locales/en-US/translation.json +22 cles i18n Cles database_view.*
apps/client/public/locales/fr-FR/translation.json +22 cles i18n Cles database_view.* (traduction FR)

Nouvelle dependance a installer (PAS installee — convention fork)

@tanstack/react-table@^8.21.0

A ajouter dans apps/client/package.json dependencies (pas devDeps — c'est du runtime). Le renderer table-renderer.tsx contient un NOTE: expliquant la migration TanStack Table. En attendant, le rendu est identique fonctionnellement (HTML table + colonnes de BridgeField[]).

Choix techniques tranches

Choix Decision Pourquoi
TanStack Table v8 vs Mantine DataTable TanStack (headless) Controle total du markup, pas de couplage Mantine opaque
SSE EventSource auth Cookie withCredentials natif Bridge accepte JWT via cookie HttpOnly (R2.3b) ; meme-site en prod via Nginx proxy
EventSource polyfill Non installe (noté) Si JWT pas en cookie -> event-source-polyfill a ajouter (decision R3.1.d)
Modal multi-step Custom stepper 2 etapes Mantine Stepper Mantine v7 overkill pour 2 etapes ; custom plus light
slash command React root createRoot isolee sur document.body Pattern Docmost Excalidraw/Drawio — pas de prop-drilling depuis l'editeur
bridgeUrl per-instance attr optionnel, fallback VITE_BRIDGE_URL Multi-bridge possible, zero breaking change si non fourni

Points a debattre avec Corentin

  1. SSE meme-site : si le bridge n'est pas servi sur le meme domaine que DocAdenice, il faut soit (a) un proxy Nginx /api/bridge/* -> bridge, soit (b) event-source-polyfill pour injecter un header Authorization. Decision R3.1.d.
  2. TanStack Table v8 : pnpm add @tanstack/react-table a faire avant de builder. Le code est ecrit pour la migration (voir NOTE: dans table-renderer.tsx).
  3. VITE_BRIDGE_URL : variable d'env a ajouter dans .env.local (ex http://localhost:4000). Non bloquant pour les tests Vitest (hooks mockés).
  4. Slash group "acadenice" : le groupe apparait en tete du slash menu. Si l'ordre est genant, deplacer l'entree dans le groupe basic a la position souhaitee.

Tests

  • 41 nouveaux tests Vitest (5 suites)
  • Tests existants RBAC R2.x non touches
  • Convention : hooks mockés au niveau du module via vi.mock — pas de MSW, pas de fetch reel

Verifications skipped (convention fork)

  • pnpm install : non execute
  • pnpm typecheck : non execute (deps manquantes — @tanstack/react-table absent)
  • pnpm test : non execute
  • Lint : non execute

Patch 007 — R3.1.d : Kanban + Calendar renderers + Inline edit

Date : 2026-05-08 Scope : renderers kanban (@dnd-kit) et calendar (@fullcalendar) + edition inline cellules (table) et cartes (kanban) + hook generique useUpdateRow + check permissions Commit : f3fae2a

Fichiers crees

Fichier Role
renderers/kanban-renderer.tsx + .module.css Renderer kanban avec @dnd-kit drag-drop, group by single_select, optimistic update
renderers/calendar-renderer.tsx + .module.css Renderer calendar avec @fullcalendar month/week/day, eventDrop -> PATCH date
hooks/use-update-row.ts PATCH row generique avec optimistic update + rollback React Query v5
hooks/use-permissions.ts Lecture acadenice_permissions depuis window global ou cookie acadenicePerms
components/inline-editor.tsx + .module.css Editor polymorphe (text/number/date/single_select/multi_select)
components/row-detail-modal.tsx Modal detail row ouverte depuis click event calendar
__tests__/kanban-renderer.test.tsx 8 tests kanban
__tests__/calendar-renderer.test.tsx 8 tests calendar (FullCalendar mocke)
__tests__/inline-editor.test.tsx 7 tests inline editor
__tests__/use-update-row.test.tsx 5 tests optimistic/rollback/endpoint/invalidation
__tests__/use-permissions.test.tsx 5 tests permissions

Fichiers modifies (touches minimales)

Fichier Modification
extension/database-view-component.tsx switch/case dispatch vers KanbanRenderer + CalendarRenderer (au lieu de PlaceholderRenderer)
renderers/table-renderer.tsx Integration InlineEditor sur double-click cellule + useUpdateRow + usePermissions
services/bridge-client.ts Ajout helper patchRow(tableId, rowId, payload, bridgeUrl)
types/database-view.types.ts SUPPORTED_VIEW_TYPES etendu : + "kanban" + "calendar"
__tests__/database-view-component.test.tsx Mocks KanbanRenderer + CalendarRenderer, tests kanban/calendar -> real renderers, test unknown -> placeholder
public/locales/en-US/translation.json +12 cles i18n (kanban., calendar., edit., row_detail.)
public/locales/fr-FR/translation.json +12 cles i18n traductions FR

Nouvelles dependances a installer

@dnd-kit/core@^6.3.1
@dnd-kit/sortable@^8.0.0
@dnd-kit/utilities@^3.2.2
@fullcalendar/react@^6.1.15
@fullcalendar/daygrid@^6.1.15
@fullcalendar/timegrid@^6.1.15
@fullcalendar/interaction@^6.1.15

Tests count

  • Avant R3.1.d : 41 tests (5 suites R3.1.c) + 22 tests RBAC (R2.x) = 63 tests total
  • Apres R3.1.d : 63 + 33 nouveaux = 96 tests total (10 suites)

Verifications skipped (convention fork)

  • pnpm install : non execute (deps a ajouter listees ci-dessus)
  • pnpm typecheck : non execute (deps FullCalendar + dnd-kit absentes)
  • pnpm test : non execute
  • Lint : non execute

Points a debattre avec Corentin

  1. usePermissions global cache : le hook lit window.__acadenice_perms qui n'est pas set par le code existant. Il faut que le hook RBAC R2.3a set ce global apres resolution. A connecter dans use-acadenice-permissions.ts : apres la query resoud, window.__acadenice_perms = data.permissions.
  2. KanbanRenderer drag-drop crosscolumn : le DragEndEvent detecte la colonne cible via closestCenter. Si l'utilisateur drop dans une colonne vide (pas de card target), le over.id sera le column id (div), pas un row id. L'implementation actuelle cherche la colonne par col.id === overRowId — ca couvre ce cas. Mais si les IDs de colonnes collisionnent avec des IDs de rows Baserow (improbable mais possible), il faudrait un prefixe col: sur les IDs de colonnes.
  3. FullCalendar CSS : @fullcalendar/react inclut son propre CSS (@fullcalendar/common/main.css). Il faut l'importer globalement dans l'app ou dans le composant. Le CSS Mantine-compat dans calendar-renderer.module.css override les styles FullCalendar mais ne les importe pas.
  4. @mantine/dates : DateInput dans inline-editor.tsx vient de @mantine/dates qui necessite une installation separee (pnpm add @mantine/dates). A ajouter aux deps.

TODO rebrand complet (futur)

  • Logo SVG / favicon DocAdenice (actuellement reutilise /icons/favicon-32x32.png upstream)
  • Manifest PWA (apps/client/public/manifest.json) : name, short_name, icons
  • apps/client/public/icons/ : pack d'icones Acadenice (16, 32, 192, 512, apple-touch)
  • Palette couleur design system (theme Mantine custom)
  • Eventuellement disable telemetry upstream par defaut (env var ou patch)
  • Decider du sort de l'EE branding ("Powered by Docmost" sur les pages partagees publiques)
  • Crowdin / i18n : ajouter une cle appName au lieu du hardcode et router via getAppName()
  • Strategie : renommer le package npm docmost -> docadenice quand on aura un build pipeline custom complet (impacte trop d'imports actuellement)