feat: add household emergency duty alarms and deadline worker

This commit is contained in:
t.indorf
2026-10-09 11:02:24 +02:00
parent a96ba0fd84
commit 8b32e4a832
34 changed files with 3947 additions and 646 deletions
+30 -25
View File
@@ -1,34 +1,39 @@
"use server";
import { revalidatePath } from "next/cache";
import { NotdienstAlertStatus } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { respondToAlarm } from "@/lib/alarms/service";
import { deliverAlarmEmails } from "@/lib/alarms/email";
import { rateLimit } from "@/lib/rate-limit";
export async function confirmAlertAction(token: string) {
export async function respondAlertAction(token: string, approve: boolean) {
if (typeof token !== "string" || token.length > 128)
return { error: "Ungültiger Rückmeldelink." };
if (!rateLimit(`alarm-response:${token}`, 12, 60).allowed)
return { error: "Bitte kurz warten und erneut versuchen." };
try {
const alert = await prisma.notdienstAlert.findUnique({
where: { confirmationToken: token },
});
if (!alert) {
return { error: "Ungültiger Bestätigungs-Link." };
const result = await respondToAlarm(token, approve);
// The household response is already committed; transport failure must not undo it.
try {
await deliverAlarmEmails(result.emailIds);
} catch {
/* Persistent jobs will retry. */
}
if (alert.status !== NotdienstAlertStatus.PENDING) {
return { error: "Dieser Alarm wurde bereits bestätigt oder abgebrochen." };
}
await prisma.notdienstAlert.update({
where: { id: alert.id },
data: {
status: NotdienstAlertStatus.CONFIRMED,
confirmedAt: new Date(),
},
});
revalidatePath("/alert/[token]", "page");
revalidatePath("/dashboard");
revalidatePath("/dashboard/notdienst/alarm");
return { success: true };
} catch {
return { error: "Fehler bei der Bestätigung." };
} catch (error) {
if (
error instanceof Error &&
!error.message.includes("prisma") &&
!error.message.includes("Invalid `")
)
return { error: error.message };
return {
error:
"Die Rückmeldung konnte nicht gespeichert werden. Bitte den aktuellen Stand prüfen.",
};
}
}
export async function confirmAlertAction(token: string) {
return respondAlertAction(token, true);
}