import Database from 'bun:sqlite'; import { drizzle } from 'drizzle-orm/bun-sqlite'; import * as schema from './db/schema/index.js'; import { join, dirname } from 'path'; import { existsSync, mkdirSync, appendFile } from 'fs'; import { fileURLToPath } from 'url'; // =================================================================== // Configuration // =================================================================== // ES module equivalent of __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const isDevelopment = process.env.NODE_ENV !== 'production'; // SECURITY: Require JWT_SECRET in production - no fallback for security // This prevents JWT token forgery if environment variable is not set if (!process.env.JWT_SECRET && !isDevelopment) { throw new Error( 'FATAL: JWT_SECRET environment variable must be set in production. ' + 'Generate one with: openssl rand -base64 32' ); } export const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-key-change-in-production'; export const LOG_LEVEL = process.env.LOG_LEVEL || (isDevelopment ? 'debug' : 'info'); // =================================================================== // Logger // =================================================================== const logLevels = { debug: 0, info: 1, warn: 2, error: 3 }; const currentLogLevel = logLevels[LOG_LEVEL] ?? 1; // Log file paths const logsDir = join(__dirname, '../../logs'); const backendLogFile = join(logsDir, 'backend.log'); // Ensure log directory exists if (!existsSync(logsDir)) { mkdirSync(logsDir, { recursive: true }); } function formatLogMessage(level, message, data) { const timestamp = new Date().toISOString(); let logMessage = `[${timestamp}] ${level.toUpperCase()}: ${message}`; if (data && Object.keys(data).length > 0) { logMessage += ' ' + JSON.stringify(data, null, 2); } return logMessage + '\n'; } function log(level, message, data) { if (logLevels[level] < currentLogLevel) return; const logMessage = formatLogMessage(level, message, data); // Append to file asynchronously (fire and forget for performance) appendFile(backendLogFile, logMessage, (err) => { if (err) console.error('Failed to write to log file:', err); }); // Also log to console in development if (isDevelopment) { const timestamp = new Date().toISOString(); const consoleMessage = `[${timestamp}] ${level.toUpperCase()}: ${message}`; if (data && Object.keys(data).length > 0) { console.log(consoleMessage, data); } else { console.log(consoleMessage); } } } export const logger = { debug: (message, data) => log('debug', message, data), info: (message, data) => log('info', message, data), warn: (message, data) => log('warn', message, data), error: (message, data) => log('error', message, data), }; // Frontend logger - writes to separate log file const frontendLogFile = join(logsDir, 'frontend.log'); export function logToFrontend(level, message, data = null, context = {}) { if (logLevels[level] < currentLogLevel) return; const timestamp = new Date().toISOString(); let logMessage = `[${timestamp}] [${context.userAgent || 'unknown'}] [${context.userId || 'anonymous'}] ${level.toUpperCase()}: ${message}`; if (data && Object.keys(data).length > 0) { logMessage += ' ' + JSON.stringify(data, null, 2); } logMessage += '\n'; // Append to frontend log file appendFile(frontendLogFile, logMessage, (err) => { if (err) console.error('Failed to write to frontend log file:', err); }); } export default logger; // =================================================================== // Database // =================================================================== // Get the directory containing this config file, then go to parent for db location const dbPath = join(__dirname, 'award.db'); const sqlite = new Database(dbPath); sqlite.exec('PRAGMA foreign_keys = ON'); export const db = drizzle({ client: sqlite, schema, }); export { sqlite }; export async function closeDatabase() { sqlite.close(); }