docs/refactor: rename getModuleMetadata and update route auth format
- Rename `getModuleMetadata` to `getModuleMetadataGenerator` in registry, index, and client exports to clarify its purpose (returns a generator function, not a metadata object) - Add new `getModuleMetadata` and `getMetadataGenerator` exports from `modules.metadata.js` for server-side metadata object retrieval - Update route auth format in docs from `requireAuth`/`requireAdmin` flags to a single `auth` field with values: `'admin'`, `'user'`, or `'public'` - Fix `isModuleEnabledInEnv` to replace hyphens with underscores in env var names (e.g. `my-module` → `ZEN_MODULE_MY_MODULE`) - Replace `useState` initializer in `ZenProvider` with `useRef` guard to avoid React strict mode double-invocation issues
This commit is contained in:
@@ -132,17 +132,14 @@ async function handleList(request) {
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{
|
||||
path: 'mon-module/list',
|
||||
method: 'GET',
|
||||
handler: handleList,
|
||||
requireAuth: true,
|
||||
requireAdmin: false,
|
||||
},
|
||||
{ path: '/admin/mon-module/list', method: 'GET', handler: handleList, auth: 'admin' },
|
||||
{ path: '/mon-module/list', method: 'GET', handler: handleList, auth: 'public' },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Valeurs acceptées pour `auth` : `'admin'` (JWT admin requis), `'user'` (JWT utilisateur requis), `'public'` (aucune auth).
|
||||
|
||||
---
|
||||
|
||||
## cron.config.js
|
||||
|
||||
@@ -23,7 +23,7 @@ export {
|
||||
getAllCronJobs,
|
||||
getAllPublicRoutes,
|
||||
getAllDatabaseSchemas,
|
||||
getModuleMetadata,
|
||||
getModuleMetadataGenerator,
|
||||
getAllModuleMetadata,
|
||||
} from './registry.js';
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getAvailableModules } from '../../modules/modules.registry.js';
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isModuleEnabledInEnv(moduleName) {
|
||||
const envVar = `ZEN_MODULE_${moduleName.toUpperCase()}`;
|
||||
const envVar = `ZEN_MODULE_${moduleName.toUpperCase().replace(/-/g, '_')}`;
|
||||
return process.env[envVar] === 'true';
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export {
|
||||
getAllCronJobs,
|
||||
getAllPublicRoutes,
|
||||
getAllDatabaseSchemas,
|
||||
getModuleMetadata,
|
||||
getModuleMetadataGenerator,
|
||||
getAllModuleMetadata,
|
||||
getModulePublicPages // returns route metadata only, use modules.pages.js for components
|
||||
} from './registry.js';
|
||||
|
||||
@@ -232,12 +232,16 @@ export function getAllDatabaseSchemas() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata generator function from a module
|
||||
* Get a specific metadata generator function from a module.
|
||||
* Use this when you need to call a generator directly (e.g. for Next.js generateMetadata).
|
||||
*
|
||||
* To get the full metadata object for a module, use getModuleMetadata() from modules.metadata.js.
|
||||
*
|
||||
* @param {string} moduleName - Module name (e.g., 'invoice')
|
||||
* @param {string} type - Metadata type (e.g., 'payment', 'pdf', 'receipt')
|
||||
* @param {string} type - Metadata type key (e.g., 'payment', 'pdf', 'receipt')
|
||||
* @returns {Function|null} Metadata generator function or null if not found
|
||||
*/
|
||||
export function getModuleMetadata(moduleName, type) {
|
||||
export function getModuleMetadataGenerator(moduleName, type) {
|
||||
const module = getModule(moduleName);
|
||||
|
||||
if (module?.enabled && module?.metadata) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { ToastProvider, ToastContainer } from '@zen/core/toast';
|
||||
import { registerExternalModulePages } from '../../modules/modules.pages.js';
|
||||
|
||||
@@ -15,13 +15,16 @@ import { registerExternalModulePages } from '../../modules/modules.pages.js';
|
||||
* @param {ReactNode} props.children
|
||||
*/
|
||||
export function ZenProvider({ modules = [], children }) {
|
||||
// Register external module pages once, synchronously, before first render.
|
||||
// useState initializer runs exactly once and does not cause a re-render.
|
||||
useState(() => {
|
||||
const registered = useRef(false);
|
||||
|
||||
if (!registered.current) {
|
||||
// Register synchronously on first render so pages are available
|
||||
// before any child component resolves a module route.
|
||||
if (modules.length > 0) {
|
||||
registerExternalModulePages(modules);
|
||||
}
|
||||
});
|
||||
registered.current = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
|
||||
@@ -37,7 +37,7 @@ export {
|
||||
getEnabledModules,
|
||||
|
||||
// Module-specific getters
|
||||
getModuleMetadata,
|
||||
getModuleMetadataGenerator,
|
||||
getAllModuleMetadata,
|
||||
getModulePublicPages,
|
||||
|
||||
@@ -46,6 +46,9 @@ export {
|
||||
isModuleRegistered,
|
||||
} from '../core/modules/index.js';
|
||||
|
||||
// Module metadata (server-side object getters)
|
||||
export { getModuleMetadata, getMetadataGenerator } from './modules.metadata.js';
|
||||
|
||||
// Client-side module pages registry
|
||||
export { registerExternalModulePages } from './modules.pages.js';
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* const { getInvoiceByToken } = getModuleActions('invoice');
|
||||
*/
|
||||
|
||||
import { getModule } from '../core/modules/registry.js';
|
||||
import { getModule, getEnabledModules } from '../core/modules/registry.js';
|
||||
|
||||
// Static actions for internal modules (add entries here for new internal modules)
|
||||
export const MODULE_ACTIONS = {
|
||||
@@ -43,14 +43,15 @@ export function getModuleDashboardAction(moduleName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dashboard stats from all modules
|
||||
* Get all dashboard stats from all modules (internal static + external runtime).
|
||||
* @returns {Promise<Object>} Object with module names as keys and stats as values
|
||||
*/
|
||||
export async function getAllModuleDashboardStats() {
|
||||
const stats = {};
|
||||
|
||||
// Internal modules — static action map
|
||||
for (const [moduleName, getStats] of Object.entries(MODULE_DASHBOARD_ACTIONS)) {
|
||||
const envKey = `ZEN_MODULE_${moduleName.toUpperCase()}`;
|
||||
const envKey = `ZEN_MODULE_${moduleName.toUpperCase().replace(/-/g, '_')}`;
|
||||
if (process.env[envKey] !== 'true') continue;
|
||||
|
||||
try {
|
||||
@@ -63,6 +64,20 @@ export async function getAllModuleDashboardStats() {
|
||||
}
|
||||
}
|
||||
|
||||
// External modules — runtime registry
|
||||
for (const mod of getEnabledModules()) {
|
||||
if (mod.external && typeof mod.actions?.getDashboardStats === 'function') {
|
||||
try {
|
||||
const result = await mod.actions.getDashboardStats();
|
||||
if (result.success) {
|
||||
stats[mod.name] = result.stats;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting dashboard stats for ${mod.name}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
||||
@@ -148,11 +148,17 @@ export function getAllModuleConfigs() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all dashboard widgets from all modules
|
||||
* Get all dashboard widgets from all modules (internal + external).
|
||||
* @returns {Object} Object with module names as keys and arrays of lazy widgets
|
||||
*/
|
||||
export function getModuleDashboardWidgets() {
|
||||
return MODULE_DASHBOARD_WIDGETS;
|
||||
const widgets = { ...MODULE_DASHBOARD_WIDGETS };
|
||||
for (const [name, config] of EXTERNAL_MODULE_CONFIGS) {
|
||||
if (config.dashboardWidgets?.length) {
|
||||
widgets[name] = config.dashboardWidgets;
|
||||
}
|
||||
}
|
||||
return widgets;
|
||||
}
|
||||
|
||||
// Legacy export for backward compatibility
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
/**
|
||||
* Available Modules Registry
|
||||
*
|
||||
* Add your module name here to enable discovery.
|
||||
* See MODULES.md for the full module creation guide.
|
||||
* Add the module name here to make it discoverable by the server.
|
||||
* See docs/modules/INTERNAL_MODULE.md for the full creation guide.
|
||||
*
|
||||
* Files to update when adding a module:
|
||||
* 1. modules.registry.js → Add to AVAILABLE_MODULES
|
||||
* 2. modules.pages.js → Import config, add to MODULE_CONFIGS
|
||||
* 3. modules.actions.js → Import actions (if public pages)
|
||||
* 4. modules.metadata.js → Import metadata (if SEO needed)
|
||||
* 5. modules/init.js → Import createTables for database CLI
|
||||
* Required steps when adding an internal module:
|
||||
* 1. modules.registry.js → Add name to AVAILABLE_MODULES (this file)
|
||||
* 2. modules.pages.js → Import module.config.js, add to MODULE_CONFIGS
|
||||
*
|
||||
* Optional steps (only when the capability is needed):
|
||||
* 3. modules.actions.js → Add to MODULE_ACTIONS (public page server actions)
|
||||
* 4. modules.metadata.js → Add to MODULE_METADATA (Next.js SEO generators)
|
||||
*/
|
||||
export const AVAILABLE_MODULES = [
|
||||
'posts',
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Posts Module Configuration
|
||||
* Navigation and adminPages are generated dynamically from ZEN_MODULE_ZEN_MODULE_POSTS_TYPES env var.
|
||||
* Navigation and adminPages are generated dynamically from ZEN_MODULE_POSTS_TYPES env var.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import { defineModule } from '../../core/modules/defineModule.js';
|
||||
import { getPostsConfig } from './config.js';
|
||||
|
||||
// Lazy components — shared across all post types
|
||||
@@ -73,7 +74,7 @@ function pageResolver(path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default {
|
||||
export default defineModule({
|
||||
name: 'posts',
|
||||
displayName: 'Posts',
|
||||
version: '1.0.0',
|
||||
@@ -81,7 +82,7 @@ export default {
|
||||
|
||||
dependencies: [],
|
||||
|
||||
envVars: ['ZEN_MODULE_ZEN_MODULE_POSTS_TYPES'],
|
||||
envVars: ['ZEN_MODULE_POSTS_TYPES'],
|
||||
|
||||
// Array of sections — one per post type (server-side, env vars available)
|
||||
navigation: navigationSections,
|
||||
@@ -95,4 +96,4 @@ export default {
|
||||
publicPages: {},
|
||||
publicRoutes: [],
|
||||
dashboardWidgets: [],
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user