feat(storage): refactor storage config and remove module registry
Introduce a dedicated `storage-config.js` for registering public prefixes and access policies via `configureStorageApi()`, replacing the previous `getAllStoragePublicPrefixes` / `getAllStorageAccessPolicies` imports from the module registry. Remove `getAllApiRoutes()` from the router so module-level routes are no longer auto-collected; feature routes must now be registered explicitly via `registerFeatureRoutes()` during `initializeZen()`. Update `.env.example` to document separate `ZEN_STORAGE_PROVIDER`, `ZEN_STORAGE_B2_*` variables for Backblaze B2 alongside the existing Cloudflare R2 variables, making provider selection explicit. Clean up admin navigation and page components to drop module-injected nav entries, keeping only core and system sections.
This commit is contained in:
@@ -1,12 +1,10 @@
|
||||
/**
|
||||
* Admin Server Actions
|
||||
*
|
||||
* These are exported separately from admin/index.js to avoid bundling
|
||||
*
|
||||
* Exported separately from admin/index.js to avoid bundling
|
||||
* server-side code (which includes database imports) into client components.
|
||||
*
|
||||
* Usage:
|
||||
* import { getDashboardStats, getModuleDashboardStats } from '@zen/core/admin/actions';
|
||||
*
|
||||
* Usage: import { getDashboardStats } from '@zen/core/admin/actions';
|
||||
*/
|
||||
|
||||
export { getDashboardStats } from './actions/statsActions.js';
|
||||
export { getAllModuleDashboardStats as getModuleDashboardStats } from '@zen/core/modules/actions';
|
||||
|
||||
@@ -15,20 +15,12 @@
|
||||
* Icons are passed as string names and resolved on the client.
|
||||
*/
|
||||
|
||||
// Import from the main package to use the same registry as discovery
|
||||
import { moduleSystem } from '@zen/core';
|
||||
const { getAllAdminNavigation } = moduleSystem;
|
||||
|
||||
/**
|
||||
* Build complete navigation sections including modules
|
||||
* This should ONLY be called on the server (in page.js)
|
||||
* Build complete navigation sections
|
||||
* @param {string} pathname - Current pathname
|
||||
* @param {Object} enabledModules - Object with module names as keys (for compatibility)
|
||||
* @returns {Array} Complete navigation sections (serializable, icons as strings)
|
||||
*/
|
||||
export function buildNavigationSections(pathname, enabledModules = null) {
|
||||
// Core navigation sections (always available)
|
||||
// Use icon NAMES (strings) for serialization across server/client boundary
|
||||
export function buildNavigationSections(pathname) {
|
||||
const coreNavigation = [
|
||||
{
|
||||
id: 'Dashboard',
|
||||
@@ -45,10 +37,6 @@ export function buildNavigationSections(pathname, enabledModules = null) {
|
||||
}
|
||||
];
|
||||
|
||||
// Get module navigation from registry (only works on server)
|
||||
const moduleNavigation = getAllAdminNavigation(pathname);
|
||||
|
||||
// System navigation (always at the end)
|
||||
const systemNavigation = [
|
||||
{
|
||||
id: 'users',
|
||||
@@ -65,5 +53,5 @@ export function buildNavigationSections(pathname, enabledModules = null) {
|
||||
}
|
||||
];
|
||||
|
||||
return [...coreNavigation, ...moduleNavigation, ...systemNavigation];
|
||||
return [...coreNavigation, ...systemNavigation];
|
||||
}
|
||||
|
||||
+21
-80
@@ -1,134 +1,75 @@
|
||||
/**
|
||||
* Admin Page - Server Component Wrapper for Next.js App Router
|
||||
*
|
||||
* This is a complete server component that handles all admin routes.
|
||||
* Users can simply re-export this in their app/admin/[...admin]/page.js:
|
||||
*
|
||||
* ```javascript
|
||||
*
|
||||
* Re-export this in your app/admin/[...admin]/page.js:
|
||||
* export { default } from '@zen/core/admin/page';
|
||||
* ```
|
||||
*
|
||||
* This eliminates the need to manually import and pass all actions and props.
|
||||
*/
|
||||
|
||||
import { AdminPagesLayout, AdminPagesClient } from '@zen/core/admin/pages';
|
||||
import { protectAdmin } from '@zen/core/admin';
|
||||
import { buildNavigationSections } from '@zen/core/admin/navigation';
|
||||
import { getDashboardStats, getModuleDashboardStats } from '@zen/core/admin/actions';
|
||||
import { getDashboardStats } from '@zen/core/admin/actions';
|
||||
import { logoutAction } from '@zen/core/auth/actions';
|
||||
import { getAppName, getModulesConfig, getAppConfig, moduleSystem } from '@zen/core';
|
||||
import { getAppName } from '@zen/core';
|
||||
|
||||
const { getAdminPage } = moduleSystem;
|
||||
|
||||
/**
|
||||
* Parse admin route params and build the module path
|
||||
* Handles nested paths like /admin/invoice/clients/edit/123
|
||||
*
|
||||
* @param {Object} params - Next.js route params
|
||||
* @returns {Object} Parsed info with path, action, and id
|
||||
*/
|
||||
function parseAdminRoute(params) {
|
||||
const parts = params?.admin || [];
|
||||
|
||||
|
||||
if (parts.length === 0) {
|
||||
return { path: '/admin/dashboard', action: null, id: null, isCorePage: true };
|
||||
return { path: '/admin/dashboard', action: null, id: null };
|
||||
}
|
||||
|
||||
// Check for core pages first
|
||||
|
||||
const corePages = ['dashboard', 'users', 'profile'];
|
||||
if (corePages.includes(parts[0])) {
|
||||
// Users: support /admin/users/edit/:id
|
||||
if (parts[0] === 'users' && parts[1] === 'edit' && parts[2]) {
|
||||
return { path: '/admin/users', action: 'edit', id: parts[2], isCorePage: true };
|
||||
return { path: '/admin/users', action: 'edit', id: parts[2] };
|
||||
}
|
||||
return { path: `/admin/${parts[0]}`, action: null, id: null, isCorePage: true };
|
||||
return { path: `/admin/${parts[0]}`, action: null, id: null };
|
||||
}
|
||||
|
||||
// Build module path
|
||||
// Look for 'new', 'create', or 'edit' to determine action
|
||||
const actionKeywords = ['new', 'create', 'edit'];
|
||||
|
||||
let pathParts = [];
|
||||
let action = null;
|
||||
let id = null;
|
||||
|
||||
const actionKeywords = ['new', 'create', 'edit'];
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
|
||||
if (actionKeywords.includes(part)) {
|
||||
action = part === 'create' ? 'new' : part;
|
||||
// If it's 'edit', the next part is the ID
|
||||
if (action === 'edit' && i + 1 < parts.length) {
|
||||
id = parts[i + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
pathParts.push(part);
|
||||
}
|
||||
|
||||
// Build the full path
|
||||
let fullPath = '/admin/' + pathParts.join('/');
|
||||
if (action) {
|
||||
fullPath += '/' + action;
|
||||
}
|
||||
|
||||
return { path: fullPath, action, id, isCorePage: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is a module page
|
||||
* @param {string} fullPath - Full admin path
|
||||
* @returns {Object|null} Module info if it's a module page, null otherwise
|
||||
*/
|
||||
function getModulePageInfo(fullPath) {
|
||||
const modulePage = getAdminPage(fullPath);
|
||||
if (modulePage) {
|
||||
return {
|
||||
module: modulePage.module,
|
||||
path: fullPath
|
||||
};
|
||||
}
|
||||
return null;
|
||||
return { path: '/admin/' + pathParts.join('/') + (action ? '/' + action : ''), action, id };
|
||||
}
|
||||
|
||||
export default async function AdminPage({ params }) {
|
||||
const resolvedParams = await params;
|
||||
const session = await protectAdmin();
|
||||
const appName = getAppName();
|
||||
const enabledModules = getModulesConfig();
|
||||
const config = getAppConfig();
|
||||
|
||||
|
||||
const statsResult = await getDashboardStats();
|
||||
const dashboardStats = statsResult.success ? statsResult.stats : null;
|
||||
|
||||
// Fetch module dashboard stats for widgets
|
||||
const moduleStats = await getModuleDashboardStats();
|
||||
|
||||
// Build navigation on server where module registry is available
|
||||
const navigationSections = buildNavigationSections('/', enabledModules);
|
||||
|
||||
// Parse route and build path
|
||||
const { path, action, id, isCorePage } = parseAdminRoute(resolvedParams);
|
||||
|
||||
// Check if this is a module page (just check existence, don't load)
|
||||
const modulePageInfo = isCorePage ? null : getModulePageInfo(path);
|
||||
|
||||
|
||||
const navigationSections = buildNavigationSections('/');
|
||||
const { path, action, id } = parseAdminRoute(resolvedParams);
|
||||
|
||||
return (
|
||||
<AdminPagesLayout
|
||||
user={session.user}
|
||||
onLogout={logoutAction}
|
||||
<AdminPagesLayout
|
||||
user={session.user}
|
||||
onLogout={logoutAction}
|
||||
appName={appName}
|
||||
enabledModules={enabledModules}
|
||||
navigationSections={navigationSections}
|
||||
>
|
||||
<AdminPagesClient
|
||||
params={resolvedParams}
|
||||
user={session.user}
|
||||
dashboardStats={dashboardStats}
|
||||
moduleStats={moduleStats}
|
||||
modulePageInfo={modulePageInfo}
|
||||
routeInfo={{ path, action, id }}
|
||||
enabledModules={enabledModules}
|
||||
/>
|
||||
</AdminPagesLayout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user