4a90ad910c
Add root Dockerfile (standalone Next output, multi-stage pnpm build), drop in-stack caddy in favor of publishing web's port for an external traefik LXC (file-provider dynamic config included), and document the portainer deploy flow. Also fixes issues that blocked any production build: a bad auth-client type cast, the ai SDK's mimeType->mediaType rename, an implicit-any callback param, and push.ts eagerly calling webpush.setVapidDetails at module import time (which crashed page-data collection whenever VAPID env vars weren't present at build) — now lazily configured on first send. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
41 lines
994 B
TypeScript
41 lines
994 B
TypeScript
import webpush from "web-push";
|
|
import { db, pushSubscriptions, eq } from "@epicure/db";
|
|
|
|
let vapidConfigured = false;
|
|
|
|
function ensureVapidConfigured(): void {
|
|
if (vapidConfigured) return;
|
|
webpush.setVapidDetails(
|
|
"mailto:contact@epicure.app",
|
|
process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
|
|
process.env.VAPID_PRIVATE_KEY!
|
|
);
|
|
vapidConfigured = true;
|
|
}
|
|
|
|
export async function sendPushNotification(
|
|
userId: string,
|
|
notification: { title: string; body: string; url?: string }
|
|
): Promise<void> {
|
|
ensureVapidConfigured();
|
|
const subs = await db
|
|
.select()
|
|
.from(pushSubscriptions)
|
|
.where(eq(pushSubscriptions.userId, userId));
|
|
|
|
const payload = JSON.stringify({
|
|
title: notification.title,
|
|
body: notification.body,
|
|
url: notification.url ?? "/",
|
|
});
|
|
|
|
await Promise.allSettled(
|
|
subs.map((sub) =>
|
|
webpush.sendNotification(
|
|
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
|
payload
|
|
)
|
|
)
|
|
);
|
|
}
|