// Database initialization - loaded at runtime after entrypoint runs import path from 'path' import { existsSync, mkdirSync, chmodSync } from 'fs' import sqlite3 from 'better-sqlite3' // --- Logger --- const LOG_LEVELS = { error: 0, warn: 1, info: 2, debug: 3 } const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL?.toLowerCase()] ?? LOG_LEVELS.info const log = { info: (...args) => { if (currentLevel >= LOG_LEVELS.info) console.log(`[${new Date().toISOString()}] INFO `, ...args) }, warn: (...args) => { if (currentLevel >= LOG_LEVELS.warn) console.warn(`[${new Date().toISOString()}] WARN `, ...args) }, error: (...args) => { if (currentLevel >= LOG_LEVELS.error) console.error(`[${new Date().toISOString()}] ERROR`, ...args) }, debug: (...args) => { if (currentLevel >= LOG_LEVELS.debug) console.debug(`[${new Date().toISOString()}] DEBUG`, ...args) }, } const dbPath = path.join(new URL(import.meta.url).pathname, '..', 'db', 'queuenorth.db') const dbDir = path.dirname(dbPath) // Ensure db directory exists with proper permissions if (!existsSync(dbDir)) { mkdirSync(dbDir, { recursive: true }) try { chmodSync(dbDir, 0o777) } catch (e) {} } // Ensure db file has proper permissions if it exists if (existsSync(dbPath)) { try { chmodSync(dbPath, 0o666) } catch (e) {} } // Initialize the database export const db = sqlite3(dbPath) // Initialize schema export const initSchema = () => { // Issue #6: Add UNIQUE constraint on leads.email with migration support const tableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='leads'").get() if (tableExists) { // Check if UNIQUE constraint already exists const pragma = db.prepare("PRAGMA table_info(leads)").all() const emailCol = pragma.find(col => col.name === 'email') if (!emailCol || !emailCol.notnull) { // UNIQUE constraint doesn't exist, need to add it via migration log.info('[DB] Adding UNIQUE constraint on leads.email via migration') // Migrate leads table to add UNIQUE constraint db.exec( [ 'CREATE TABLE IF NOT EXISTS leads_new (', ' id INTEGER PRIMARY KEY AUTOINCREMENT,', ' company TEXT NOT NULL,', ' name TEXT NOT NULL,', ' email TEXT NOT NULL UNIQUE,', ' phone TEXT,', ' zip TEXT,', ' message TEXT,', ' service_interest TEXT,', ' created_at DATETIME DEFAULT CURRENT_TIMESTAMP', ')' ].join('\n') ) // Copy existing data (deduplicate - keep first occurrence per email) db.exec( [ 'INSERT OR IGNORE INTO leads_new (id, company, name, email, phone, zip, message, service_interest, created_at)', 'SELECT id, company, name, email, phone, zip, message, service_interest, created_at', 'FROM leads' ].join('\n') ) // Drop old table db.exec('DROP TABLE leads') // Rename new table db.exec('ALTER TABLE leads_new RENAME TO leads') log.info('[DB] UNIQUE constraint added on leads.email') } } else { // New table - create with UNIQUE constraint from the start db.exec( [ 'CREATE TABLE IF NOT EXISTS leads (', ' id INTEGER PRIMARY KEY AUTOINCREMENT,', ' company TEXT NOT NULL,', ' name TEXT NOT NULL,', ' email TEXT NOT NULL UNIQUE,', ' phone TEXT,', ' zip TEXT,', ' message TEXT,', ' service_interest TEXT,', ' created_at DATETIME DEFAULT CURRENT_TIMESTAMP', ')' ].join('\n') ) } // Support requests table db.exec( [ 'CREATE TABLE IF NOT EXISTS support_requests (', ' id INTEGER PRIMARY KEY AUTOINCREMENT,', ' name TEXT NOT NULL,', ' company TEXT NOT NULL,', ' email TEXT NOT NULL,', ' phone TEXT,', ' issue TEXT NOT NULL,', ' priority TEXT DEFAULT \'medium\',', ' created_at DATETIME DEFAULT CURRENT_TIMESTAMP', ')' ].join('\n') ) } initSchema()