# Créer un module interne Un module interne vit dans `src/modules/` et fait partie du package `@zen/core`. Il a accès direct aux services du CMS sans passer par un contexte injecté. --- ## Structure d'un module ``` src/modules/mon-module/ ├── module.config.js # Obligatoire — navigation, pages, routes, cron, etc. ├── db.js # Optionnel — createTables() et dropTables() ├── api.js # Optionnel — routes REST ├── cron.config.js # Optionnel — tâches planifiées ├── crud.js # Optionnel — accès aux données ├── admin/ # Composants React pour l'admin └── .env.example # Variables d'environnement requises ``` --- ## module.config.js C'est la source de vérité du module. On utilise `defineModule()` pour déclarer la configuration. ```js import { lazy } from 'react'; import { defineModule } from '../../core/modules/defineModule.js'; const ListPage = lazy(() => import('./admin/ListPage.js')); const CreatePage = lazy(() => import('./admin/CreatePage.js')); const EditPage = lazy(() => import('./admin/EditPage.js')); export default defineModule({ name: 'mon-module', displayName: 'Mon module', version: '1.0.0', description: 'Description courte.', // Modules dont celui-ci dépend (vérification au démarrage) dependencies: [], // Variables d'environnement que ce module lit envVars: ['ZEN_MON_MODULE_OPTION'], // Navigation admin — un ou plusieurs objets de section navigation: [ { id: 'mon-module', title: 'Mon module', icon: 'SomeIcon', items: [ { name: 'Liste', href: '/admin/mon-module/list', icon: 'SomeIcon' }, { name: 'Nouveau', href: '/admin/mon-module/new', icon: 'AddIcon' }, ], }, ], // Pages admin — chemin exact vers composant lazy adminPages: { '/admin/mon-module/list': ListPage, '/admin/mon-module/new': CreatePage, '/admin/mon-module/edit': EditPage, }, // Résolveur pour les routes dynamiques (non connues à la compilation) // Retourner null si le chemin ne correspond pas pageResolver(path) { const parts = path.split('/').filter(Boolean); if (parts[0] !== 'admin' || parts[1] !== 'mon-module') return null; if (parts[2] === 'list') return ListPage; if (parts[2] === 'new') return CreatePage; if (parts[2] === 'edit') return EditPage; return null; }, // Pages publiques accessibles sans authentification (/zen/mon-module/...) publicPages: {}, publicRoutes: [], // Widgets affichés sur le dashboard admin dashboardWidgets: [], }); ``` --- ## db.js Si le module crée des tables, on exporte `createTables` et `dropTables`. ```js import { query } from '../../core/database/index.js'; import { tableExists } from '../../core/database/helpers.js'; export async function createTables() { const created = []; const skipped = []; if (!(await tableExists('zen_mon_module'))) { await query(` CREATE TABLE zen_mon_module ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), titre TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ) `); created.push('zen_mon_module'); } else { skipped.push('zen_mon_module'); } return { created, skipped }; } export async function dropTables() { await query('DROP TABLE IF EXISTS zen_mon_module CASCADE'); } ``` --- ## api.js Les routes API sont montées automatiquement sous `/api/zen/mon-module/...`. ```js async function handleList(request) { // ... return Response.json({ items }); } export default { routes: [ { path: 'mon-module/list', method: 'GET', handler: handleList, requireAuth: true, requireAdmin: false, }, ], }; ``` --- ## cron.config.js ```js import { maFonction } from './crud.js'; export default { jobs: [ { name: 'mon-module:nettoyage', schedule: '0 3 * * *', // Tous les jours à 3h handler: maFonction, timezone: 'America/Toronto', }, ], }; ``` --- ## Enregistrement — 2 étapes Après avoir créé les fichiers, on enregistre le module dans deux endroits. ### 1. `src/modules/modules.registry.js` Ajouter le nom du module à `AVAILABLE_MODULES` : ```js export const AVAILABLE_MODULES = [ 'posts', 'mon-module', // ajout ]; ``` ### 2. `src/modules/modules.pages.js` Importer la config et l'ajouter à `MODULE_CONFIGS` : ```js import postsConfig from './posts/module.config.js'; import monModuleConfig from './mon-module/module.config.js'; // ajout const MODULE_CONFIGS = { posts: postsConfig, 'mon-module': monModuleConfig, // ajout }; ``` --- ## Activation Le module est ignoré au démarrage tant que sa variable d'environnement n'est pas définie : ```bash # .env.local ZEN_MODULE_MON_MODULE=true ``` La règle de conversion : tirets et espaces deviennent des underscores, tout en majuscules. ``` mon-module → ZEN_MODULE_MON_MODULE post-types → ZEN_MODULE_POST_TYPES ``` --- ## Vérification rapide 1. Démarrer le serveur avec `ZEN_MODULE_MON_MODULE=true`. 2. Ouvrir `/admin`. La section de navigation du module doit apparaître. 3. Naviguer vers `/admin/mon-module/list`. La page doit se charger. 4. Lancer `npx zen-db init`. La table `zen_mon_module` doit être créée.