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
+72 -33
View File
@@ -253,6 +253,41 @@ async function initDatabase() {
`);
}
// Push subscription tables — PG when available, SQLite fallback
if (pgPool) {
await pgPool.query(`
CREATE TABLE IF NOT EXISTS push_subscriptions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
medicine_nregistro TEXT NOT NULL,
medicine_name TEXT,
endpoint TEXT NOT NULL,
p256dh TEXT NOT NULL,
auth TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
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 pgPool.query(`
CREATE TABLE IF NOT EXISTS push_subscriptions_pharmacy (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
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 TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, medicine_nregistro, pharmacy_id, endpoint)
)
`);
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,
@@ -281,6 +316,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) {
// SQLite-only migrations for existing deployments
@@ -307,9 +343,11 @@ if (!pgPool) {
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)
if (!pgPool) {
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
try {
@@ -1733,35 +1771,35 @@ app.post('/api/notifications/subscribe', requireAuth, async (req, res) => {
}
if (pharmacy_id) {
const existing = await dbGet(
'SELECT id FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[medicine_nregistro, pharmacy_id, endpoint]
const existing = await userDbGet(
'SELECT id FROM push_subscriptions_pharmacy WHERE user_id = ? AND medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[userId, medicine_nregistro, pharmacy_id, endpoint]
);
if (existing) {
await dbRun(
'UPDATE push_subscriptions_pharmacy SET medicine_name = ?, p256dh = ?, auth = ?, user_id = ? WHERE id = ?',
[medicine_name || null, p256dh, auth, userId, existing.id]
await userDbRun(
'UPDATE push_subscriptions_pharmacy SET medicine_name = ?, p256dh = ?, auth = ? WHERE id = ? AND user_id = ?',
[medicine_name || null, p256dh, auth, existing.id, userId]
);
} else {
await dbRun(
'INSERT INTO push_subscriptions_pharmacy (medicine_nregistro, medicine_name, pharmacy_id, endpoint, p256dh, auth, user_id) VALUES (?, ?, ?, ?, ?, ?, ?)',
[medicine_nregistro, medicine_name || null, pharmacy_id, endpoint, p256dh, auth, userId]
await userDbRun(
'INSERT INTO push_subscriptions_pharmacy (user_id, medicine_nregistro, medicine_name, pharmacy_id, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?, ?, ?)',
[userId, medicine_nregistro, medicine_name || null, pharmacy_id, endpoint, p256dh, auth]
);
}
} else {
const existing = await dbGet(
'SELECT id FROM push_subscriptions WHERE medicine_nregistro = ? AND endpoint = ?',
[medicine_nregistro, endpoint]
const existing = await userDbGet(
'SELECT id FROM push_subscriptions WHERE user_id = ? AND medicine_nregistro = ? AND endpoint = ?',
[userId, medicine_nregistro, endpoint]
);
if (existing) {
await dbRun(
'UPDATE push_subscriptions SET medicine_name = ?, p256dh = ?, auth = ?, user_id = ? WHERE id = ?',
[medicine_name || null, p256dh, auth, userId, existing.id]
await userDbRun(
'UPDATE push_subscriptions SET medicine_name = ?, p256dh = ?, auth = ? WHERE id = ? AND user_id = ?',
[medicine_name || null, p256dh, auth, existing.id, userId]
);
} else {
await dbRun(
'INSERT INTO push_subscriptions (medicine_nregistro, medicine_name, endpoint, p256dh, auth, user_id) VALUES (?, ?, ?, ?, ?, ?)',
[medicine_nregistro, medicine_name || null, endpoint, p256dh, auth, userId]
await userDbRun(
'INSERT INTO push_subscriptions (user_id, medicine_nregistro, medicine_name, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?, ?)',
[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) => {
try {
const { medicine_nregistro, endpoint, pharmacy_id } = req.body || {};
const userId = req.session.userId;
if (!endpoint) {
return res.status(400).json({ error: 'endpoint is required' });
}
if (pharmacy_id) {
await dbRun(
'DELETE FROM push_subscriptions_pharmacy WHERE medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[medicine_nregistro, pharmacy_id, endpoint]
await userDbRun(
'DELETE FROM push_subscriptions_pharmacy WHERE user_id = ? AND medicine_nregistro = ? AND pharmacy_id = ? AND endpoint = ?',
[userId, medicine_nregistro, pharmacy_id, endpoint]
);
} else if (medicine_nregistro) {
await dbRun(
'DELETE FROM push_subscriptions WHERE medicine_nregistro = ? AND endpoint = ?',
[medicine_nregistro, endpoint]
await userDbRun(
'DELETE FROM push_subscriptions WHERE user_id = ? AND medicine_nregistro = ? AND endpoint = ?',
[userId, medicine_nregistro, endpoint]
);
} 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();
} catch (error) {
@@ -1803,14 +1842,14 @@ app.delete('/api/notifications/unsubscribe', requireAuth, async (req, res) => {
app.get('/api/notifications/mine', requireAuth, async (req, res) => {
try {
const userId = req.session.userId;
const globalRows = await dbAll(
const globalRows = await userDbAll(
`SELECT id, medicine_nregistro, medicine_name, created_at
FROM push_subscriptions
WHERE user_id = ?
ORDER BY created_at DESC`,
[userId]
);
const pharmacyRows = await dbAll(
const pharmacyRows = await userDbAll(
`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
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' });
}
if (scope === 'pharmacy') {
await dbRun(
await userDbRun(
'DELETE FROM push_subscriptions_pharmacy WHERE id = ? AND user_id = ?',
[rowId, userId]
);
} else if (scope === 'global') {
await dbRun(
await userDbRun(
'DELETE FROM push_subscriptions WHERE id = ? AND user_id = ?',
[rowId, userId]
);
@@ -1861,11 +1900,11 @@ app.delete('/api/notifications/mine', requireAuth, async (req, res) => {
async function sendPushForMedicine({ medicine_nregistro, medicine_name, pharmacy }) {
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 = ?',
[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 = ?',
[medicine_nregistro, pharmacy.id]
) : [];
@@ -1907,7 +1946,7 @@ async function sendPushForMedicine({ medicine_nregistro, medicine_name, pharmacy
const deleteQuery = sub._pharmSub
? 'DELETE FROM push_subscriptions_pharmacy WHERE id = ?'
: 'DELETE FROM push_subscriptions WHERE id = ?';
await dbRun(deleteQuery, [sub.id]);
await userDbRun(deleteQuery, [sub.id]);
pruned++;
} catch {}
} else {
+56 -17
View File
@@ -1,8 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceStreaming">
<option name="defaultDeviceApplied" value="true" />
<option name="deviceSelectionList">
<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>
<option name="api" value="34" />
<option name="brand" value="Sony" />
@@ -171,6 +190,18 @@
<option name="screenX" value="720" />
<option name="screenY" value="1600" />
</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>
<option name="api" value="35" />
<option name="brand" value="samsung" />
@@ -531,6 +562,18 @@
<option name="screenX" value="1440" />
<option name="screenY" value="3088" />
</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>
<option name="api" value="36" />
<option name="brand" value="samsung" />
@@ -1681,6 +1724,18 @@
<option name="screenX" value="1080" />
<option name="screenY" value="2340" />
</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>
<option name="api" value="30" />
<option name="brand" value="google" />
@@ -1785,23 +1840,6 @@
</list>
</option>
</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>
<option name="accessStatus">
<list>
@@ -2186,5 +2224,6 @@
</PersistentDeviceSelectionData>
</list>
</option>
<option name="selectedCloudProject" value="farmaclic-53c42" />
</component>
</project>
+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>