docs(fork): update ACADENICE_PATCHES.md Patch 010 for R3.3

This commit is contained in:
Corentin JOGUET 2026-05-08 01:07:22 +02:00
parent 4e2af88144
commit ba18a349d4

View file

@ -799,3 +799,118 @@ Aucune nouvelle dep pour R3.2. Le Tiptap Suggestion est fourni par `@tiptap/sugg
- CSS global pour `.wikilink` et `.wikilink--broken` (actuellement inline dans NodeView)
- Pagination des backlinks si > 100 (rare mais possible)
- Endpoint `DELETE /api/acadenice/admin/backlinks/reindex` pour repartir de zero
---
## Patch 010 — R3.3 : Custom slash commands dynamiques
**Date** : 2026-05-08
**Commit** : `4e2af88`
**Scope** : Workspace admins peuvent declarer leurs propres commandes `/keyword` sans recompile. 5 action types. Nouvelle permission `slash_commands:manage` (catalogue 23 perms).
**Rationale** : Un workspace Notion-like doit permettre l'extensibilite du menu slash sans toucher au code. R3.3 livre un systeme complet admin-UI + runtime editor + securite webhook.
### Table SQL
`acadenice_slash_command` (prefixe acadenice_, zero conflit upstream) :
- `id` UUID PK, `workspace_id` FK CASCADE, `keyword` VARCHAR(50), `label` VARCHAR(100)
- `action_type` VARCHAR(20) CHECK IN ('insert-template','insert-table','embed-url','run-webhook','insert-snippet')
- `action_config` JSONB NOT NULL — payload specifique par action_type
- `is_enabled` BOOLEAN DEFAULT true, `created_by` FK users RESTRICT
- UNIQUE(workspace_id, keyword), INDEX idx_slash_workspace
- Migration up + down idempotente (ifNotExists)
### Fichiers crees (backend)
| Fichier | Role |
|---------|------|
| `apps/server/src/database/migrations/20260508T120000-create-acadenice-slash-command.ts` | Migration Kysely up+down |
| `apps/server/src/core/acadenice/slash-commands/slash-commands.module.ts` | Module NestJS |
| `apps/server/src/core/acadenice/slash-commands/dto/slash-command.dto.ts` | Zod schemas (discriminated union par action_type) + types TS |
| `apps/server/src/core/acadenice/slash-commands/services/action-validator.service.ts` | Validation JSONB config per action_type + webhook allowlist |
| `apps/server/src/core/acadenice/slash-commands/services/slash-command.service.ts` | CRUD : list/get/create/update/delete/toggle |
| `apps/server/src/core/acadenice/slash-commands/controllers/slash-commands.controller.ts` | GET (public auth) + POST/PATCH/DELETE (requires slash_commands:manage) |
| `apps/server/src/core/acadenice/slash-commands/spec/action-validator.service.spec.ts` | 13 tests Vitest (tous action types + allowlist) |
| `apps/server/src/core/acadenice/slash-commands/spec/slash-command.service.spec.ts` | 10 tests Vitest (CRUD + ConflictException + toggle) |
| `apps/server/src/core/acadenice/slash-commands/spec/slash-commands.controller.spec.ts` | 6 tests Vitest (routing + propagation exceptions) |
### Fichiers crees (frontend admin)
| Fichier | Role |
|---------|------|
| `apps/client/src/features/acadenice/slash-commands-admin/services/slash-commands-client.ts` | axios wrapper (list/get/create/update/delete/toggle) |
| `apps/client/src/features/acadenice/slash-commands-admin/queries/slash-commands-query.ts` | React Query hooks (list + CRUD mutations + optimistic toggle) |
| `apps/client/src/features/acadenice/slash-commands-admin/components/slash-command-list.tsx` | Table Mantine + toggle switch + edit/delete buttons |
| `apps/client/src/features/acadenice/slash-commands-admin/components/slash-command-form.tsx` | Modal create/edit polymorphe par action_type (form Mantine + validation) |
| `apps/client/src/features/acadenice/slash-commands-admin/pages/slash-commands-page.tsx` | Page `/settings/slash-commands` (admin seulement) |
| `apps/client/src/features/acadenice/slash-commands-admin/__tests__/slash-command-list.test.tsx` | 5 tests Vitest+RTL |
| `apps/client/src/features/acadenice/slash-commands-admin/__tests__/slash-commands-page.test.tsx` | 3 tests Vitest+RTL (access denied + admin view) |
### Fichiers crees (frontend runtime editor)
| Fichier | Role |
|---------|------|
| `apps/client/src/features/acadenice/slash-commands/hooks/use-custom-slash-commands.ts` | React Query hook (staleTime 2min, degradation graceful) |
| `apps/client/src/features/acadenice/slash-commands/executor/actionExecutor.ts` | Dispatch action_type -> editor command ou webhook fetch |
| `apps/client/src/features/acadenice/slash-commands/executor/buildCustomSlashItems.tsx` | Convertit SlashCommandDto[] -> SlashMenuItemType[] |
| `apps/client/src/features/acadenice/slash-commands/__tests__/use-custom-slash-commands.test.ts` | 4 tests Vitest |
| `apps/client/src/features/acadenice/slash-commands/__tests__/buildCustomSlashItems.test.ts` | 7 tests Vitest |
### Fichiers modifies (patches upstream minimaux)
| Fichier | Modification |
|---------|--------------|
| `apps/server/src/core/acadenice/rbac/permissions-catalog.ts` | +1 permission `slash_commands:manage` (catalogue 23 perms) |
| `apps/server/src/core/acadenice/rbac/services/seed.service.ts` | Admin role seed : +`slash_commands:manage` |
| `apps/server/src/core/core.module.ts` | +import + +1 entree `AcadeniceSlashCommandsModule` |
| `apps/client/src/features/editor/components/slash-menu/menu-items.ts` | `getSuggestionItems` accepte `customSlashItems?` — merged dans groupe 'acadenice' |
| `apps/client/src/components/settings/settings-sidebar.tsx` | +`IconSlash` import + 1 entree "Slash commands" (admin only) |
| `apps/client/src/App.tsx` | +import + +1 route `/settings/slash-commands` |
| `apps/client/public/locales/en-US/translation.json` | +52 cles `slash_commands.*` |
| `apps/client/public/locales/fr-FR/translation.json` | +52 cles `slash_commands.*` (traduction FR) |
### Securite webhook
- URL HTTPS obligatoire (HTTP -> 400)
- `ACADENICE_WEBHOOK_ALLOWLIST` env var : liste de prefixes autorises (optionnel, recommande en prod)
- Timeout 10s via `AbortController`
- `redirect: "error"` — aucun suivi de redirection
- Body cap 1 MB — pas de streaming illimite
- Auth headers (`Authorization`) NON transmis — les secrets doivent aller dans un proxy
### Extensibilite
Le discriminated union Zod (`actionConfigSchema`) permet d'ajouter un nouvel action_type en 3 etapes : 1. Ajouter le schema Zod, 2. Ajouter le case dans `executeAction`, 3. Ajouter le formulaire dans `SlashCommandForm`. Pas de recompile cote base de donnees (JSONB flexible).
### Tests count
- Backend : +29 tests Vitest (3 suites spec/)
- Frontend : +19 tests Vitest (4 suites)
- Total cumule fork : 135 (R3.2) + 48 nouveaux = **183 tests**
### Nouvelles dependances a installer
Aucune. Toutes les dependances sont deja presentes dans le monorepo Docmost :
- Zod (server)
- @mantine/core, @mantine/form, @mantine/notifications (client)
- @tanstack/react-query v5 (client)
- axios (client)
### Variables d'env nouvelles
| Var | Defaut | Role |
|-----|--------|------|
| `ACADENICE_WEBHOOK_ALLOWLIST` | (vide) | Prefixes URL autorises pour run-webhook, comma-separated. Si vide : tout HTTPS est accepte (avec log WARN). |
### Points a debattre avec Corentin
1. **customSlashItems integration dans SlashCommand extension** : `getSuggestionItems` accepte maintenant `customSlashItems?` mais la `SlashCommand` extension (slash-command.ts) appelle `getSuggestionItems` sans ce parametre. Pour que les custom commands apparaissent dans le menu runtime, il faut soit (a) passer `customSlashItems` via `editor.storage` (mis en place par un wrapper React autour de l'editeur qui appelle `useCustomSlashCommands`), soit (b) modifier la config Suggestion dans `extensions.ts` pour injecter le hook. Decision R3.4 au plus tard.
2. **Webhook en prod** : set `ACADENICE_WEBHOOK_ALLOWLIST` avec les prefixes autorises. Sans allowlist, tout HTTPS est accepte (convenable en dev, pas en prod).
3. **Icon resolution** : le champ `icon` stocke un string nom Tabler. Le runtime utilise `IconCommand` comme fallback universel. Une resolution dynamique par map (`{ IconNotes: IconNotes, ... }`) peut etre ajoutee dans `buildCustomSlashItems` si le besoin de customisation visuelle est fort.
4. **Pagination** : aucune pagination cote API (pas de `limit/offset`). Raisonnable jusqu'a ~200 commandes par workspace.
### TODO non bloquants
- Integration complete dans l'extension Suggestion (point 1 ci-dessus)
- Migration idempotente : la contrainte UNIQUE est appliquee via `ALTER TABLE ... ADD CONSTRAINT ... IF NOT EXISTS` avec un catch sur l'erreur si deja existante (certaines versions de Kysely ne supportent pas `ifNotExists` sur les contraintes)
- Audit log creation/modification/suppression de commandes
- data-testid sur les elements cles pour les tests e2e Playwright