/** * Posts Module - Config Parser * Parses ZEN_MODULE_ZEN_MODULE_POSTS_TYPES and ZEN_MODULE_POSTS_TYPE_* environment variables into a structured config. * * .env format: * ZEN_MODULE_ZEN_MODULE_POSTS_TYPES=blogue|cve|emploi * ZEN_MODULE_POSTS_TYPE_BLOGUE=title:title|slug:slug|category:category|date:date|resume:text|content:markdown|image:image * ZEN_MODULE_POSTS_TYPE_CVE=title:title|slug:slug|cve_id:text|severity:text|description:markdown|date:date * * Supported field types: title, slug, text, markdown, date, datetime, color, category, image, relation * * Relation field format: name:relation:target_post_type * e.g. keywords:relation:mots-cle */ const VALID_FIELD_TYPES = ['title', 'slug', 'text', 'markdown', 'date', 'datetime', 'color', 'category', 'image', 'relation']; let _cachedConfig = null; /** * Parse a single type's field string into an array of field definitions. * Format: "name:type" or "name:type:param" (param used for relation target) * e.g. "title:title|slug:slug|date:date|keywords:relation:mots-cle" * -> [{ name: 'title', type: 'title' }, ..., { name: 'keywords', type: 'relation', target: 'mots-cle' }] * @param {string} fieldString * @returns {Array<{name: string, type: string, target?: string}>} */ function parseFields(fieldString) { if (!fieldString) return []; return fieldString .split('|') .map(part => { const segments = part.trim().split(':'); const name = segments[0]; const type = segments[1]; const param = segments[2] || null; if (!name) return null; const resolvedType = VALID_FIELD_TYPES.includes(type) ? type : 'text'; const field = { name: name.trim(), type: resolvedType }; if (resolvedType === 'relation' && param) { field.target = param.trim().toLowerCase(); } return field; }) .filter(Boolean); } /** * Parse all ZEN_MODULE_POSTS_TYPE_* env vars and build the config object. * @returns {Object} Parsed config */ function buildConfig() { const enabled = process.env.ZEN_MODULE_POSTS === 'true'; if (!enabled) { return { enabled: false, types: {} }; } const typesRaw = process.env.ZEN_MODULE_ZEN_MODULE_POSTS_TYPES || ''; // Each entry can be "key" or "key:Label" — only lowercase the key part const typeKeys = typesRaw .split('|') .map(k => { const [key, ...rest] = k.trim().split(':'); const label = rest.join(':'); // preserve colons in label if any return label ? `${key.toLowerCase()}:${label}` : key.toLowerCase(); }) .filter(Boolean); const types = {}; for (const entry of typeKeys) { // Support "key:Label" format (label is optional) const [key, customLabel] = entry.split(':'); const envKey = `ZEN_MODULE_POSTS_TYPE_${key.toUpperCase()}`; const fieldString = process.env[envKey] || ''; const fields = parseFields(fieldString); const titleField = fields.find(f => f.type === 'title')?.name || null; const slugField = fields.find(f => f.type === 'slug')?.name || null; const hasCategory = fields.some(f => f.type === 'category'); const hasRelations = fields.some(f => f.type === 'relation'); const label = customLabel || (key.charAt(0).toUpperCase() + key.slice(1)); types[key] = { key, label, fields, hasCategory, hasRelations, titleField, slugField, }; } return { enabled: true, types }; } /** * Get the parsed posts config (cached after first call). * @returns {{ enabled: boolean, types: Object }} */ export function getPostsConfig() { if (!_cachedConfig) { _cachedConfig = buildConfig(); } return _cachedConfig; } /** * Get a single post type config by key. * @param {string} key - Type key (e.g. 'blogue') * @returns {Object|null} */ export function getPostType(key) { const config = getPostsConfig(); return config.types[key] || null; } /** * Check if the posts module is enabled. * @returns {boolean} */ export function isPostsEnabled() { return process.env.ZEN_MODULE_POSTS === 'true'; } /** * Reset cached config (useful for testing). */ export function resetConfig() { _cachedConfig = null; }