Notify to PGSql instead of SQLite
This commit is contained in:
+72
-33
@@ -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(`
|
await dbRun(`
|
||||||
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
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)`);
|
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)
|
||||||
|
if (!pgPool) {
|
||||||
try { await dbRun('ALTER TABLE push_subscriptions 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 {}
|
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
@@ -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>
|
||||||
Generated
+1
@@ -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>
|
||||||
Reference in New Issue
Block a user