Files
Condopay/server/db.js
frakarr fd107c1ef8 feat: Enhance condo and family data models
Adds new fields for detailed address information and notes to the Condo and Family types.
Updates database schema and server API endpoints to support these new fields, improving data richness for location and specific family/condo details.
2025-12-07 16:10:33 +01:00

293 lines
10 KiB
JavaScript

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
});
}
const executeQuery = async (sql, params = []) => {
if (DB_CLIENT === 'postgres') {
let paramIndex = 1;
const pgSql = sql.replace(/\?/g, () => `$${paramIndex++}`);
const result = await pgPool.query(pgSql, params);
return [result.rows, result.fields];
} else {
return await mysqlPool.query(sql, params);
}
};
const dbInterface = {
query: executeQuery,
getConnection: async () => {
if (DB_CLIENT === 'postgres') {
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}.`);
const TIMESTAMP_TYPE = 'TIMESTAMP';
// 0. Settings Table (Global App Settings)
await connection.query(`
CREATE TABLE IF NOT EXISTS settings (
id INT PRIMARY KEY,
current_year INT,
smtp_config JSON ${DB_CLIENT === 'postgres' ? 'NULL' : 'NULL'}
)
`);
// 1. Condos Table
await connection.query(`
CREATE TABLE IF NOT EXISTS condos (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
address VARCHAR(255),
street_number VARCHAR(20),
city VARCHAR(100),
province VARCHAR(100),
zip_code VARCHAR(20),
notes TEXT,
iban VARCHAR(50),
default_monthly_quota DECIMAL(10, 2) DEFAULT 100.00,
image VARCHAR(255),
created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP
)
`);
// Migration for condos: Add new address fields
try {
let hasCity = false;
if (DB_CLIENT === 'postgres') {
const [cols] = await connection.query("SELECT column_name FROM information_schema.columns WHERE table_name='condos'");
hasCity = cols.some(c => c.column_name === 'city');
} else {
const [cols] = await connection.query("SHOW COLUMNS FROM condos");
hasCity = cols.some(c => c.Field === 'city');
}
if (!hasCity) {
console.log('Migrating: Adding address fields to condos...');
await connection.query("ALTER TABLE condos ADD COLUMN street_number VARCHAR(20)");
await connection.query("ALTER TABLE condos ADD COLUMN city VARCHAR(100)");
await connection.query("ALTER TABLE condos ADD COLUMN province VARCHAR(100)");
await connection.query("ALTER TABLE condos ADD COLUMN zip_code VARCHAR(20)");
await connection.query("ALTER TABLE condos ADD COLUMN notes TEXT");
}
} catch(e) { console.warn("Condos migration warning:", e.message); }
// 2. Families Table
await connection.query(`
CREATE TABLE IF NOT EXISTS families (
id VARCHAR(36) PRIMARY KEY,
condo_id VARCHAR(36),
name VARCHAR(255) NOT NULL,
unit_number VARCHAR(50),
stair VARCHAR(50),
floor VARCHAR(50),
notes TEXT,
contact_email VARCHAR(255),
custom_monthly_quota DECIMAL(10, 2) NULL,
created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (condo_id) REFERENCES condos(id) ON DELETE CASCADE
)
`);
// Migration for families: Add condo_id, custom_monthly_quota, stair, floor, notes
try {
let hasCondoId = false;
let hasQuota = false;
let hasStair = false;
if (DB_CLIENT === 'postgres') {
const [cols] = await connection.query("SELECT column_name FROM information_schema.columns WHERE table_name='families'");
hasCondoId = cols.some(c => c.column_name === 'condo_id');
hasQuota = cols.some(c => c.column_name === 'custom_monthly_quota');
hasStair = cols.some(c => c.column_name === 'stair');
} else {
const [cols] = await connection.query("SHOW COLUMNS FROM families");
hasCondoId = cols.some(c => c.Field === 'condo_id');
hasQuota = cols.some(c => c.Field === 'custom_monthly_quota');
hasStair = cols.some(c => c.Field === 'stair');
}
if (!hasCondoId) {
console.log('Migrating: Adding condo_id to families...');
await connection.query("ALTER TABLE families ADD COLUMN condo_id VARCHAR(36)");
}
if (!hasQuota) {
console.log('Migrating: Adding custom_monthly_quota to families...');
await connection.query("ALTER TABLE families ADD COLUMN custom_monthly_quota DECIMAL(10, 2) NULL");
}
if (!hasStair) {
console.log('Migrating: Adding extended fields to families...');
await connection.query("ALTER TABLE families ADD COLUMN stair VARCHAR(50)");
await connection.query("ALTER TABLE families ADD COLUMN floor VARCHAR(50)");
await connection.query("ALTER TABLE families ADD COLUMN notes TEXT");
}
} catch(e) { console.warn("Families migration warning:", e.message); }
// 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
)
`);
// 5. Alerts Table
await connection.query(`
CREATE TABLE IF NOT EXISTS alerts (
id VARCHAR(36) PRIMARY KEY,
condo_id VARCHAR(36) NULL,
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,
FOREIGN KEY (condo_id) REFERENCES condos(id) ON DELETE CASCADE
)
`);
// Migration for alerts: Add condo_id if missing
try {
let hasCondoId = false;
if (DB_CLIENT === 'postgres') {
const [cols] = await connection.query("SELECT column_name FROM information_schema.columns WHERE table_name='alerts'");
hasCondoId = cols.some(c => c.column_name === 'condo_id');
} else {
const [cols] = await connection.query("SHOW COLUMNS FROM alerts");
hasCondoId = cols.some(c => c.Field === 'condo_id');
}
if (!hasCondoId) {
console.log('Migrating: Adding condo_id to alerts...');
await connection.query("ALTER TABLE alerts ADD COLUMN condo_id VARCHAR(36)");
await connection.query("ALTER TABLE alerts ADD CONSTRAINT fk_alerts_condo FOREIGN KEY (condo_id) REFERENCES condos(id) ON DELETE CASCADE");
}
} catch(e) { console.warn("Alerts migration warning:", e.message); }
// 6. Notices Table
await connection.query(`
CREATE TABLE IF NOT EXISTS notices (
id VARCHAR(36) PRIMARY KEY,
condo_id VARCHAR(36) NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT,
type VARCHAR(50) DEFAULT 'info',
link VARCHAR(255),
date ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP,
active BOOLEAN DEFAULT TRUE,
created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (condo_id) REFERENCES condos(id) ON DELETE CASCADE
)
`);
// 7. Notice Reads
await connection.query(`
CREATE TABLE IF NOT EXISTS notice_reads (
user_id VARCHAR(36),
notice_id VARCHAR(36),
read_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, notice_id),
FOREIGN KEY (notice_id) REFERENCES notices(id) ON DELETE CASCADE
)
`);
// --- SEEDING ---
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, current_year) VALUES (1, ?)',
[currentYear]
);
}
// ENSURE ADMIN EXISTS AND HAS CORRECT ROLE
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.');
} else {
// Force update role to admin just in case it was changed or created wrongly
await connection.query('UPDATE users SET role = ? WHERE email = ?', ['admin', 'fcarra79@gmail.com']);
console.log('Ensured default user has admin role.');
}
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 };