Design hotfixes and improvements.
Run Tests on Branches / Backend Tests (push) Successful in 3m32s
Run Tests on Branches / Frontend Tests (push) Successful in 3m29s

This commit is contained in:
Antoni Nuñez Romeu
2026-07-02 12:40:09 +02:00
parent 15c1127b34
commit 7606d118e4
17 changed files with 700 additions and 282 deletions
+128 -6
View File
@@ -65,7 +65,8 @@ const sessionConfig = {
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
secure: process.env.COOKIE_SECURE === 'true',
sameSite: 'lax',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
@@ -225,6 +226,22 @@ async function initDatabase() {
`);
}
if (pgPool) {
await pgPool.query(`
CREATE TABLE IF NOT EXISTS user_alerts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
type TEXT NOT NULL CHECK (type IN ('reminder', 'availability', 'schedule')),
medicine_nregistro TEXT,
title TEXT,
detail TEXT,
schedule TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)
`);
}
await dbRun(`
CREATE TABLE IF NOT EXISTS push_subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -254,7 +271,7 @@ async function initDatabase() {
`);
await dbRun(`CREATE INDEX IF NOT EXISTS idx_push_subs_pharm ON push_subscriptions_pharmacy(medicine_nregistro, pharmacy_id)`);
if (!pgPool) {
if (!pgPool) {
// SQLite-only migrations for existing deployments
try { await dbRun('ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0'); } catch {}
try { await dbRun('ALTER TABLE users ADD COLUMN address TEXT'); } catch {}
@@ -262,13 +279,32 @@ async function initDatabase() {
try { await dbRun('ALTER TABLE users ADD COLUMN longitude REAL'); } catch {}
}
// Add user_id to push tables (SQLite — no cross-DB FK)
// Add user_id to push tables (SQLite — no cross-DB FK)
try { await dbRun('ALTER TABLE push_subscriptions ADD COLUMN user_id INTEGER'); } catch {}
try { await dbRun('ALTER TABLE push_subscriptions_pharmacy ADD COLUMN user_id INTEGER'); } catch {}
console.log('Database initialized successfully');
} catch (error) {
console.error('Error initializing database:', error);
// Create alerts table for persistent Avisos data
try {
await dbRun(`
CREATE TABLE IF NOT EXISTS user_alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
type TEXT NOT NULL CHECK (type IN ('reminder', 'availability', 'schedule')),
medicine_nregistro TEXT,
title TEXT,
detail TEXT,
schedule TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
} catch (e) {
if (!/table already exists/i.test(e.message)) throw e;
}
} catch (err) {
console.error('initDatabase failed:', err);
throw err;
}
}
@@ -630,6 +666,92 @@ app.put('/api/users/me', requireAuth, async (req, res) => {
}
});
// ========== USER ALERTS API ==========
// Get all alerts for the current user
app.get('/api/alerts', requireAuth, async (req, res) => {
try {
const userId = req.session.userId;
const rows = await userDbAll(
'SELECT id, type, medicine_nregistro, title, detail, schedule, created_at, updated_at FROM user_alerts WHERE user_id = ? ORDER BY created_at DESC',
[userId]
);
res.json(rows);
} catch (error) {
console.error('Error fetching alerts:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Create a new alert
app.post('/api/alerts', requireAuth, async (req, res) => {
try {
const userId = req.session.userId;
const { type, medicine_nregistro, title, detail, schedule } = req.body || {};
if (!type || !['reminder', 'availability', 'schedule'].includes(type)) {
return res.status(400).json({ error: 'Valid type is required (reminder, availability, schedule)' });
}
const result = await userDbRun(
'INSERT INTO user_alerts (user_id, type, medicine_nregistro, title, detail, schedule) VALUES (?, ?, ?, ?, ?, ?)',
[userId, type, medicine_nregistro || null, title || null, detail || null, schedule || null]
);
const newAlert = await userDbGet('SELECT * FROM user_alerts WHERE id = ?', [result.lastID]);
res.status(201).json(newAlert);
} catch (error) {
console.error('Error creating alert:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update an existing alert
app.put('/api/alerts/:id', requireAuth, async (req, res) => {
try {
const userId = req.session.userId;
const alertId = parseInt(req.params.id);
const { medicine_nregistro, title, detail, schedule } = req.body || {};
const result = await userDbRun(
'UPDATE user_alerts SET medicine_nregistro = ?, title = ?, detail = ?, schedule = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND user_id = ?',
[medicine_nregistro || null, title || null, detail || null, schedule || null, alertId, userId]
);
if (result.changes === 0) {
return res.status(404).json({ error: 'Alert not found' });
}
const updatedAlert = await userDbGet('SELECT * FROM user_alerts WHERE id = ?', [alertId]);
res.json(updatedAlert);
} catch (error) {
console.error('Error updating alert:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Delete an alert
app.delete('/api/alerts/:id', requireAuth, async (req, res) => {
try {
const userId = req.session.userId;
const alertId = parseInt(req.params.id);
const result = await userDbRun(
'DELETE FROM user_alerts WHERE id = ? AND user_id = ?',
[alertId, userId]
);
if (result.changes === 0) {
return res.status(404).json({ error: 'Alert not found' });
}
res.status(204).end();
} catch (error) {
console.error('Error deleting alert:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Geocode for any authenticated user (rate-limited). Same impl as /api/admin/geocode.
app.get('/api/geocode', requireAuth, geocodeLimiter, async (req, res) => {
const q = (req.query.q || '').trim();