Files
Arnaud 4a90ad910c feat(deploy): dockerize web app for portainer git-stack deploy behind external traefik
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>
2026-07-01 16:20:34 +02:00

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
)
)
);
}