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:
156
server/db.js
156
server/db.js
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user