- Goals CRUD API + AI study plan generator + progress reports - Goals frontend page with progress bars - Team tasks: assign, transfer, collaborate endpoints - Push notifications: web-push, VAPID, subscribe/send - i18n: 4 languages (cs, he, ru, ua) translation files - notifications.js + goals.js routes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
78 lines
2.7 KiB
JavaScript
78 lines
2.7 KiB
JavaScript
// 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;
|