v0.9.88 major change sqlite to postgres

This commit is contained in:
2026-03-20 10:46:29 -04:00
parent 7dc4cfcbce
commit ac7cba0f92
31 changed files with 3729 additions and 2645 deletions

View File

@@ -1,104 +1,112 @@
const express = require('express');
const webpush = require('web-push');
const router = express.Router();
const { getDb } = require('../models/db');
const express = require('express');
const webpush = require('web-push');
const router = express.Router();
const { query, queryOne, queryResult, exec } = require('../models/db');
const { authMiddleware } = require('../middleware/auth');
// Get or generate VAPID keys stored in settings
function getVapidKeys() {
const db = getDb();
let pub = db.prepare("SELECT value FROM settings WHERE key = 'vapid_public'").get();
let priv = db.prepare("SELECT value FROM settings WHERE key = 'vapid_private'").get();
// VAPID keys are stored in settings; lazily initialised on first request
let vapidPublicKey = null;
async function getVapidKeys(schema) {
const pub = await queryOne(schema, "SELECT value FROM settings WHERE key = 'vapid_public'");
const priv = await queryOne(schema, "SELECT value FROM settings WHERE key = 'vapid_private'");
if (!pub?.value || !priv?.value) {
const keys = webpush.generateVAPIDKeys();
const ins = db.prepare("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?");
ins.run('vapid_public', keys.publicKey, keys.publicKey);
ins.run('vapid_private', keys.privateKey, keys.privateKey);
await exec(schema,
"INSERT INTO settings (key,value) VALUES ('vapid_public',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
[keys.publicKey]
);
await exec(schema,
"INSERT INTO settings (key,value) VALUES ('vapid_private',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
[keys.privateKey]
);
console.log('[Push] Generated new VAPID keys');
return keys;
}
return { publicKey: pub.value, privateKey: priv.value };
}
function initWebPush() {
const keys = getVapidKeys();
webpush.setVapidDetails(
'mailto:admin@jama.local',
keys.publicKey,
keys.privateKey
);
async function initWebPush(schema) {
const keys = await getVapidKeys(schema);
webpush.setVapidDetails('mailto:admin@jama.local', keys.publicKey, keys.privateKey);
return keys.publicKey;
}
// Export for use in index.js
let vapidPublicKey = null;
function getVapidPublicKey() {
if (!vapidPublicKey) vapidPublicKey = initWebPush();
return vapidPublicKey;
}
// Send a push notification to all subscriptions for a user
async function sendPushToUser(userId, payload) {
const db = getDb();
getVapidPublicKey(); // ensure webpush is configured
const subs = db.prepare('SELECT * FROM push_subscriptions WHERE user_id = ?').all(userId);
for (const sub of subs) {
try {
await webpush.sendNotification(
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
JSON.stringify(payload)
);
} catch (err) {
if (err.statusCode === 410 || err.statusCode === 404) {
// Subscription expired — remove it
db.prepare('DELETE FROM push_subscriptions WHERE id = ?').run(sub.id);
// Called from index.js socket push notifications — schema comes from caller
async function sendPushToUser(schema, userId, payload) {
try {
if (!vapidPublicKey) vapidPublicKey = await initWebPush(schema);
const subs = await query(schema, 'SELECT * FROM push_subscriptions WHERE user_id = $1', [userId]);
for (const sub of subs) {
try {
await webpush.sendNotification(
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
JSON.stringify(payload)
);
} catch (err) {
if (err.statusCode === 410 || err.statusCode === 404) {
await exec(schema, 'DELETE FROM push_subscriptions WHERE id = $1', [sub.id]);
}
}
}
} catch (e) {
console.error('[Push] sendPushToUser error:', e.message);
}
}
// GET /api/push/vapid-public — returns VAPID public key for client subscription
router.get('/vapid-public', (req, res) => {
res.json({ publicKey: getVapidPublicKey() });
router.get('/vapid-public', async (req, res) => {
try {
if (!vapidPublicKey) vapidPublicKey = await initWebPush(req.schema);
res.json({ publicKey: vapidPublicKey });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// POST /api/push/subscribe — save push subscription for current user
router.post('/subscribe', authMiddleware, (req, res) => {
router.post('/subscribe', authMiddleware, async (req, res) => {
const { endpoint, keys } = req.body;
if (!endpoint || !keys?.p256dh || !keys?.auth) {
if (!endpoint || !keys?.p256dh || !keys?.auth)
return res.status(400).json({ error: 'Invalid subscription' });
}
const db = getDb();
const device = req.device || 'desktop';
// Delete any existing subscription for this user+device or this endpoint, then insert fresh
db.prepare('DELETE FROM push_subscriptions WHERE endpoint = ? OR (user_id = ? AND device = ?)').run(endpoint, req.user.id, device);
db.prepare('INSERT INTO push_subscriptions (user_id, device, endpoint, p256dh, auth) VALUES (?, ?, ?, ?, ?)').run(req.user.id, device, endpoint, keys.p256dh, keys.auth);
res.json({ success: true });
try {
const device = req.device || 'desktop';
await exec(req.schema,
'DELETE FROM push_subscriptions WHERE endpoint = $1 OR (user_id = $2 AND device = $3)',
[endpoint, req.user.id, device]
);
await exec(req.schema,
'INSERT INTO push_subscriptions (user_id, device, endpoint, p256dh, auth) VALUES ($1,$2,$3,$4,$5)',
[req.user.id, device, endpoint, keys.p256dh, keys.auth]
);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// POST /api/push/generate-vapid — admin: generate (or regenerate) VAPID keys
router.post('/generate-vapid', authMiddleware, (req, res) => {
router.post('/generate-vapid', authMiddleware, async (req, res) => {
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admins only' });
const db = getDb();
const keys = webpush.generateVAPIDKeys();
const ins = db.prepare("INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?");
ins.run('vapid_public', keys.publicKey, keys.publicKey);
ins.run('vapid_private', keys.privateKey, keys.privateKey);
// Reinitialise webpush with new keys immediately
webpush.setVapidDetails('mailto:admin@jama.local', keys.publicKey, keys.privateKey);
vapidPublicKey = keys.publicKey;
console.log('[Push] VAPID keys regenerated by admin');
res.json({ publicKey: keys.publicKey });
try {
const keys = webpush.generateVAPIDKeys();
await exec(req.schema,
"INSERT INTO settings (key,value) VALUES ('vapid_public',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
[keys.publicKey]
);
await exec(req.schema,
"INSERT INTO settings (key,value) VALUES ('vapid_private',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
[keys.privateKey]
);
webpush.setVapidDetails('mailto:admin@jama.local', keys.publicKey, keys.privateKey);
vapidPublicKey = keys.publicKey;
res.json({ publicKey: keys.publicKey });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// POST /api/push/unsubscribe — remove subscription
router.post('/unsubscribe', authMiddleware, (req, res) => {
router.post('/unsubscribe', authMiddleware, async (req, res) => {
const { endpoint } = req.body;
if (!endpoint) return res.status(400).json({ error: 'Endpoint required' });
const db = getDb();
db.prepare('DELETE FROM push_subscriptions WHERE user_id = ? AND endpoint = ?').run(req.user.id, endpoint);
res.json({ success: true });
try {
await exec(req.schema,
'DELETE FROM push_subscriptions WHERE user_id = $1 AND endpoint = $2',
[req.user.id, endpoint]
);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
module.exports = { router, sendPushToUser, getVapidPublicKey };
module.exports = { router, sendPushToUser };