import webpush from "web-push"; import type { ApprovalType, BudgetReleaseNotifyTarget } from "@prisma/client"; import { approvalLabel } from "@/lib/domain"; import prisma from "@/lib/prisma"; type PushTargetExpense = { id: string; title: string; amount: number; workingGroupId: string; }; type PushTargetBudgetRelease = { id: string; name: string; workingGroupId: string; workingGroupName: string; releasedAmount: number; }; function configureWebPush() { const publicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY; const privateKey = process.env.VAPID_PRIVATE_KEY; const subject = process.env.VAPID_SUBJECT; if (!publicKey || !privateKey || !subject) { return false; } webpush.setVapidDetails(subject, publicKey, privateKey); return true; } export function getApprovalRole(approvalType: ApprovalType) { switch (approvalType) { case "CHAIR_A": return "ORGA"; case "CHAIR_B": return "BOARD"; case "FINANCE": return "FINANCE"; } } export async function notifyApprovalRequest(expense: PushTargetExpense, approvalTypes: ApprovalType[]) { if (!configureWebPush() || approvalTypes.length === 0) { return; } const roles = approvalTypes.map(getApprovalRole); const subscriptions = await prisma.pushSubscription.findMany({ where: { user: { role: { in: roles } } }, include: { user: { select: { role: true } } } }); await Promise.all( subscriptions.map(async (subscription) => { const approvalType = approvalTypes.find((type) => getApprovalRole(type) === subscription.user.role); const payload = JSON.stringify({ title: "Freigabe angefragt", body: `${expense.title} (${expense.amount.toFixed(2)} EUR) braucht ${approvalType ? approvalLabel(approvalType) : "deine Freigabe"}.`, url: `/?expense=${encodeURIComponent(expense.id)}&group=${encodeURIComponent(expense.workingGroupId)}`, tag: `approval-${expense.id}-${subscription.user.role}` }); try { await webpush.sendNotification( { endpoint: subscription.endpoint, keys: { p256dh: subscription.p256dh, auth: subscription.auth } }, payload ); } catch (error) { const statusCode = typeof error === "object" && error && "statusCode" in error ? error.statusCode : null; if (statusCode === 404 || statusCode === 410) { await prisma.pushSubscription.delete({ where: { endpoint: subscription.endpoint } }); } } }) ); } export async function notifyBudgetRelease(budget: PushTargetBudgetRelease, target: BudgetReleaseNotifyTarget) { if (!configureWebPush()) { return; } const subscriptions = await prisma.pushSubscription.findMany({ where: { user: { workingGroupId: budget.workingGroupId, ...(target === "GROUP_MEMBERS_ONLY" ? { role: "MEMBER" } : {}) } } }); await Promise.all( subscriptions.map(async (subscription) => { const payload = JSON.stringify({ title: "Budget freigegeben", body: `${budget.workingGroupName}: ${budget.name} wurde mit ${budget.releasedAmount.toFixed(2)} EUR freigegeben.`, url: `/?budget=${encodeURIComponent(budget.id)}&group=${encodeURIComponent(budget.workingGroupId)}`, tag: `budget-release-${budget.id}` }); try { await webpush.sendNotification( { endpoint: subscription.endpoint, keys: { p256dh: subscription.p256dh, auth: subscription.auth } }, payload ); } catch (error) { const statusCode = typeof error === "object" && error && "statusCode" in error ? error.statusCode : null; if (statusCode === 404 || statusCode === 410) { await prisma.pushSubscription.delete({ where: { endpoint: subscription.endpoint } }); } } }) ); }