docs/feat: add storage policies to discovery and refactor utils

- Add `storagePublicPrefixes` and `storageAccessPolicies` fields to
  both internal and external module config loading in discovery.js
- Add a module-level `MIME_TYPES` constant in storage/utils.js to
  avoid recreating the object on every `getMimeType` call
- Remove unused `validateImageDimensions` export from storage/index.js
- Remove dead `isFinite` check after `Math.min/max` in `getPresignedUrl`
  (result is always finite at that point)
- Remove unused `warn` import from storage/utils.js
- Add documentation rule in DEV.md: comments must always reflect the
  actual behavior of the code they describe
This commit is contained in:
2026-04-14 17:23:43 -04:00
parent 2e348a1608
commit 936d21fdec
6 changed files with 66 additions and 125 deletions
+11 -38
View File
@@ -11,40 +11,17 @@
* import { getAllStoragePublicPrefixes, getAllStorageAccessPolicies } from '@zen/core/modules/storage';
*/
import { getModule, getEnabledModules } from '@zen/core/core/modules';
import { getPostsConfig } from './posts/config.js';
import { getEnabledModules } from '@zen/core/core/modules';
/**
* Compute public storage prefixes for the posts module from its type config.
* Avoids importing module.config.js (which contains React lazy() calls).
* @returns {string[]}
*/
function getPostsPublicPrefixes() {
if (process.env.ZEN_MODULE_POSTS !== 'true') return [];
const config = getPostsConfig();
return Object.values(config.types)
.filter(t => t.public)
.map(t => `posts/${t.key}`);
}
/**
* Get all storage public prefixes from every enabled module (internal + external).
* Get all storage public prefixes from every enabled module.
* @returns {string[]} Deduplicated list of public storage prefixes
*/
export function getAllStoragePublicPrefixes() {
const prefixes = new Set();
// Internal modules — call server-only config helpers directly to avoid
// importing module.config.js files that contain React lazy() references.
for (const prefix of getPostsPublicPrefixes()) {
prefixes.add(prefix);
}
// External modules — runtime registry
for (const mod of getEnabledModules()) {
if (!mod.external) continue;
const runtimeConfig = getModule(mod.name);
for (const prefix of runtimeConfig?.storagePublicPrefixes ?? []) {
for (const prefix of mod.storagePublicPrefixes ?? []) {
prefixes.add(prefix);
}
}
@@ -55,8 +32,12 @@ export function getAllStoragePublicPrefixes() {
/**
* Get all storage access policies from every enabled module.
*
* Policies for built-in features (auth, posts) are included directly.
* External modules contribute via their `storageAccessPolicies` defineModule field.
* Built-in: user files at users/{id}/... are always owner-scoped.
* The auth feature is an always-on core feature with no module registration;
* its policy is declared here as the single built-in entry.
*
* Additional policies are contributed by enabled modules via their
* `storageAccessPolicies` defineModule field.
*
* Policy shape: { prefix: string, type: 'owner' | 'admin' }
* 'owner' — pathParts[1] must match session.user.id, or role is 'admin'
@@ -66,20 +47,12 @@ export function getAllStoragePublicPrefixes() {
*/
export function getAllStorageAccessPolicies() {
const policies = [
// Built-in auth feature — user files are owner-scoped
// Built-in: user files are owner-scoped (auth feature, always enabled)
{ prefix: 'users', type: 'owner' },
];
// Posts module — non-public post paths require admin
if (process.env.ZEN_MODULE_POSTS === 'true') {
policies.push({ prefix: 'posts', type: 'admin' });
}
// External modules
for (const mod of getEnabledModules()) {
if (!mod.external) continue;
const runtimeConfig = getModule(mod.name);
for (const policy of runtimeConfig?.storageAccessPolicies ?? []) {
for (const policy of mod.storageAccessPolicies ?? []) {
policies.push(policy);
}
}
+1
View File
@@ -91,6 +91,7 @@ export default defineModule({
envVars: ['ZEN_MODULE_POSTS_TYPES'],
storagePublicPrefixes,
storageAccessPolicies: [{ prefix: 'posts', type: 'admin' }],
// Array of sections — one per post type (server-side, env vars available)
navigation: navigationSections,