refactor: Migrate to API endpoint and use real DB connection

This commit shifts the application's data fetching mechanism from local storage mocks to a dedicated API endpoint. It also refactors the database connection logic to utilize a connection pool for improved performance and scalability.

Key changes include:
- Disabling `FORCE_LOCAL_DB` in `mockDb.ts` and implementing a generic `request` function for API calls.
- Centralizing authentication headers in `mockDb.ts`.
- Modifying `server/db.js` to use `pg` and `mysql2/promise` pools and a unified `executeQuery` function.
- Updating `server/server.js` to use the database pool for queries.
- Configuring Vite's development server to proxy API requests to the backend.
This commit is contained in:
2025-12-07 01:45:12 +01:00
parent 3f954c65b1
commit 1641b931e8
5 changed files with 406 additions and 625 deletions

View File

@@ -1,3 +1,4 @@
const mysql = require('mysql2/promise');
const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
@@ -28,31 +29,21 @@ if (DB_CLIENT === 'postgres') {
});
}
// 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: () => {}
@@ -68,59 +59,71 @@ const initDb = async () => {
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
const TIMESTAMP_TYPE = 'TIMESTAMP';
// 1. Settings Table
// 0. Settings Table (Global App Settings)
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]
);
}
// 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),
iban VARCHAR(50),
default_monthly_quota DECIMAL(10, 2) DEFAULT 100.00,
image VARCHAR(255),
created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP
)
`);
// 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),
contact_email VARCHAR(255),
created_at ${TIMESTAMP_TYPE} DEFAULT CURRENT_TIMESTAMP
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 and custom_monthly_quota if missing
try {
let hasCondoId = false;
let hasQuota = 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');
} 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');
}
if (!hasCondoId) {
console.log('Migrating: Adding condo_id to families...');
await connection.query("ALTER TABLE families ADD COLUMN condo_id VARCHAR(36)");
if (DB_CLIENT !== 'postgres') { // Add FK for mysql specifically if needed, simplified here
// await connection.query("ALTER TABLE families ADD CONSTRAINT fk_condo FOREIGN KEY (condo_id) REFERENCES condos(id)");
}
}
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");
}
} catch(e) { console.warn("Families migration warning:", e.message); }
// 3. Payments Table
await connection.query(`
CREATE TABLE IF NOT EXISTS payments (
@@ -152,33 +155,6 @@ const initDb = async () => {
)
`);
// --- 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 (
@@ -194,7 +170,43 @@ const initDb = async () => {
)
`);
// Seed Admin User
// 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]
);
}
const [admins] = await connection.query('SELECT * FROM users WHERE email = ?', ['fcarra79@gmail.com']);
if (admins.length === 0) {
const hashedPassword = await bcrypt.hash('Mr10921.', 10);
@@ -214,4 +226,4 @@ const initDb = async () => {
}
};
module.exports = { pool: dbInterface, initDb };
module.exports = { pool: dbInterface, initDb };