d9ba777028
Refactor storage access control to use dynamic public prefixes sourced from `getAllStoragePublicPrefixes()` instead of a hardcoded `blog` check. Each module can now declare its own public storage prefixes via `defineModule()` storagePublicPrefixes, making the system extensible without modifying the core handler. Also adds a `posts` path handler requiring admin access for private post types, removes the deprecated `version` API endpoint and its rate-limit exemption, and minor whitespace/comment cleanup.
137 lines
4.2 KiB
JavaScript
137 lines
4.2 KiB
JavaScript
/**
|
|
* 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));
|
|
const isPublic = process.env[`ZEN_MODULE_POSTS_TYPE_${key.toUpperCase()}_PUBLIC`] === 'true';
|
|
|
|
types[key] = {
|
|
key,
|
|
label,
|
|
fields,
|
|
hasCategory,
|
|
hasRelations,
|
|
titleField,
|
|
slugField,
|
|
public: isPublic,
|
|
};
|
|
}
|
|
|
|
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;
|
|
}
|