// Task Team — Push Notifications — 2026-03-29 const webpush = require("web-push"); async function notificationRoutes(app) { // Generate VAPID keys if not exist const vapidKeys = { publicKey: process.env.VAPID_PUBLIC_KEY || "", privateKey: process.env.VAPID_PRIVATE_KEY || "" }; if (!vapidKeys.publicKey) { const keys = webpush.generateVAPIDKeys(); vapidKeys.publicKey = keys.publicKey; vapidKeys.privateKey = keys.privateKey; console.log("VAPID keys generated. Add to env:", JSON.stringify(keys)); } webpush.setVapidDetails("mailto:admin@hasdo.info", vapidKeys.publicKey, vapidKeys.privateKey); // Create subscriptions table if not exists await app.db.query(` CREATE TABLE IF NOT EXISTS push_subscriptions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, subscription JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW() ) `); // Get VAPID public key app.get("/notifications/vapid-key", async () => { return { data: { publicKey: vapidKeys.publicKey } }; }); // Subscribe app.post("/notifications/subscribe", async (req) => { const { subscription, user_id } = req.body; await app.db.query( "INSERT INTO push_subscriptions (user_id, subscription) VALUES ($1, $2)", [user_id, JSON.stringify(subscription)] ); return { status: "subscribed" }; }); // Send notification (internal use) app.post("/notifications/send", async (req) => { const { user_id, title, body, url } = req.body; const { rows } = await app.db.query( "SELECT subscription FROM push_subscriptions WHERE user_id = $1", [user_id] ); let sent = 0; for (const row of rows) { try { await webpush.sendNotification( JSON.parse(row.subscription), JSON.stringify({ title, body, url: url || "/tasks" }) ); sent++; } catch (e) { if (e.statusCode === 410) { await app.db.query("DELETE FROM push_subscriptions WHERE subscription = $1", [row.subscription]); } } } return { status: "sent", count: sent }; }); // Unsubscribe app.delete("/notifications/unsubscribe", async (req) => { const { user_id } = req.body; await app.db.query("DELETE FROM push_subscriptions WHERE user_id = $1", [user_id]); return { status: "unsubscribed" }; }); } module.exports = notificationRoutes;