const mysql = require('mysql2/promise'); const { Pool } = require('pg'); const bcrypt = require('bcryptjs'); require('dotenv').config(); const DB_CLIENT = process.env.DB_CLIENT || 'mysql'; // 'mysql' or 'postgres' // DB Configuration const dbConfig = { host: process.env.DB_HOST || 'localhost', user: process.env.DB_USER || 'root', password: process.env.DB_PASS || '', database: process.env.DB_NAME || 'condominio', port: process.env.DB_PORT || (DB_CLIENT === 'postgres' ? 5432 : 3306), }; let mysqlPool = null; let pgPool = null; if (DB_CLIENT === 'postgres') { pgPool = new Pool(dbConfig); } else { mysqlPool = mysql.createPool({ ...dbConfig, waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); } // Wrapper to normalize query execution between MySQL and Postgres // MySQL uses '?' for placeholders, Postgres uses '$1', '$2', etc. // MySQL returns [rows, fields], pg returns result object with .rows const executeQuery = async (sql, params = []) => { if (DB_CLIENT === 'postgres') { // Convert ? to $1, $2, ... let paramIndex = 1; const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`); const result = await pgPool.query(pgSql, params); // Normalize return to match mysql2 [rows, fields] signature return [result.rows, result.fields]; } else { return await mysqlPool.query(sql, params); } }; // Interface object to be used by server.js const dbInterface = { query: executeQuery, getConnection: async () => { if (DB_CLIENT === 'postgres') { // Postgres pool handles connections automatically, but we provide a mock // release function to satisfy the existing initDb pattern if needed. // For general queries, we use the pool directly in executeQuery. return { query: executeQuery, release: () => {} }; } else { return await mysqlPool.getConnection(); } } }; const initDb = async () => { try { const connection = await dbInterface.getConnection(); console.log(`Database connected successfully using ${DB_CLIENT}.`); // Helper for syntax differences const AUTO_INCREMENT = DB_CLIENT === 'postgres' ? '' : 'AUTO_INCREMENT'; // Settings ID logic: Postgres doesn't like DEFAULT 1 on INT PK without sequence easily, // but since we insert ID 1 manually, we can just use INT PRIMARY KEY. const TIMESTAMP_TYPE = 'TIMESTAMP'; // Both support TIMESTAMP // 1. Settings Table await connection.query(` CREATE TABLE IF NOT EXISTS settings ( id INT PRIMARY KEY, condo_name VARCHAR(255) DEFAULT 'Il Mio Condominio', default_monthly_quota DECIMAL(10, 2) DEFAULT 100.00, current_year INT, smtp_config JSON ${DB_CLIENT === 'postgres' ? 'NULL' : 'NULL'} ) `); // Migration for smtp_config if missing (Check column existence) try { let hasCol = false; if (DB_CLIENT === 'postgres') { const [res] = await connection.query("SELECT column_name FROM information_schema.columns WHERE table_name='settings' AND column_name='smtp_config'"); hasCol = res.length > 0; } else { const [res] = await connection.query("SHOW COLUMNS FROM settings LIKE 'smtp_config'"); hasCol = res.length > 0; } if (!hasCol) { await connection.query("ALTER TABLE settings ADD COLUMN smtp_config JSON NULL"); } } catch (e) { console.warn("Settings migration check failed", e.message); } const [rows] = await connection.query('SELECT * FROM settings WHERE id = 1'); if (rows.length === 0) { const currentYear = new Date().getFullYear(); await connection.query( 'INSERT INTO settings (id, condo_name, default_monthly_quota, current_year) VALUES (1, ?, ?, ?)', ['Condominio Demo', 100.00, currentYear] ); } // 2. Families Table await connection.query(` CREATE TABLE IF NOT EXISTS families ( id VARCHAR(36) PRIMARY KEY, name VARCHAR(255) NOT NULL, unit_number VARCHAR(50), contact_email VARCHAR(255), created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP ) `); // 3. Payments Table await connection.query(` CREATE TABLE IF NOT EXISTS payments ( id VARCHAR(36) PRIMARY KEY, family_id VARCHAR(36) NOT NULL, amount DECIMAL(10, 2) NOT NULL, date_paid ${TIMESTAMP_TYPE} NOT NULL, for_month INT NOT NULL, for_year INT NOT NULL, notes TEXT, created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (family_id) REFERENCES families(id) ON DELETE CASCADE ) `); // 4. Users Table await connection.query(` CREATE TABLE IF NOT EXISTS users ( id VARCHAR(36) PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, name VARCHAR(255), role VARCHAR(20) DEFAULT 'user', phone VARCHAR(20), family_id VARCHAR(36) NULL, receive_alerts BOOLEAN DEFAULT TRUE, created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (family_id) REFERENCES families(id) ON DELETE SET NULL ) `); // --- MIGRATION: CHECK FOR PHONE & ALERTS COLUMNS --- try { let hasPhone = false; let hasAlerts = false; if (DB_CLIENT === 'postgres') { const [cols] = await connection.query("SELECT column_name FROM information_schema.columns WHERE table_name='users'"); hasPhone = cols.some(c => c.column_name === 'phone'); hasAlerts = cols.some(c => c.column_name === 'receive_alerts'); } else { const [cols] = await connection.query("SHOW COLUMNS FROM users"); hasPhone = cols.some(c => c.Field === 'phone'); hasAlerts = cols.some(c => c.Field === 'receive_alerts'); } if (!hasPhone) { console.log('Adding missing "phone" column to users table...'); await connection.query("ALTER TABLE users ADD COLUMN phone VARCHAR(20)"); } if (!hasAlerts) { console.log('Adding missing "receive_alerts" column to users table...'); await connection.query("ALTER TABLE users ADD COLUMN receive_alerts BOOLEAN DEFAULT TRUE"); } } catch (migError) { console.warn("Migration check failed:", migError.message); } // 5. Alerts Table await connection.query(` CREATE TABLE IF NOT EXISTS alerts ( id VARCHAR(36) PRIMARY KEY, subject VARCHAR(255) NOT NULL, body TEXT, days_offset INT DEFAULT 1, offset_type VARCHAR(50) DEFAULT 'before_next_month', send_hour INT DEFAULT 9, active BOOLEAN DEFAULT TRUE, last_sent ${TIMESTAMP_TYPE} NULL, created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP ) `); // Seed Admin User const [admins] = await connection.query('SELECT * FROM users WHERE email = ?', ['fcarra79@gmail.com']); if (admins.length === 0) { const hashedPassword = await bcrypt.hash('Mr10921.', 10); const { v4: uuidv4 } = require('uuid'); await connection.query( 'INSERT INTO users (id, email, password_hash, name, role) VALUES (?, ?, ?, ?, ?)', [uuidv4(), 'fcarra79@gmail.com', hashedPassword, 'Amministratore', 'admin'] ); console.log('Default Admin user created.'); } console.log('Database tables initialized.'); if (connection.release) connection.release(); } catch (error) { console.error('Database initialization failed:', error); process.exit(1); } }; module.exports = { pool: dbInterface, initDb };