refactor: replace console.log with structured logger calls
Replace raw `console.log`/`console.error` calls across CLI, API handlers, and module files with structured logger functions (`step`, `done`, `warn`, `fail`) from the shared logger library. This improves log consistency, readability, and makes it easier to control output formatting and log levels from a single place.
This commit is contained in:
+13
-12
@@ -18,6 +18,7 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
||||
import { initDatabase, dropAuthTables, testConnection, closePool } from '../core/database/index.js';
|
||||
import readline from 'readline';
|
||||
import { step, done, warn, fail } from '../shared/lib/logger.js';
|
||||
|
||||
async function runCLI() {
|
||||
const command = process.argv[2];
|
||||
@@ -44,25 +45,25 @@ Example:
|
||||
try {
|
||||
switch (command) {
|
||||
case 'init':
|
||||
console.log('🔧 Initializing database...\n');
|
||||
step('Initializing database...');
|
||||
const result = await initDatabase();
|
||||
console.log(`\n✅ Success! Created ${result.created.length} tables, skipped ${result.skipped.length} existing tables.`);
|
||||
done(`Created ${result.created.length} tables, skipped ${result.skipped.length} existing tables`);
|
||||
break;
|
||||
|
||||
case 'test':
|
||||
console.log('🔌 Testing database connection...\n');
|
||||
step('Testing database connection...');
|
||||
const isConnected = await testConnection();
|
||||
if (isConnected) {
|
||||
console.log('✅ Database connection successful!');
|
||||
done('Database connection successful');
|
||||
} else {
|
||||
console.log('❌ Database connection failed!');
|
||||
fail('Database connection failed');
|
||||
process.exit(1);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'drop':
|
||||
console.log('⚠️ WARNING: This will delete all authentication tables!\n');
|
||||
console.log('Type "yes" to confirm or Ctrl+C to cancel...');
|
||||
warn('This will delete all authentication tables!');
|
||||
process.stdout.write(' Type "yes" to confirm or Ctrl+C to cancel...\n');
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -72,9 +73,9 @@ Example:
|
||||
rl.question('Confirm (yes/no): ', async (answer) => {
|
||||
if (answer.toLowerCase() === 'yes') {
|
||||
await dropAuthTables();
|
||||
console.log('✅ Tables dropped successfully.');
|
||||
done('Tables dropped successfully');
|
||||
} else {
|
||||
console.log('❌ Operation cancelled.');
|
||||
warn('Operation cancelled');
|
||||
}
|
||||
rl.close();
|
||||
process.exit(0);
|
||||
@@ -97,8 +98,8 @@ Usage:
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`❌ Unknown command: ${command}`);
|
||||
console.log('Run "npx zen-db help" for usage information.');
|
||||
fail(`Unknown command: ${command}`);
|
||||
process.stdout.write(' Run "npx zen-db help" for usage information.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ Usage:
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
fail(`Error: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { cookies } from 'next/headers';
|
||||
import { getSessionCookieName } from '../../../shared/lib/appConfig.js';
|
||||
import { getAllStoragePublicPrefixes } from '@zen/core/modules/storage';
|
||||
import { getFile } from '@zen/core/storage';
|
||||
import { fail } from '../../../shared/lib/logger.js';
|
||||
|
||||
// Get cookie name from environment or use default
|
||||
const COOKIE_NAME = getSessionCookieName();
|
||||
@@ -151,7 +152,7 @@ export async function handleGetFile(_request, fileKey) {
|
||||
};
|
||||
} catch (error) {
|
||||
// Log full error server-side; never surface internal details to the client.
|
||||
console.error('[ZEN STORAGE] Error serving file:', error);
|
||||
fail(`Storage: error serving file: ${error.message}`);
|
||||
return {
|
||||
error: 'Internal Server Error',
|
||||
message: 'Failed to retrieve file'
|
||||
|
||||
@@ -9,6 +9,7 @@ import { query, updateById } from '@zen/core/database';
|
||||
import { getSessionCookieName } from '../../../shared/lib/appConfig.js';
|
||||
import { updateUser } from '../../../features/auth/lib/auth.js';
|
||||
import { uploadImage, deleteFile, generateUniqueFilename, generateUserFilePath, getFileExtension, FILE_TYPE_PRESETS, FILE_SIZE_LIMITS, validateUpload } from '@zen/core/storage';
|
||||
import { fail, info } from '../../../shared/lib/logger.js';
|
||||
|
||||
// Get cookie name from environment or use default
|
||||
const COOKIE_NAME = getSessionCookieName();
|
||||
@@ -36,7 +37,7 @@ const ALLOWED_IMAGE_MIME_TYPES = new Set([
|
||||
* @param {string} fallback - The safe message to surface to the client
|
||||
*/
|
||||
function logAndObscureError(error, fallback) {
|
||||
console.error('[ZEN] Internal handler error:', error);
|
||||
fail(`Internal handler error: ${error.message}`);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@@ -468,7 +469,7 @@ export async function handleUploadProfilePicture(request) {
|
||||
try {
|
||||
await deleteFile(key);
|
||||
} catch (rollbackError) {
|
||||
console.error('[ZEN] Rollback delete of newly uploaded object failed:', rollbackError);
|
||||
fail(`Rollback delete of newly uploaded object failed: ${rollbackError.message}`);
|
||||
}
|
||||
throw dbError;
|
||||
}
|
||||
@@ -477,10 +478,10 @@ export async function handleUploadProfilePicture(request) {
|
||||
if (oldImageKey) {
|
||||
try {
|
||||
await deleteFile(oldImageKey);
|
||||
console.log(`[ZEN] Deleted old profile picture: ${oldImageKey}`);
|
||||
info(`Deleted old profile picture: ${oldImageKey}`);
|
||||
} catch (deleteError) {
|
||||
// Non-fatal: log for operator cleanup; the DB is consistent.
|
||||
console.error('[ZEN] Failed to delete old profile picture (orphaned object):', deleteError);
|
||||
fail(`Failed to delete old profile picture (orphaned object): ${deleteError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,10 +569,10 @@ export async function handleDeleteProfilePicture(request) {
|
||||
if (imageKey) {
|
||||
try {
|
||||
await deleteFile(imageKey);
|
||||
console.log(`[ZEN] Deleted profile picture: ${imageKey}`);
|
||||
info(`Deleted profile picture: ${imageKey}`);
|
||||
} catch (deleteError) {
|
||||
// Log error but don't fail the update
|
||||
console.error('[ZEN] Failed to delete profile picture from storage:', deleteError);
|
||||
fail(`Failed to delete profile picture from storage: ${deleteError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { routeRequest, getStatusCode } from './router.js';
|
||||
import { fail } from '../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Handle GET requests
|
||||
@@ -58,7 +59,7 @@ export async function GET(request, { params }) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
fail(`API error: ${error.message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
@@ -86,7 +87,7 @@ export async function POST(request, { params }) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
fail(`API error: ${error.message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
@@ -114,7 +115,7 @@ export async function PUT(request, { params }) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
fail(`API error: ${error.message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
@@ -142,7 +143,7 @@ export async function DELETE(request, { params }) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
fail(`API error: ${error.message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
@@ -170,7 +171,7 @@ export async function PATCH(request, { params }) {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
fail(`API error: ${error.message}`);
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { cookies } from 'next/headers';
|
||||
import { getSessionCookieName } from '../../shared/lib/appConfig.js';
|
||||
import { getAllApiRoutes } from '../modules/index.js';
|
||||
import { checkRateLimit, getIpFromRequest, formatRetryAfter } from '../../features/auth/lib/rateLimit.js';
|
||||
import { fail } from '../../shared/lib/logger.js';
|
||||
|
||||
// Core handlers
|
||||
import { handleHealth } from './handlers/health.js';
|
||||
@@ -64,7 +65,7 @@ function passesCsrfCheck(request) {
|
||||
|
||||
const appUrl = resolveAppUrl();
|
||||
if (!appUrl) {
|
||||
console.error('[ZEN CSRF] No app URL configured (NEXT_PUBLIC_URL_DEV / NEXT_PUBLIC_URL) — CSRF check denied.');
|
||||
fail('CSRF: no app URL configured (NEXT_PUBLIC_URL_DEV / NEXT_PUBLIC_URL) — request denied');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ function passesCsrfCheck(request) {
|
||||
try {
|
||||
expectedOrigin = new URL(appUrl).origin;
|
||||
} catch {
|
||||
console.error('[ZEN CSRF] Configured app URL is not valid:', appUrl);
|
||||
fail(`CSRF: configured app URL is not valid: ${appUrl}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -295,7 +296,7 @@ async function routeModuleRequest(request, path, method) {
|
||||
// or internal hostnames. Log the full error server-side only.
|
||||
const SAFE_AUTH_MESSAGES = new Set(['Unauthorized', 'Admin access required']);
|
||||
if (!SAFE_AUTH_MESSAGES.has(err.message)) {
|
||||
console.error('[ZEN] Module route handler error:', err);
|
||||
fail(`Module route handler error: ${err.message}`);
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
|
||||
+10
-10
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import cron from 'node-cron';
|
||||
import { done, fail, info } from '../../shared/lib/logger.js';
|
||||
|
||||
// Store for all scheduled cron jobs
|
||||
const CRON_JOBS_KEY = Symbol.for('__ZEN_CRON_JOBS__');
|
||||
@@ -47,19 +48,18 @@ export function schedule(name, cronSchedule, handler, options = {}) {
|
||||
// Stop existing job with same name
|
||||
if (jobs.has(name)) {
|
||||
jobs.get(name).stop();
|
||||
console.log(`[Cron] Stopped existing job: ${name}`);
|
||||
info(`Cron replaced: ${name}`);
|
||||
}
|
||||
|
||||
const timezone = options.timezone || process.env.ZEN_TIMEZONE || 'America/Toronto';
|
||||
|
||||
const job = cron.schedule(cronSchedule, async () => {
|
||||
console.log(`[Cron: ${name}] Running at:`, new Date().toISOString());
|
||||
|
||||
info(`Cron ${name} running at ${new Date().toISOString()}`);
|
||||
try {
|
||||
await handler();
|
||||
console.log(`[Cron: ${name}] Completed`);
|
||||
info(`Cron ${name} completed`);
|
||||
} catch (error) {
|
||||
console.error(`[Cron: ${name}] Error:`, error);
|
||||
fail(`Cron ${name}: ${error.message}`);
|
||||
}
|
||||
}, {
|
||||
scheduled: true,
|
||||
@@ -68,7 +68,7 @@ export function schedule(name, cronSchedule, handler, options = {}) {
|
||||
});
|
||||
|
||||
jobs.set(name, job);
|
||||
console.log(`[Cron] Scheduled job: ${name} (${cronSchedule})`);
|
||||
done(`Cron scheduled: ${name} (${cronSchedule})`);
|
||||
|
||||
return job;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ export function stop(name) {
|
||||
if (jobs.has(name)) {
|
||||
jobs.get(name).stop();
|
||||
jobs.delete(name);
|
||||
console.log(`[Cron] Stopped job: ${name}`);
|
||||
info(`Cron stopped: ${name}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -99,11 +99,11 @@ export function stopAll() {
|
||||
|
||||
for (const [name, job] of jobs.entries()) {
|
||||
job.stop();
|
||||
console.log(`[Cron] Stopped job: ${name}`);
|
||||
info(`Cron stopped: ${name}`);
|
||||
}
|
||||
|
||||
jobs.clear();
|
||||
console.log('[Cron] All jobs stopped');
|
||||
done('All cron jobs stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,7 @@ export async function trigger(name) {
|
||||
throw new Error(`Cron job '${name}' not found`);
|
||||
}
|
||||
|
||||
console.log(`[Cron] Manual trigger for: ${name}`);
|
||||
info(`Cron manual trigger: ${name}`);
|
||||
// Note: node-cron doesn't expose the handler directly,
|
||||
// so modules should keep their handler function accessible
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import pkg from 'pg';
|
||||
const { Pool } = pkg;
|
||||
import { fail } from '../../shared/lib/logger.js';
|
||||
|
||||
let pool = null;
|
||||
|
||||
@@ -44,7 +45,7 @@ function getPool() {
|
||||
|
||||
// Handle pool errors
|
||||
pool.on('error', (err) => {
|
||||
console.error('Unexpected error on idle client', err);
|
||||
fail(`DB idle client error: ${err.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ async function query(sql, params = []) {
|
||||
const result = await client.query(sql, params);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Database query error:', error);
|
||||
fail(`DB query error: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@ async function transaction(callback) {
|
||||
return result;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('Transaction error:', error);
|
||||
fail(`DB transaction error: ${error.message}`);
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
@@ -133,7 +134,7 @@ async function testConnection() {
|
||||
await query('SELECT NOW()');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Database connection test failed:', error);
|
||||
fail(`DB connection test failed: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { query } from './db.js';
|
||||
import { step, done, warn, fail, info } from '../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Check if a table exists in the database
|
||||
@@ -103,10 +104,10 @@ async function createAuthTables() {
|
||||
if (!exists) {
|
||||
await query(table.sql);
|
||||
created.push(table.name);
|
||||
console.log(`✓ Created table: ${table.name}`);
|
||||
done(`Created table: ${table.name}`);
|
||||
} else {
|
||||
skipped.push(table.name);
|
||||
console.log(`- Table already exists: ${table.name}`);
|
||||
info(`Table already exists: ${table.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ async function createAuthTables() {
|
||||
* @returns {Promise<Object>} Result object with created and skipped tables
|
||||
*/
|
||||
async function initDatabase() {
|
||||
console.log('Initializing Zen database...');
|
||||
step('Initializing Zen database...');
|
||||
|
||||
try {
|
||||
const authResult = await createAuthTables();
|
||||
@@ -134,13 +135,10 @@ async function initDatabase() {
|
||||
modulesResult = await initModules();
|
||||
} catch (error) {
|
||||
// Modules might not be available or enabled
|
||||
console.log('\nNo modules to initialize or modules not available.');
|
||||
info('No modules to initialize or modules not available');
|
||||
}
|
||||
|
||||
console.log('\nDatabase initialization completed!');
|
||||
console.log(`Auth tables created: ${authResult.created.length}`);
|
||||
console.log(`Module tables created: ${modulesResult.created.length}`);
|
||||
console.log(`Total tables skipped: ${authResult.skipped.length + modulesResult.skipped.length}`);
|
||||
done(`DB ready — auth: ${authResult.created.length} created, modules: ${modulesResult.created.length} created, ${authResult.skipped.length + modulesResult.skipped.length} skipped`);
|
||||
|
||||
return {
|
||||
created: [...authResult.created, ...modulesResult.created],
|
||||
@@ -148,7 +146,7 @@ async function initDatabase() {
|
||||
success: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Database initialization failed:', error);
|
||||
fail(`DB initialization failed: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -165,17 +163,17 @@ async function dropAuthTables() {
|
||||
'zen_auth_users'
|
||||
];
|
||||
|
||||
console.log('WARNING: Dropping all Zen authentication tables...');
|
||||
warn('Dropping all Zen authentication tables...');
|
||||
|
||||
for (const tableName of tables) {
|
||||
const exists = await tableExists(tableName);
|
||||
if (exists) {
|
||||
await query(`DROP TABLE IF EXISTS "${tableName}" CASCADE`);
|
||||
console.log(`✓ Dropped table: ${tableName}`);
|
||||
done(`Dropped table: ${tableName}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('All authentication tables dropped.');
|
||||
done('All authentication tables dropped');
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Resend } from 'resend';
|
||||
import { done, fail, info } from '../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Initialize Resend client
|
||||
@@ -74,7 +75,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att
|
||||
|
||||
// Resend returns { data: { id: "..." }, error: null } or { data: null, error: { message: "..." } }
|
||||
if (response.error) {
|
||||
console.error('[ZEN EMAIL] Resend error:', response.error);
|
||||
fail(`Email Resend error: ${response.error.message || response.error}`);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
@@ -83,7 +84,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att
|
||||
}
|
||||
|
||||
const emailId = response.data?.id || response.id;
|
||||
console.log(`[ZEN EMAIL] Email sent to ${to} - ID: ${emailId}`);
|
||||
info(`Email sent to ${to} — ID: ${emailId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -91,7 +92,7 @@ async function sendEmail({ to, subject, html, text, from, fromName, replyTo, att
|
||||
error: null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ZEN EMAIL] Error sending email:', error);
|
||||
fail(`Email send failed: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
@@ -176,7 +177,7 @@ async function sendBatchEmails(emails) {
|
||||
|
||||
// Handle Resend error response
|
||||
if (response.error) {
|
||||
console.error('[ZEN EMAIL] Resend batch error:', response.error);
|
||||
fail(`Email batch Resend error: ${response.error.message || response.error}`);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
@@ -184,7 +185,7 @@ async function sendBatchEmails(emails) {
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[ZEN EMAIL] Batch of ${emails.length} emails sent`);
|
||||
done(`Email batch of ${emails.length} sent`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -192,7 +193,7 @@ async function sendBatchEmails(emails) {
|
||||
error: null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ZEN EMAIL] Error sending batch emails:', error);
|
||||
fail(`Email batch send failed: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { registerModule, clearRegistry } from './registry.js';
|
||||
import { getAvailableModules } from '../../modules/modules.registry.js';
|
||||
import { step, done, warn, fail, info } from '../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Check if a module is enabled via environment variable
|
||||
@@ -29,15 +30,15 @@ export async function discoverModules(options = {}) {
|
||||
const DISCOVERY_KEY = Symbol.for('__ZEN_MODULES_DISCOVERED__');
|
||||
|
||||
if (globalThis[DISCOVERY_KEY] && !force) {
|
||||
console.log('[Module Discovery] Already discovered, skipping...');
|
||||
warn('modules already discovered, skipping');
|
||||
return { alreadyDiscovered: true };
|
||||
}
|
||||
|
||||
|
||||
if (force) {
|
||||
clearRegistry();
|
||||
}
|
||||
|
||||
console.log('[Module Discovery] Starting module discovery...');
|
||||
|
||||
step('Discovering modules...');
|
||||
|
||||
const discovered = [];
|
||||
const enabled = [];
|
||||
@@ -52,7 +53,6 @@ export async function discoverModules(options = {}) {
|
||||
|
||||
if (!isEnabled) {
|
||||
skipped.push(moduleName);
|
||||
console.log(`[Module Discovery] Skipped ${moduleName} (not enabled)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -72,18 +72,16 @@ export async function discoverModules(options = {}) {
|
||||
|
||||
discovered.push(moduleName);
|
||||
enabled.push(moduleName);
|
||||
console.log(`[Module Discovery] Registered ${moduleName}`);
|
||||
info(`Registered ${moduleName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push({ module: moduleName, error: error.message });
|
||||
console.error(`[Module Discovery] Error loading ${moduleName}:`, error);
|
||||
fail(`Error loading ${moduleName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
globalThis[DISCOVERY_KEY] = true;
|
||||
|
||||
console.log(`[Module Discovery] Complete. Enabled: ${enabled.length}, Skipped: ${skipped.length}, Errors: ${errors.length}`);
|
||||
|
||||
return { discovered, enabled, skipped, errors };
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ async function loadModuleConfig(moduleName) {
|
||||
: undefined,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(`[Module Discovery] No module.config.js for ${moduleName}, using defaults`);
|
||||
// No module.config.js — use defaults silently
|
||||
return {
|
||||
name: moduleName,
|
||||
displayName: moduleName.charAt(0).toUpperCase() + moduleName.slice(1),
|
||||
@@ -186,7 +184,6 @@ export async function registerExternalModules(modules = []) {
|
||||
try {
|
||||
if (!isModuleEnabledInEnv(moduleName)) {
|
||||
skipped.push(moduleName);
|
||||
console.log(`[External Modules] Skipped ${moduleName} (not enabled)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -226,19 +223,13 @@ export async function registerExternalModules(modules = []) {
|
||||
}
|
||||
|
||||
registered.push(moduleName);
|
||||
console.log(`[External Modules] Registered ${moduleName}`);
|
||||
info(`Registered external ${moduleName}`);
|
||||
} catch (error) {
|
||||
errors.push({ module: moduleName, error: error.message });
|
||||
console.error(`[External Modules] Error registering ${moduleName}:`, error);
|
||||
fail(`Error registering external ${moduleName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (registered.length > 0 || skipped.length > 0) {
|
||||
console.log(
|
||||
`[External Modules] Done. Registered: ${registered.length}, Skipped: ${skipped.length}, Errors: ${errors.length}`
|
||||
);
|
||||
}
|
||||
|
||||
return { registered, skipped, errors };
|
||||
}
|
||||
|
||||
@@ -300,10 +291,7 @@ async function buildModuleContext(allowedKeys = []) {
|
||||
*/
|
||||
get: (key) => {
|
||||
if (!allowedSet.has(key)) {
|
||||
console.error(
|
||||
`[ZEN Module Security] Module attempted to read undeclared env var "${key}". ` +
|
||||
'Access denied. Declare all required env vars in the module envVars list.'
|
||||
);
|
||||
fail(`[Security] Module attempted to read undeclared env var "${key}" — access denied`);
|
||||
return undefined;
|
||||
}
|
||||
return process.env[key];
|
||||
|
||||
+18
-18
@@ -11,6 +11,7 @@ import {
|
||||
getAllDatabaseSchemas,
|
||||
isModuleEnabled
|
||||
} from './registry.js';
|
||||
import { step, done, warn, fail, info } from '../../shared/lib/logger.js';
|
||||
|
||||
// Use globalThis to track initialization state
|
||||
const INIT_KEY = Symbol.for('__ZEN_MODULES_INITIALIZED__');
|
||||
@@ -30,11 +31,11 @@ export async function initializeModules(options = {}) {
|
||||
|
||||
// Prevent multiple initializations
|
||||
if (globalThis[INIT_KEY] && !force) {
|
||||
console.log('[Module Loader] Already initialized, skipping...');
|
||||
warn('modules already initialized, skipping');
|
||||
return { alreadyInitialized: true };
|
||||
}
|
||||
|
||||
console.log('[Module Loader] Starting module initialization...');
|
||||
|
||||
step('Initializing modules...');
|
||||
|
||||
const result = {
|
||||
discovery: null,
|
||||
@@ -57,10 +58,10 @@ export async function initializeModules(options = {}) {
|
||||
}
|
||||
|
||||
globalThis[INIT_KEY] = true;
|
||||
console.log('[Module Loader] Module initialization complete');
|
||||
|
||||
done('Modules initialized');
|
||||
|
||||
} catch (error) {
|
||||
console.error('[Module Loader] Initialization failed:', error);
|
||||
fail(`Module initialization failed: ${error.message}`);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ export async function initializeModules(options = {}) {
|
||||
* @returns {Promise<Object>} Database initialization result
|
||||
*/
|
||||
export async function initializeModuleDatabases() {
|
||||
console.log('[Module Loader] Initializing module databases...');
|
||||
step('Initializing module databases...');
|
||||
|
||||
const schemas = getAllDatabaseSchemas();
|
||||
const result = {
|
||||
@@ -93,14 +94,14 @@ export async function initializeModuleDatabases() {
|
||||
result.skipped.push(...initResult.skipped);
|
||||
}
|
||||
|
||||
console.log(`[Module Loader] Database initialized for ${schema.module}`);
|
||||
info(`DB ready: ${schema.module}`);
|
||||
}
|
||||
} catch (error) {
|
||||
result.errors.push({
|
||||
module: schema.module,
|
||||
error: error.message
|
||||
});
|
||||
console.error(`[Module Loader] Database init error for ${schema.module}:`, error);
|
||||
fail(`DB init error for ${schema.module}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +113,7 @@ export async function initializeModuleDatabases() {
|
||||
* @returns {Promise<Object>} Cron job start result
|
||||
*/
|
||||
export async function startModuleCronJobs() {
|
||||
console.log('[Module Loader] Starting module cron jobs...');
|
||||
step('Starting cron jobs...');
|
||||
|
||||
// Stop existing cron jobs first
|
||||
stopModuleCronJobs();
|
||||
@@ -135,12 +136,12 @@ export async function startModuleCronJobs() {
|
||||
const cron = (await import('node-cron')).default;
|
||||
|
||||
const cronJob = cron.schedule(job.schedule, async () => {
|
||||
console.log(`[Cron: ${job.name}] Running at:`, new Date().toISOString());
|
||||
info(`Cron ${job.name} running at ${new Date().toISOString()}`);
|
||||
try {
|
||||
await job.handler();
|
||||
console.log(`[Cron: ${job.name}] Completed`);
|
||||
info(`Cron ${job.name} completed`);
|
||||
} catch (error) {
|
||||
console.error(`[Cron: ${job.name}] Error:`, error);
|
||||
fail(`Cron ${job.name}: ${error.message}`);
|
||||
}
|
||||
}, {
|
||||
scheduled: true,
|
||||
@@ -149,8 +150,7 @@ export async function startModuleCronJobs() {
|
||||
|
||||
globalThis[CRON_JOBS_KEY].set(job.name, cronJob);
|
||||
result.started.push(job.name);
|
||||
|
||||
console.log(`[Module Loader] Started cron job: ${job.name} (${job.schedule})`);
|
||||
info(`Cron ready: ${job.name} (${job.schedule})`);
|
||||
}
|
||||
} catch (error) {
|
||||
result.errors.push({
|
||||
@@ -158,7 +158,7 @@ export async function startModuleCronJobs() {
|
||||
module: job.module,
|
||||
error: error.message
|
||||
});
|
||||
console.error(`[Module Loader] Cron job error for ${job.name}:`, error);
|
||||
fail(`Cron error for ${job.name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,9 +173,9 @@ export function stopModuleCronJobs() {
|
||||
for (const [name, job] of globalThis[CRON_JOBS_KEY].entries()) {
|
||||
try {
|
||||
job.stop();
|
||||
console.log(`[Module Loader] Stopped cron job: ${name}`);
|
||||
info(`Cron stopped: ${name}`);
|
||||
} catch (error) {
|
||||
console.error(`[Module Loader] Error stopping cron job ${name}:`, error);
|
||||
fail(`Error stopping cron ${name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
globalThis[CRON_JOBS_KEY].clear();
|
||||
|
||||
+13
-14
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { createHmac, createHash } from 'crypto';
|
||||
import { fail, warn, info } from '../../shared/lib/logger.js';
|
||||
|
||||
// ─── AWS Signature V4 ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -288,7 +289,7 @@ async function uploadFile({ key, body, contentType, metadata = {}, cacheControl
|
||||
|
||||
return { success: true, data: { key, bucket: config.bucket, contentType }, error: null };
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error uploading file:', error);
|
||||
fail(`Storage upload failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -327,7 +328,7 @@ async function deleteFile(key) {
|
||||
|
||||
return { success: true, data: { key }, error: null };
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error deleting file:', error);
|
||||
fail(`Storage delete failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -378,7 +379,7 @@ async function deleteFiles(keys) {
|
||||
|
||||
return { success: true, data: { deleted, errors }, error: null };
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error deleting files:', error);
|
||||
fail(`Storage batch delete failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -419,7 +420,7 @@ async function getFile(key) {
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error getting file:', error);
|
||||
fail(`Storage get file failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -457,7 +458,7 @@ async function getFileMetadata(key) {
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error getting file metadata:', error);
|
||||
fail(`Storage get metadata failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -520,7 +521,7 @@ async function listFiles({ prefix = '', maxKeys = 1000, continuationToken } = {}
|
||||
error: null,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error listing files:', error);
|
||||
fail(`Storage list files failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -550,7 +551,7 @@ async function getPresignedUrl({ key, expiresIn = 3600, operation = 'get' }) {
|
||||
|
||||
return { success: true, data: { key, url, expiresIn: validExpiresIn, operation }, error: null };
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error generating presigned URL:', error);
|
||||
fail(`Storage presigned URL failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -575,12 +576,12 @@ async function copyFile({ sourceKey, destinationKey }) {
|
||||
});
|
||||
|
||||
if (uploadResult.success) {
|
||||
console.log(`[ZEN STORAGE] File copied from ${sourceKey} to ${destinationKey}`);
|
||||
info(`Storage: copied ${sourceKey} → ${destinationKey}`);
|
||||
}
|
||||
|
||||
return uploadResult;
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error copying file:', error);
|
||||
fail(`Storage copy failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
@@ -622,16 +623,14 @@ async function moveFile({ sourceKey, destinationKey }) {
|
||||
|
||||
const deleteResult = await deleteFile(sourceKey);
|
||||
if (!deleteResult.success) {
|
||||
console.warn(
|
||||
`[ZEN STORAGE] File copied to ${destinationKey} but failed to delete source ${sourceKey}`
|
||||
);
|
||||
warn(`Storage: copied to ${destinationKey} but failed to delete source ${sourceKey}`);
|
||||
} else {
|
||||
console.log(`[ZEN STORAGE] File moved from ${sourceKey} to ${destinationKey}`);
|
||||
info(`Storage: moved ${sourceKey} → ${destinationKey}`);
|
||||
}
|
||||
|
||||
return copyResult;
|
||||
} catch (error) {
|
||||
console.error('[ZEN STORAGE] Error moving file:', error);
|
||||
fail(`Storage move failed: ${error.message}`);
|
||||
return { success: false, data: null, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
import { warn } from '../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Generate a unique filename with timestamp and random hash
|
||||
@@ -205,11 +206,7 @@ export async function validateImageDimensions(buffer, constraints = {}) {
|
||||
// Returning valid=false with a clear diagnostic forces callers to either
|
||||
// install 'sharp' (the recommended path) or explicitly handle the
|
||||
// unvalidated case themselves. Never silently approve what cannot be checked.
|
||||
console.warn(
|
||||
'[ZEN STORAGE] validateImageDimensions: image dimension enforcement is not ' +
|
||||
'available. Install the "sharp" package and implement pixel-level validation ' +
|
||||
'before enabling uploads that depend on dimension constraints.'
|
||||
);
|
||||
warn('Storage: validateImageDimensions — dimension enforcement unavailable. Install "sharp" to enable pixel-level validation.');
|
||||
return {
|
||||
valid: false,
|
||||
width: null,
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
'use server';
|
||||
|
||||
import { query } from '@zen/core/database';
|
||||
import { fail } from '../../../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Get total number of users
|
||||
@@ -50,7 +51,7 @@ async function getTotalUsersCount() {
|
||||
);
|
||||
return parseInt(result.rows[0].count) || 0;
|
||||
} catch (error) {
|
||||
console.error('Error getting users count:', error);
|
||||
fail(`Error getting users count: ${error.message}`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +71,7 @@ export async function getDashboardStats() {
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting dashboard stats:', error);
|
||||
fail(`Error getting dashboard stats: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || 'Failed to get dashboard statistics'
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { register, login, requestPasswordReset, resetPassword, verifyUserEmail } from '../lib/auth.js';
|
||||
import { validateSession, deleteSession } from '../lib/session.js';
|
||||
import { verifyEmailToken, verifyResetToken, sendVerificationEmail, sendPasswordResetEmail } from '../lib/email.js';
|
||||
import { fail } from '../../../shared/lib/logger.js';
|
||||
import { cookies, headers } from 'next/headers';
|
||||
import { getSessionCookieName, getPublicBaseUrl } from '../../../shared/lib/appConfig.js';
|
||||
import { checkRateLimit, getIpFromHeaders, formatRetryAfter } from '../lib/rateLimit.js';
|
||||
@@ -228,7 +229,7 @@ export async function getSession() {
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Session validation error:', error);
|
||||
fail(`Auth: session validation error: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { create, findOne, updateById, count } from '../../../core/database/crud.js';
|
||||
import { hashPassword, verifyPassword, generateId } from './password.js';
|
||||
import { createSession } from './session.js';
|
||||
import { fail } from '../../../shared/lib/logger.js';
|
||||
import { createEmailVerification, createPasswordReset, verifyResetToken, deleteResetToken, sendPasswordChangedEmail } from './email.js';
|
||||
|
||||
/**
|
||||
@@ -251,7 +252,7 @@ async function resetPassword(resetData) {
|
||||
await sendPasswordChangedEmail(email);
|
||||
} catch (error) {
|
||||
// Log error but don't fail the password reset process
|
||||
console.error(`[ZEN AUTH] Failed to send password changed email to ${email}:`, error.message);
|
||||
fail(`Auth: failed to send password changed email to ${email}: ${error.message}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import crypto from 'crypto';
|
||||
import { create, findOne, deleteWhere } from '../../../core/database/crud.js';
|
||||
import { generateToken, generateId } from './password.js';
|
||||
import { fail, info } from '../../../shared/lib/logger.js';
|
||||
import { sendAuthEmail } from '../../../core/email/index.js';
|
||||
import { renderVerificationEmail, renderPasswordResetEmail, renderPasswordChangedEmail } from '../../../core/email/templates/index.js';
|
||||
|
||||
@@ -178,11 +179,11 @@ async function sendVerificationEmail(email, token, baseUrl) {
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[ZEN AUTH] Failed to send verification email to ${email}:`, result.error);
|
||||
fail(`Auth: failed to send verification email to ${email}: ${result.error}`);
|
||||
throw new Error('Failed to send verification email');
|
||||
}
|
||||
|
||||
console.log(`[ZEN AUTH] Verification email sent to ${email}`);
|
||||
info(`Auth: verification email sent to ${email}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -205,11 +206,11 @@ async function sendPasswordResetEmail(email, token, baseUrl) {
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[ZEN AUTH] Failed to send password reset email to ${email}:`, result.error);
|
||||
fail(`Auth: failed to send password reset email to ${email}: ${result.error}`);
|
||||
throw new Error('Failed to send password reset email');
|
||||
}
|
||||
|
||||
console.log(`[ZEN AUTH] Password reset email sent to ${email}`);
|
||||
info(`Auth: password reset email sent to ${email}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -229,11 +230,11 @@ async function sendPasswordChangedEmail(email) {
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[ZEN AUTH] Failed to send password changed email to ${email}:`, result.error);
|
||||
fail(`Auth: failed to send password changed email to ${email}: ${result.error}`);
|
||||
throw new Error('Failed to send password changed email');
|
||||
}
|
||||
|
||||
console.log(`[ZEN AUTH] Password changed email sent to ${email}`);
|
||||
info(`Auth: password changed email sent to ${email}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { AVAILABLE_MODULES } from './modules.registry.js';
|
||||
import { step, done, warn, fail, info } from '../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Check if a module is enabled via environment variable
|
||||
@@ -26,16 +27,16 @@ export async function initModules() {
|
||||
const created = [];
|
||||
const skipped = [];
|
||||
|
||||
console.log('\nInitializing module databases...');
|
||||
step('Initializing module databases...');
|
||||
|
||||
for (const moduleName of AVAILABLE_MODULES) {
|
||||
if (!isModuleEnabled(moduleName)) {
|
||||
console.log(`- Skipped ${moduleName} (not enabled)`);
|
||||
info(`Skipped ${moduleName} (not enabled)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`\nInitializing ${moduleName} module tables...`);
|
||||
step(`Initializing ${moduleName}...`);
|
||||
const db = await import(`./${moduleName}/db.js`);
|
||||
|
||||
if (typeof db.createTables === 'function') {
|
||||
@@ -44,15 +45,15 @@ export async function initModules() {
|
||||
if (result?.created) created.push(...result.created);
|
||||
if (result?.skipped) skipped.push(...result.skipped);
|
||||
|
||||
console.log(`✓ ${moduleName} module initialized`);
|
||||
done(`${moduleName} initialized`);
|
||||
} else {
|
||||
console.log(`- ${moduleName} has no createTables function`);
|
||||
info(`${moduleName} has no createTables function`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_MODULE_NOT_FOUND' || error.message?.includes('Cannot find module')) {
|
||||
console.log(`- ${moduleName} has no db.js (skipped)`);
|
||||
info(`${moduleName} has no db.js (skipped)`);
|
||||
} else {
|
||||
console.error(`Error initializing ${moduleName}:`, error.message);
|
||||
fail(`${moduleName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { getModule, getEnabledModules } from '@zen/core/core/modules';
|
||||
import { fail } from '../shared/lib/logger.js';
|
||||
|
||||
// Static actions for internal modules (add entries here for new internal modules)
|
||||
export const MODULE_ACTIONS = {
|
||||
@@ -60,7 +61,7 @@ export async function getAllModuleDashboardStats() {
|
||||
stats[moduleName] = result.stats;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting dashboard stats for ${moduleName}:`, error);
|
||||
fail(`Error getting dashboard stats for ${moduleName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ export async function getAllModuleDashboardStats() {
|
||||
stats[mod.name] = result.stats;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error getting dashboard stats for ${mod.name}:`, error);
|
||||
fail(`Error getting dashboard stats for ${mod.name}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@ import { PublicPagesLayout, PublicPagesClient } from '@zen/core/modules/pages';
|
||||
import { getMetadataGenerator } from '@zen/core/modules/metadata';
|
||||
import { getAppConfig } from '@zen/core';
|
||||
import { getModuleActions } from '@zen/core/modules/actions';
|
||||
import { fail } from '../shared/lib/logger.js';
|
||||
|
||||
/**
|
||||
* Per-module path configuration.
|
||||
@@ -62,7 +63,7 @@ export async function generateMetadata({ params }) {
|
||||
try {
|
||||
return await generator(token);
|
||||
} catch (error) {
|
||||
console.error(`[ZEN] Error generating metadata for ${moduleName}/${metadataType}:`, error);
|
||||
fail(`Error generating metadata for ${moduleName}/${metadataType}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-14
@@ -32,6 +32,7 @@ import {
|
||||
} from '@zen/core/storage';
|
||||
|
||||
import { getPostsConfig, getPostType } from './config.js';
|
||||
import { fail, warn } from '../../shared/lib/logger.js';
|
||||
|
||||
// ============================================================================
|
||||
// Config
|
||||
@@ -91,7 +92,7 @@ async function handleGetPosts(request) {
|
||||
limit: result.pagination.limit
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error GET posts:', error);
|
||||
fail(`Posts: error GET posts: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to fetch posts' };
|
||||
}
|
||||
}
|
||||
@@ -111,7 +112,7 @@ async function handleCreatePost(request) {
|
||||
const post = await createPost(postType, postData);
|
||||
return { success: true, post, message: 'Post created successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error creating post:', error);
|
||||
fail(`Posts: error creating post: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to create post' };
|
||||
}
|
||||
}
|
||||
@@ -148,7 +149,7 @@ async function handleUpdatePost(request) {
|
||||
try {
|
||||
await deleteFile(oldKey);
|
||||
} catch (err) {
|
||||
console.warn(`[Posts] Error deleting old image ${oldKey}:`, err.message);
|
||||
warn(`Posts: error deleting old image ${oldKey}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +157,7 @@ async function handleUpdatePost(request) {
|
||||
|
||||
return { success: true, post, message: 'Post updated successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error updating post:', error);
|
||||
fail(`Posts: error updating post: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to update post' };
|
||||
}
|
||||
}
|
||||
@@ -174,7 +175,7 @@ async function handleDeletePost(request) {
|
||||
if (!deleted) return { success: false, error: 'Post not found' };
|
||||
return { success: true, message: 'Post deleted successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error deleting post:', error);
|
||||
fail(`Posts: error deleting post: ${error.message}`);
|
||||
return { success: false, error: 'Failed to delete post' };
|
||||
}
|
||||
}
|
||||
@@ -221,7 +222,7 @@ async function handleUploadImage(request) {
|
||||
|
||||
return { success: true, key: uploadResult.data.key };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error uploading image:', error);
|
||||
fail(`Posts: error uploading image: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Upload failed' };
|
||||
}
|
||||
}
|
||||
@@ -268,7 +269,7 @@ async function handleGetCategories(request) {
|
||||
limit: result.pagination.limit
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error GET categories:', error);
|
||||
fail(`Posts: error GET categories: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to fetch categories' };
|
||||
}
|
||||
}
|
||||
@@ -288,7 +289,7 @@ async function handleCreateCategory(request) {
|
||||
const category = await createCategory(postType, categoryData);
|
||||
return { success: true, category, message: 'Category created successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error creating category:', error);
|
||||
fail(`Posts: error creating category: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to create category' };
|
||||
}
|
||||
}
|
||||
@@ -314,7 +315,7 @@ async function handleUpdateCategory(request) {
|
||||
const category = await updateCategory(postType, parseInt(id), updates);
|
||||
return { success: true, category, message: 'Category updated successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error updating category:', error);
|
||||
fail(`Posts: error updating category: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to update category' };
|
||||
}
|
||||
}
|
||||
@@ -331,7 +332,7 @@ async function handleDeleteCategory(request) {
|
||||
await deleteCategory(postType, parseInt(id));
|
||||
return { success: true, message: 'Category deleted successfully' };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error deleting category:', error);
|
||||
fail(`Posts: error deleting category: ${error.message}`);
|
||||
if (error.message.includes('Cannot delete')) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
@@ -356,7 +357,7 @@ async function handleSearchPosts(request) {
|
||||
const posts = await searchPosts(postType, q, limit);
|
||||
return { success: true, posts };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error searching posts:', error);
|
||||
fail(`Posts: error searching posts: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to search posts' };
|
||||
}
|
||||
}
|
||||
@@ -406,7 +407,7 @@ async function handlePublicGetPosts(request, params) {
|
||||
limit: result.pagination.limit
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error public GET posts:', error);
|
||||
fail(`Posts: error public GET posts: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to fetch posts' };
|
||||
}
|
||||
}
|
||||
@@ -421,7 +422,7 @@ async function handlePublicGetPostBySlug(request, params) {
|
||||
if (!post) return { success: false, error: 'Post not found' };
|
||||
return { success: true, post };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error public GET post by slug:', error);
|
||||
fail(`Posts: error public GET post by slug: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to fetch post' };
|
||||
}
|
||||
}
|
||||
@@ -434,7 +435,7 @@ async function handlePublicGetCategories(request, params) {
|
||||
const categories = await getActiveCategories(postType);
|
||||
return { success: true, categories };
|
||||
} catch (error) {
|
||||
console.error('[Posts] Error public GET categories:', error);
|
||||
fail(`Posts: error public GET categories: ${error.message}`);
|
||||
return { success: false, error: error.message || 'Failed to fetch categories' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { query } from '@zen/core/database';
|
||||
import { deleteFile } from '@zen/core/storage';
|
||||
import { warn } from '../../shared/lib/logger.js';
|
||||
import { getPostType } from './config.js';
|
||||
|
||||
function slugify(text) {
|
||||
@@ -438,10 +439,10 @@ export async function deletePost(postType, id) {
|
||||
try {
|
||||
const deleteResult = await deleteFile(imageKey);
|
||||
if (!deleteResult.success) {
|
||||
console.warn(`[Posts] Failed to delete image from storage: ${imageKey}`, deleteResult.error);
|
||||
warn(`Posts: failed to delete image from storage: ${imageKey} — ${deleteResult.error}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[Posts] Error deleting image from storage: ${imageKey}`, err.message);
|
||||
warn(`Posts: error deleting image from storage: ${imageKey} — ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-9
@@ -5,6 +5,7 @@
|
||||
|
||||
import { query } from '@zen/core/database';
|
||||
import { getPostsConfig } from './config.js';
|
||||
import { done, info, step } from '../../shared/lib/logger.js';
|
||||
|
||||
async function tableExists(tableName) {
|
||||
const result = await query(
|
||||
@@ -23,7 +24,7 @@ async function createPostsCategoryTable() {
|
||||
const exists = await tableExists(tableName);
|
||||
|
||||
if (exists) {
|
||||
console.log(`- Table already exists: ${tableName}`);
|
||||
info(`Table already exists: ${tableName}`);
|
||||
return { created: false, tableName };
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ async function createPostsCategoryTable() {
|
||||
await query(`CREATE INDEX idx_zen_posts_category_post_type ON zen_posts_category(post_type)`);
|
||||
await query(`CREATE INDEX idx_zen_posts_category_is_active ON zen_posts_category(is_active)`);
|
||||
|
||||
console.log(`✓ Created table: ${tableName}`);
|
||||
done(`Created table: ${tableName}`);
|
||||
return { created: true, tableName };
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ async function createPostsTable() {
|
||||
const exists = await tableExists(tableName);
|
||||
|
||||
if (exists) {
|
||||
console.log(`- Table already exists: ${tableName}`);
|
||||
info(`Table already exists: ${tableName}`);
|
||||
return { created: false, tableName };
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ async function createPostsTable() {
|
||||
await query(`CREATE INDEX idx_zen_posts_category_id ON zen_posts(category_id)`);
|
||||
await query(`CREATE INDEX idx_zen_posts_data_gin ON zen_posts USING GIN (data)`);
|
||||
|
||||
console.log(`✓ Created table: ${tableName}`);
|
||||
done(`Created table: ${tableName}`);
|
||||
return { created: true, tableName };
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ async function createPostsRelationsTable() {
|
||||
const exists = await tableExists(tableName);
|
||||
|
||||
if (exists) {
|
||||
console.log(`- Table already exists: ${tableName}`);
|
||||
info(`Table already exists: ${tableName}`);
|
||||
return { created: false, tableName };
|
||||
}
|
||||
|
||||
@@ -100,7 +101,7 @@ async function createPostsRelationsTable() {
|
||||
await query(`CREATE INDEX idx_zen_posts_relations_post_id ON zen_posts_relations(post_id)`);
|
||||
await query(`CREATE INDEX idx_zen_posts_relations_related ON zen_posts_relations(related_post_id)`);
|
||||
|
||||
console.log(`✓ Created table: ${tableName}`);
|
||||
done(`Created table: ${tableName}`);
|
||||
return { created: true, tableName };
|
||||
}
|
||||
|
||||
@@ -119,18 +120,18 @@ export async function createTables() {
|
||||
|
||||
// zen_posts_category must always be created before zen_posts
|
||||
// because zen_posts has a FK reference to it
|
||||
console.log('\n--- Posts Categories ---');
|
||||
step('Posts Categories');
|
||||
const catResult = await createPostsCategoryTable();
|
||||
if (catResult.created) created.push(catResult.tableName);
|
||||
else skipped.push(catResult.tableName);
|
||||
|
||||
console.log('\n--- Posts ---');
|
||||
step('Posts');
|
||||
const postResult = await createPostsTable();
|
||||
if (postResult.created) created.push(postResult.tableName);
|
||||
else skipped.push(postResult.tableName);
|
||||
|
||||
if (needsRelations) {
|
||||
console.log('\n--- Posts Relations ---');
|
||||
step('Posts Relations');
|
||||
const relResult = await createPostsRelationsTable();
|
||||
if (relResult.created) created.push(relResult.tableName);
|
||||
else skipped.push(relResult.tableName);
|
||||
|
||||
+24
-24
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { discoverModules, registerExternalModules, startModuleCronJobs, stopModuleCronJobs } from '../../core/modules/index.js';
|
||||
import { step, done, warn, fail } from './logger.js';
|
||||
|
||||
// Use globalThis to persist initialization flag across module reloads
|
||||
const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__');
|
||||
@@ -11,10 +12,10 @@ const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__');
|
||||
/**
|
||||
* Initialize ZEN system
|
||||
* Discovers modules dynamically and starts cron jobs
|
||||
*
|
||||
*
|
||||
* Recommended: Use instrumentation.js for automatic initialization
|
||||
* Alternative: Call this function manually in your root layout
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* // instrumentation.js (Recommended) — internal modules only
|
||||
* export async function register() {
|
||||
@@ -42,26 +43,26 @@ const ZEN_INIT_KEY = Symbol.for('__ZEN_INITIALIZED__');
|
||||
*/
|
||||
export async function initializeZen(config = {}) {
|
||||
const { modules: externalModules = [], skipCron = false, skipDb = true } = config;
|
||||
|
||||
|
||||
// Only run on server-side
|
||||
if (typeof window !== 'undefined') {
|
||||
return { skipped: true, reason: 'client-side' };
|
||||
}
|
||||
|
||||
|
||||
// Prevent multiple initializations using globalThis
|
||||
if (globalThis[ZEN_INIT_KEY]) {
|
||||
console.log('⚠ ZEN: Already initialized, skipping...');
|
||||
warn('ZEN: already initialized, skipping');
|
||||
return { skipped: true, reason: 'already-initialized' };
|
||||
}
|
||||
|
||||
|
||||
globalThis[ZEN_INIT_KEY] = true;
|
||||
console.log('🚀 ZEN: Starting initialization...');
|
||||
|
||||
step('ZEN starting...');
|
||||
|
||||
const result = {
|
||||
discovery: null,
|
||||
cron: { started: [], errors: [] }
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
// Step 1: Discover and register internal modules (from modules.registry.js)
|
||||
result.discovery = await discoverModules();
|
||||
@@ -70,10 +71,10 @@ export async function initializeZen(config = {}) {
|
||||
const skippedCount = result.discovery.skipped?.length || 0;
|
||||
|
||||
if (enabledCount > 0) {
|
||||
console.log(`✓ ZEN: Discovered ${enabledCount} internal module(s): ${result.discovery.enabled.join(', ')}`);
|
||||
done(`ZEN: ${enabledCount} module(s): ${result.discovery.enabled.join(', ')}`);
|
||||
}
|
||||
if (skippedCount > 0) {
|
||||
console.log(`⚠ ZEN: Skipped ${skippedCount} disabled module(s): ${result.discovery.skipped.join(', ')}`);
|
||||
warn(`ZEN: skipped ${skippedCount} module(s): ${result.discovery.skipped.join(', ')}`);
|
||||
}
|
||||
|
||||
// Step 2: Register external modules from zen.config.js (if any)
|
||||
@@ -81,26 +82,26 @@ export async function initializeZen(config = {}) {
|
||||
result.external = await registerExternalModules(externalModules);
|
||||
|
||||
if (result.external.registered.length > 0) {
|
||||
console.log(`✓ ZEN: Registered ${result.external.registered.length} external module(s): ${result.external.registered.join(', ')}`);
|
||||
done(`ZEN: ${result.external.registered.length} external module(s): ${result.external.registered.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Step 3: Start cron jobs for all enabled modules (internal + external)
|
||||
if (!skipCron) {
|
||||
result.cron = await startModuleCronJobs();
|
||||
|
||||
|
||||
if (result.cron.started.length > 0) {
|
||||
console.log(`✓ ZEN: Started ${result.cron.started.length} cron job(s): ${result.cron.started.join(', ')}`);
|
||||
done(`ZEN: ${result.cron.started.length} cron job(s): ${result.cron.started.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✓ ZEN: Initialization complete');
|
||||
|
||||
|
||||
done('ZEN: ready');
|
||||
|
||||
} catch (error) {
|
||||
console.error('✗ ZEN: Initialization failed:', error);
|
||||
fail(`ZEN: init failed: ${error.message}`);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -110,14 +111,13 @@ export async function initializeZen(config = {}) {
|
||||
*/
|
||||
export function resetZenInitialization() {
|
||||
globalThis[ZEN_INIT_KEY] = false;
|
||||
|
||||
|
||||
// Stop all cron jobs using the module system
|
||||
try {
|
||||
stopModuleCronJobs();
|
||||
} catch (e) {
|
||||
// Cron system not available
|
||||
}
|
||||
|
||||
console.log('⚠ ZEN: Initialization flag reset');
|
||||
}
|
||||
|
||||
warn('ZEN: initialization reset');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* ZEN Console Logger
|
||||
* Centralized, styled logging for server-side output.
|
||||
* Inspired by the zen-start CLI style.
|
||||
*/
|
||||
|
||||
const c = {
|
||||
reset: '\x1b[0m',
|
||||
dim: '\x1b[2m',
|
||||
bold: '\x1b[1m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
red: '\x1b[31m',
|
||||
cyan: '\x1b[36m',
|
||||
};
|
||||
|
||||
const out = (msg) => process.stdout.write(msg + '\n');
|
||||
|
||||
/** In-progress step — dim ◆ */
|
||||
export const step = (msg) => out(` ${c.dim}◆${c.reset} ${msg}`);
|
||||
/** Success — green ✓ */
|
||||
export const done = (msg) => out(` ${c.green}✓${c.reset} ${msg}`);
|
||||
/** Warning — yellow ⚠ */
|
||||
export const warn = (msg) => out(` ${c.yellow}⚠${c.reset} ${msg}`);
|
||||
/** Error / failure — red ✗ */
|
||||
export const fail = (msg) => out(` ${c.red}✗${c.reset} ${msg}`);
|
||||
/** Sub-item detail — dim · */
|
||||
export const info = (msg) => out(` ${c.dim}·${c.reset} ${msg}`);
|
||||
|
||||
export const logger = { step, done, warn, fail, info };
|
||||
Reference in New Issue
Block a user