Notify to PGSql instead of SQLite #15

Merged
Ichitux merged 2 commits from notification_fixes into main 2026-07-07 13:37:07 +00:00
3 changed files with 158 additions and 79 deletions
Showing only changes of commit 071ed91e43 - Show all commits
+101 -62
View File
@@ -253,34 +253,70 @@ async function initDatabase() {
`); `);
} }
await dbRun(` // Push subscription tables — PG when available, SQLite fallback
CREATE TABLE IF NOT EXISTS push_subscriptions ( if (pgPool) {
id INTEGER PRIMARY KEY AUTOINCREMENT, await pgPool.query(`
medicine_nregistro TEXT NOT NULL, CREATE TABLE IF NOT EXISTS push_subscriptions (
medicine_name TEXT, id SERIAL PRIMARY KEY,
endpoint TEXT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
p256dh TEXT NOT NULL, medicine_nregistro TEXT NOT NULL,
auth TEXT NOT NULL, medicine_name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, endpoint TEXT NOT NULL,
UNIQUE(medicine_nregistro, endpoint) p256dh TEXT NOT NULL,
) auth TEXT NOT NULL,
`); created_at TIMESTAMPTZ DEFAULT NOW(),
await dbRun(`CREATE INDEX IF NOT EXISTS idx_push_subscriptions_med ON push_subscriptions(medicine_nregistro)`); UNIQUE(user_id, medicine_nregistro, endpoint)
)
`);
await pgPool.query(`CREATE INDEX IF NOT EXISTS idx_push_subscriptions_med ON push_subscriptions(medicine_nregistro)`);
await pgPool.query(`CREATE INDEX IF NOT EXISTS idx_push_subscriptions_user ON push_subscriptions(user_id)`);
await dbRun(` await pgPool.query(`
CREATE TABLE IF NOT EXISTS push_subscriptions_pharmacy ( CREATE TABLE IF NOT EXISTS push_subscriptions_pharmacy (
id INTEGER PRIMARY KEY AUTOINCREMENT, id SERIAL PRIMARY KEY,
medicine_nregistro TEXT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
medicine_name TEXT, medicine_nregistro TEXT NOT NULL,
pharmacy_id INTEGER NOT NULL, medicine_name TEXT,
endpoint TEXT NOT NULL, pharmacy_id INTEGER NOT NULL,
p256dh TEXT NOT NULL, endpoint TEXT NOT NULL,
auth TEXT NOT NULL, p256dh TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, auth TEXT NOT NULL,
UNIQUE(medicine_nregistro, pharmacy_id, endpoint) created_at TIMESTAMPTZ DEFAULT NOW(),
) UNIQUE(user_id, medicine_nregistro, pharmacy_id, endpoint)
`); )
await dbRun(`CREATE INDEX IF NOT EXISTS idx_push_subs_pharm ON push_subscriptions_pharmacy(medicine_nregistro, pharmacy_id)`); `);
await pgPool.query(`CREATE INDEX IF NOT EXISTS idx_push_subs_pharm ON push_subscriptions_pharmacy(medicine_nregistro, pharmacy_id)`);
await pgPool.query(`CREATE INDEX IF NOT EXISTS idx_push_subs_pharm_user ON push_subscriptions_pharmacy(user_id)`);
} else {
await dbRun(`
CREATE TABLE IF NOT EXISTS push_subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
medicine_nregistro TEXT NOT NULL,
medicine_name TEXT,
endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(medicine_nregistro, endpoint)
)
`);
await dbRun(`CREATE INDEX IF NOT EXISTS idx_push_subscriptions_med ON push_subscriptions(medicine_nregistro)`);
await dbRun(`
CREATE TABLE IF NOT EXISTS push_subscriptions_pharmacy (
id INTEGER PRIMARY KEY AUTOINCREMENT,
medicine_nregistro TEXT NOT NULL,
medicine_name TEXT,
pharmacy_id INTEGER NOT NULL,
endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(medicine_nregistro, pharmacy_id, endpoint)
)
`);
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 // SQLite-only migrations for existing deployments
@@ -307,9 +343,11 @@ if (!pgPool) {
try { await dbRun('ALTER TABLE users ADD COLUMN city TEXT'); } catch {} try { await dbRun('ALTER TABLE users ADD COLUMN city TEXT'); } catch {}
} }
// Add user_id to push tables (SQLite — no cross-DB FK) // Add user_id to push tables (SQLite only — PG tables already have it)
try { await dbRun('ALTER TABLE push_subscriptions ADD COLUMN user_id INTEGER'); } catch {} if (!pgPool) {
try { await dbRun('ALTER TABLE push_subscriptions_pharmacy ADD COLUMN user_id INTEGER'); } catch {} 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 {}
}
// Create alerts table for persistent Avisos data // Create alerts table for persistent Avisos data
try { try {
@@ -1733,35 +1771,35 @@ app.post('/api/notifications/subscribe', requireAuth, async (req, res) => {
} }
if (pharmacy_id) { if (pharmacy_id) {
const existing = await dbGet( const existing = await userDbGet(
'SELECT id FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?', 'SELECT id FROM push_subscriptions_pharmacy WHERE user_id = ? AND medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[medicine_nregistro, pharmacy_id, endpoint] [userId, medicine_nregistro, pharmacy_id, endpoint]
); );
if (existing) { if (existing) {
await dbRun( await userDbRun(
'UPDATE push_subscriptions_pharmacy SET medicine_name = ?, p256dh = ?, auth = ?, user_id = ? WHERE id = ?', 'UPDATE push_subscriptions_pharmacy SET medicine_name = ?, p256dh = ?, auth = ? WHERE id = ? AND user_id = ?',
[medicine_name || null, p256dh, auth, userId, existing.id] [medicine_name || null, p256dh, auth, existing.id, userId]
); );
} else { } else {
await dbRun( await userDbRun(
'INSERT INTO push_subscriptions_pharmacy (medicine_nregistro, medicine_name, pharmacy_id, endpoint, p256dh, auth, user_id) VALUES (?, ?, ?, ?, ?, ?, ?)', 'INSERT INTO push_subscriptions_pharmacy (user_id, medicine_nregistro, medicine_name, pharmacy_id, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?, ?, ?)',
[medicine_nregistro, medicine_name || null, pharmacy_id, endpoint, p256dh, auth, userId] [userId, medicine_nregistro, medicine_name || null, pharmacy_id, endpoint, p256dh, auth]
); );
} }
} else { } else {
const existing = await dbGet( const existing = await userDbGet(
'SELECT id FROM push_subscriptions WHERE medicine_nregistro = ? AND endpoint = ?', 'SELECT id FROM push_subscriptions WHERE user_id = ? AND medicine_nregistro = ? AND endpoint = ?',
[medicine_nregistro, endpoint] [userId, medicine_nregistro, endpoint]
); );
if (existing) { if (existing) {
await dbRun( await userDbRun(
'UPDATE push_subscriptions SET medicine_name = ?, p256dh = ?, auth = ?, user_id = ? WHERE id = ?', 'UPDATE push_subscriptions SET medicine_name = ?, p256dh = ?, auth = ? WHERE id = ? AND user_id = ?',
[medicine_name || null, p256dh, auth, userId, existing.id] [medicine_name || null, p256dh, auth, existing.id, userId]
); );
} else { } else {
await dbRun( await userDbRun(
'INSERT INTO push_subscriptions (medicine_nregistro, medicine_name, endpoint, p256dh, auth, user_id) VALUES (?, ?, ?, ?, ?, ?)', 'INSERT INTO push_subscriptions (user_id, medicine_nregistro, medicine_name, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?, ?)',
[medicine_nregistro, medicine_name || null, endpoint, p256dh, auth, userId] [userId, medicine_nregistro, medicine_name || null, endpoint, p256dh, auth]
); );
} }
} }
@@ -1776,21 +1814,22 @@ app.post('/api/notifications/subscribe', requireAuth, async (req, res) => {
app.delete('/api/notifications/unsubscribe', requireAuth, async (req, res) => { app.delete('/api/notifications/unsubscribe', requireAuth, async (req, res) => {
try { try {
const { medicine_nregistro, endpoint, pharmacy_id } = req.body || {}; const { medicine_nregistro, endpoint, pharmacy_id } = req.body || {};
const userId = req.session.userId;
if (!endpoint) { if (!endpoint) {
return res.status(400).json({ error: 'endpoint is required' }); return res.status(400).json({ error: 'endpoint is required' });
} }
if (pharmacy_id) { if (pharmacy_id) {
await dbRun( await userDbRun(
'DELETE FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?', 'DELETE FROM push_subscriptions_pharmacy WHERE user_id = ? AND medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[medicine_nregistro, pharmacy_id, endpoint] [userId, medicine_nregistro, pharmacy_id, endpoint]
); );
} else if (medicine_nregistro) { } else if (medicine_nregistro) {
await dbRun( await userDbRun(
'DELETE FROM push_subscriptions WHERE medicine_nregistro = ? AND endpoint = ?', 'DELETE FROM push_subscriptions WHERE user_id = ? AND medicine_nregistro = ? AND endpoint = ?',
[medicine_nregistro, endpoint] [userId, medicine_nregistro, endpoint]
); );
} else { } else {
await dbRun('DELETE FROM push_subscriptions WHERE endpoint = ?', [endpoint]); await userDbRun('DELETE FROM push_subscriptions WHERE user_id = ? AND endpoint = ?', [userId, endpoint]);
} }
res.status(204).end(); res.status(204).end();
} catch (error) { } catch (error) {
@@ -1803,14 +1842,14 @@ app.delete('/api/notifications/unsubscribe', requireAuth, async (req, res) => {
app.get('/api/notifications/mine', requireAuth, async (req, res) => { app.get('/api/notifications/mine', requireAuth, async (req, res) => {
try { try {
const userId = req.session.userId; const userId = req.session.userId;
const globalRows = await dbAll( const globalRows = await userDbAll(
`SELECT id, medicine_nregistro, medicine_name, created_at `SELECT id, medicine_nregistro, medicine_name, created_at
FROM push_subscriptions FROM push_subscriptions
WHERE user_id = ? WHERE user_id = ?
ORDER BY created_at DESC`, ORDER BY created_at DESC`,
[userId] [userId]
); );
const pharmacyRows = await dbAll( const pharmacyRows = await userDbAll(
`SELECT ps.id, ps.medicine_nregistro, ps.medicine_name, ps.pharmacy_id, `SELECT ps.id, ps.medicine_nregistro, ps.medicine_name, ps.pharmacy_id,
ps.created_at, p.name AS pharmacy_name, p.address AS pharmacy_address ps.created_at, p.name AS pharmacy_name, p.address AS pharmacy_address
FROM push_subscriptions_pharmacy ps FROM push_subscriptions_pharmacy ps
@@ -1839,12 +1878,12 @@ app.delete('/api/notifications/mine', requireAuth, async (req, res) => {
return res.status(400).json({ error: 'id must be a positive integer' }); return res.status(400).json({ error: 'id must be a positive integer' });
} }
if (scope === 'pharmacy') { if (scope === 'pharmacy') {
await dbRun( await userDbRun(
'DELETE FROM push_subscriptions_pharmacy WHERE id = ? AND user_id = ?', 'DELETE FROM push_subscriptions_pharmacy WHERE id = ? AND user_id = ?',
[rowId, userId] [rowId, userId]
); );
} else if (scope === 'global') { } else if (scope === 'global') {
await dbRun( await userDbRun(
'DELETE FROM push_subscriptions WHERE id = ? AND user_id = ?', 'DELETE FROM push_subscriptions WHERE id = ? AND user_id = ?',
[rowId, userId] [rowId, userId]
); );
@@ -1861,11 +1900,11 @@ app.delete('/api/notifications/mine', requireAuth, async (req, res) => {
async function sendPushForMedicine({ medicine_nregistro, medicine_name, pharmacy }) { async function sendPushForMedicine({ medicine_nregistro, medicine_name, pharmacy }) {
if (!PUSH_ENABLED) return { sent: 0, failed: 0, pruned: 0 }; if (!PUSH_ENABLED) return { sent: 0, failed: 0, pruned: 0 };
const globalSubs = await dbAll( const globalSubs = await userDbAll(
'SELECT id, endpoint, p256dh, auth FROM push_subscriptions WHERE medicine_nregistro = ?', 'SELECT id, endpoint, p256dh, auth FROM push_subscriptions WHERE medicine_nregistro = ?',
[medicine_nregistro] [medicine_nregistro]
); );
const pharmacySubs = pharmacy?.id ? await dbAll( const pharmacySubs = pharmacy?.id ? await userDbAll(
'SELECT id, endpoint, p256dh, auth FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ?', 'SELECT id, endpoint, p256dh, auth FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ?',
[medicine_nregistro, pharmacy.id] [medicine_nregistro, pharmacy.id]
) : []; ) : [];
@@ -1907,7 +1946,7 @@ async function sendPushForMedicine({ medicine_nregistro, medicine_name, pharmacy
const deleteQuery = sub._pharmSub const deleteQuery = sub._pharmSub
? 'DELETE FROM push_subscriptions_pharmacy WHERE id = ?' ? 'DELETE FROM push_subscriptions_pharmacy WHERE id = ?'
: 'DELETE FROM push_subscriptions WHERE id = ?'; : 'DELETE FROM push_subscriptions WHERE id = ?';
await dbRun(deleteQuery, [sub.id]); await userDbRun(deleteQuery, [sub.id]);
pruned++; pruned++;
} catch {} } catch {}
} else { } else {
+56 -17
View File
@@ -1,8 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DeviceStreaming"> <component name="DeviceStreaming">
<option name="defaultDeviceApplied" value="true" />
<option name="deviceSelectionList"> <option name="deviceSelectionList">
<list> <list>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="selected" value="true" />
<option name="tags">
<list>
<option value="default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="34" /> <option name="api" value="34" />
<option name="brand" value="Sony" /> <option name="brand" value="Sony" />
@@ -171,6 +190,18 @@
<option name="screenX" value="720" /> <option name="screenX" value="720" />
<option name="screenY" value="1600" /> <option name="screenY" value="1600" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="33" />
<option name="brand" value="samsung" />
<option name="codename" value="a03sutfn" />
<option name="id" value="a03sutfn" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy A03s" />
<option name="screenDensity" value="280" />
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="35" /> <option name="api" value="35" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -531,6 +562,18 @@
<option name="screenX" value="1440" /> <option name="screenX" value="1440" />
<option name="screenY" value="3088" /> <option name="screenY" value="3088" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="b4qsqw" />
<option name="id" value="b4qsqw" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy Z Flip4" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2640" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="36" /> <option name="api" value="36" />
<option name="brand" value="samsung" /> <option name="brand" value="samsung" />
@@ -1681,6 +1724,18 @@
<option name="screenX" value="1080" /> <option name="screenX" value="1080" />
<option name="screenY" value="2340" /> <option name="screenY" value="2340" />
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="36" />
<option name="brand" value="samsung" />
<option name="codename" value="r0qksx" />
<option name="id" value="r0qksx" />
<option name="labId" value="google" />
<option name="manufacturer" value="Samsung" />
<option name="name" value="Galaxy S22" />
<option name="screenDensity" value="480" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="api" value="30" /> <option name="api" value="30" />
<option name="brand" value="google" /> <option name="brand" value="google" />
@@ -1785,23 +1840,6 @@
</list> </list>
</option> </option>
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
<PersistentDeviceSelectionData>
<option name="api" value="30" />
<option name="brand" value="google" />
<option name="codename" value="redfin" />
<option name="id" value="redfin" />
<option name="labId" value="google" />
<option name="manufacturer" value="Google" />
<option name="name" value="Pixel 5" />
<option name="screenDensity" value="440" />
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
<option name="tags">
<list>
<option value="default" />
</list>
</option>
</PersistentDeviceSelectionData>
<PersistentDeviceSelectionData> <PersistentDeviceSelectionData>
<option name="accessStatus"> <option name="accessStatus">
<list> <list>
@@ -2186,5 +2224,6 @@
</PersistentDeviceSelectionData> </PersistentDeviceSelectionData>
</list> </list>
</option> </option>
<option name="selectedCloudProject" value="farmaclic-53c42" />
</component> </component>
</project> </project>
+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component> </component>
</project> </project>