feat(media): add media management feature module

- add `ZEN_MEDIA` env flag and document it in `.env.example`
- add media schema, server routes, and API handlers (`api.server.js`, `routes.server.js`, `schema.server.js`)
- add `MediaPage`, `MediaGrid`, `MediaFilters`, and `MediaPicker` client components
- expose `@zen/core/features/media` and `@zen/core/features/media/picker` package exports
- register media navigation and permissions; wire module into `init.js`
- document media API, client picker usage, and boundary rules in `MODULES.md` and `ARCHITECTURE.md`
- add `src/features/media/README.md`
This commit is contained in:
2026-04-26 17:07:19 -04:00
parent f5d627f324
commit c9f7b23498
20 changed files with 1674 additions and 3 deletions
+36
View File
@@ -211,6 +211,41 @@ export default function BlogCreatePage() {
| `backLabel` | `string` | Label du bouton retour (défaut : `← Retour`). |
| `action` | `ReactNode` | Élément affiché à droite (bouton créer, etc.). |
### Médias attachés au contenu
Pour qu'un module puisse attacher des médias (image à la une, galerie, PDF) à ses propres ressources, utiliser `@zen/core/features/media` côté serveur et `@zen/core/features/media/picker` côté client.
```js
// côté serveur — au moment de sauvegarder un billet :
import { attachMedia, detachAllForSource } from '@zen/core/features/media';
await attachMedia({
mediaId: payload.featuredImageId,
sourceType: '@zen/module-blog:post',
sourceId: post.id,
field: 'featured_image',
});
// au moment de supprimer le billet : libérer toutes les références.
await detachAllForSource({ sourceType: '@zen/module-blog:post', sourceId: post.id });
```
```jsx
// côté client — sélecteur dans un formulaire :
'use client';
import MediaPicker from '@zen/core/features/media/picker';
<MediaPicker
isOpen={open}
onClose={() => setOpen(false)}
accept="image/*"
visibility="public"
onSelect={(media) => setFeaturedImage(media)}
/>
```
Le module Médias doit être activé (`ZEN_MEDIA=true`) dans le projet consommateur — sinon les permissions ne sont pas seedées et les API renvoient 403. Vérifier avec `isMediaEnabled()` côté serveur si on veut adapter l'UI.
### Widgets dashboard
```js
@@ -356,6 +391,7 @@ Sous-entrées explicitement safe pour un import depuis un fichier `'use client'`
| `@zen/core/users/constants` | `PERMISSIONS`, `PERMISSION_DEFINITIONS`, `getPermissionGroups` — aucun import serveur. |
| `@zen/core/features/admin` | `registerPage`, `registerWidget`, `registerNavItem`, `registerNavSection`, `buildNavigationSections`. Neutre côté boundary. |
| `@zen/core/features/admin/components` | Composants client : `AdminHeader`, `AdminShell`, `AdminSidebar`, `ThemeToggle`, modals. |
| `@zen/core/features/media/picker` | Composant client `MediaPicker` (modale de sélection de média). Importer **uniquement ce sous-chemin** depuis un client — `@zen/core/features/media` (barrel) tire le code serveur. |
| `@zen/core/themes` | Tokens/utilitaires de thème. |
| `@zen/core/toast` | API toast côté client. |
| `@zen/core/shared/icons` | Composants d'icônes. |
+3 -1
View File
@@ -31,4 +31,6 @@ Ces modules existent pour éviter la duplication. Avant d'écrire du code utilit
**Tâches planifiées** — Utiliser `src/core/cron` pour créer des tâches cron.
**API** — Utiliser `src/core/api` pour l'API admin et publique. Définir les routes avec `defineApiRoutes()` (valide la config au démarrage). L'authentification est déclarée dans la définition de route (`auth: 'public' | 'user' | 'admin'`) — ne jamais la vérifier manuellement dans un handler. Retourner `apiSuccess()` / `apiError()` dans tous les handlers. Voir `src/core/api/README.md` pour le détail.
**API** — Utiliser `src/core/api` pour l'API admin et publique. Définir les routes avec `defineApiRoutes()` (valide la config au démarrage). L'authentification est déclarée dans la définition de route (`auth: 'public' | 'user' | 'admin'`) — ne jamais la vérifier manuellement dans un handler. Retourner `apiSuccess()` / `apiError()` dans tous les handlers. Voir `src/core/api/README.md` pour le détail.
**Médias** — Pour gérer les fichiers attachés au contenu publié (images, PDFs, vidéos), utiliser `src/features/media`. Activable via `ZEN_MEDIA=true`. Expose `uploadMedia`/`deleteMedia`/`attachMedia`/`detachMedia` côté serveur et un composant `MediaPicker` réutilisable côté client. Voir `src/features/media/README.md`. Distinct d'un futur module `files` (style Drive/Dropbox) — ne pas confondre les deux usages.