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 {
|
export default {
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{ path: '/admin/mon-module/list', method: 'GET', handler: handleList, auth: 'admin' },
|
||||||
path: 'mon-module/list',
|
{ path: '/mon-module/list', method: 'GET', handler: handleList, auth: 'public' },
|
||||||
method: 'GET',
|
|
||||||
handler: handleList,
|
|
||||||
requireAuth: true,
|
|
||||||
requireAdmin: false,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Valeurs acceptées pour `auth` : `'admin'` (JWT admin requis), `'user'` (JWT utilisateur requis), `'public'` (aucune auth).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## cron.config.js
|
## cron.config.js
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export {
|
|||||||
getAllCronJobs,
|
getAllCronJobs,
|
||||||
getAllPublicRoutes,
|
getAllPublicRoutes,
|
||||||
getAllDatabaseSchemas,
|
getAllDatabaseSchemas,
|
||||||
getModuleMetadata,
|
getModuleMetadataGenerator,
|
||||||
getAllModuleMetadata,
|
getAllModuleMetadata,
|
||||||
} from './registry.js';
|
} from './registry.js';
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { getAvailableModules } from '../../modules/modules.registry.js';
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isModuleEnabledInEnv(moduleName) {
|
export function isModuleEnabledInEnv(moduleName) {
|
||||||
const envVar = `ZEN_MODULE_${moduleName.toUpperCase()}`;
|
const envVar = `ZEN_MODULE_${moduleName.toUpperCase().replace(/-/g, '_')}`;
|
||||||
return process.env[envVar] === 'true';
|
return process.env[envVar] === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export {
|
|||||||
getAllCronJobs,
|
getAllCronJobs,
|
||||||
getAllPublicRoutes,
|
getAllPublicRoutes,
|
||||||
getAllDatabaseSchemas,
|
getAllDatabaseSchemas,
|
||||||
getModuleMetadata,
|
getModuleMetadataGenerator,
|
||||||
getAllModuleMetadata,
|
getAllModuleMetadata,
|
||||||
getModulePublicPages // returns route metadata only, use modules.pages.js for components
|
getModulePublicPages // returns route metadata only, use modules.pages.js for components
|
||||||
} from './registry.js';
|
} 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} 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
|
* @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);
|
const module = getModule(moduleName);
|
||||||
|
|
||||||
if (module?.enabled && module?.metadata) {
|
if (module?.enabled && module?.metadata) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { ToastProvider, ToastContainer } from '@zen/core/toast';
|
import { ToastProvider, ToastContainer } from '@zen/core/toast';
|
||||||
import { registerExternalModulePages } from '../../modules/modules.pages.js';
|
import { registerExternalModulePages } from '../../modules/modules.pages.js';
|
||||||
|
|
||||||
@@ -15,13 +15,16 @@ import { registerExternalModulePages } from '../../modules/modules.pages.js';
|
|||||||
* @param {ReactNode} props.children
|
* @param {ReactNode} props.children
|
||||||
*/
|
*/
|
||||||
export function ZenProvider({ modules = [], children }) {
|
export function ZenProvider({ modules = [], children }) {
|
||||||
// Register external module pages once, synchronously, before first render.
|
const registered = useRef(false);
|
||||||
// useState initializer runs exactly once and does not cause a re-render.
|
|
||||||
useState(() => {
|
if (!registered.current) {
|
||||||
|
// Register synchronously on first render so pages are available
|
||||||
|
// before any child component resolves a module route.
|
||||||
if (modules.length > 0) {
|
if (modules.length > 0) {
|
||||||
registerExternalModulePages(modules);
|
registerExternalModulePages(modules);
|
||||||
}
|
}
|
||||||
});
|
registered.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export {
|
|||||||
getEnabledModules,
|
getEnabledModules,
|
||||||
|
|
||||||
// Module-specific getters
|
// Module-specific getters
|
||||||
getModuleMetadata,
|
getModuleMetadataGenerator,
|
||||||
getAllModuleMetadata,
|
getAllModuleMetadata,
|
||||||
getModulePublicPages,
|
getModulePublicPages,
|
||||||
|
|
||||||
@@ -46,6 +46,9 @@ export {
|
|||||||
isModuleRegistered,
|
isModuleRegistered,
|
||||||
} from '../core/modules/index.js';
|
} from '../core/modules/index.js';
|
||||||
|
|
||||||
|
// Module metadata (server-side object getters)
|
||||||
|
export { getModuleMetadata, getMetadataGenerator } from './modules.metadata.js';
|
||||||
|
|
||||||
// Client-side module pages registry
|
// Client-side module pages registry
|
||||||
export { registerExternalModulePages } from './modules.pages.js';
|
export { registerExternalModulePages } from './modules.pages.js';
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
* const { getInvoiceByToken } = getModuleActions('invoice');
|
* 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)
|
// Static actions for internal modules (add entries here for new internal modules)
|
||||||
export const MODULE_ACTIONS = {
|
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
|
* @returns {Promise<Object>} Object with module names as keys and stats as values
|
||||||
*/
|
*/
|
||||||
export async function getAllModuleDashboardStats() {
|
export async function getAllModuleDashboardStats() {
|
||||||
const stats = {};
|
const stats = {};
|
||||||
|
|
||||||
|
// Internal modules — static action map
|
||||||
for (const [moduleName, getStats] of Object.entries(MODULE_DASHBOARD_ACTIONS)) {
|
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;
|
if (process.env[envKey] !== 'true') continue;
|
||||||
|
|
||||||
try {
|
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;
|
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
|
* @returns {Object} Object with module names as keys and arrays of lazy widgets
|
||||||
*/
|
*/
|
||||||
export function getModuleDashboardWidgets() {
|
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
|
// Legacy export for backward compatibility
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
/**
|
/**
|
||||||
* Available Modules Registry
|
* Available Modules Registry
|
||||||
*
|
*
|
||||||
* Add your module name here to enable discovery.
|
* Add the module name here to make it discoverable by the server.
|
||||||
* See MODULES.md for the full module creation guide.
|
* See docs/modules/INTERNAL_MODULE.md for the full creation guide.
|
||||||
*
|
*
|
||||||
* Files to update when adding a module:
|
* Required steps when adding an internal module:
|
||||||
* 1. modules.registry.js → Add to AVAILABLE_MODULES
|
* 1. modules.registry.js → Add name to AVAILABLE_MODULES (this file)
|
||||||
* 2. modules.pages.js → Import config, add to MODULE_CONFIGS
|
* 2. modules.pages.js → Import module.config.js, add to MODULE_CONFIGS
|
||||||
* 3. modules.actions.js → Import actions (if public pages)
|
*
|
||||||
* 4. modules.metadata.js → Import metadata (if SEO needed)
|
* Optional steps (only when the capability is needed):
|
||||||
* 5. modules/init.js → Import createTables for database CLI
|
* 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 = [
|
export const AVAILABLE_MODULES = [
|
||||||
'posts',
|
'posts',
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Posts Module Configuration
|
* 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 { lazy } from 'react';
|
||||||
|
import { defineModule } from '../../core/modules/defineModule.js';
|
||||||
import { getPostsConfig } from './config.js';
|
import { getPostsConfig } from './config.js';
|
||||||
|
|
||||||
// Lazy components — shared across all post types
|
// Lazy components — shared across all post types
|
||||||
@@ -73,7 +74,7 @@ function pageResolver(path) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default defineModule({
|
||||||
name: 'posts',
|
name: 'posts',
|
||||||
displayName: 'Posts',
|
displayName: 'Posts',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -81,7 +82,7 @@ export default {
|
|||||||
|
|
||||||
dependencies: [],
|
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)
|
// Array of sections — one per post type (server-side, env vars available)
|
||||||
navigation: navigationSections,
|
navigation: navigationSections,
|
||||||
@@ -95,4 +96,4 @@ export default {
|
|||||||
publicPages: {},
|
publicPages: {},
|
||||||
publicRoutes: [],
|
publicRoutes: [],
|
||||||
dashboardWidgets: [],
|
dashboardWidgets: [],
|
||||||
};
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user