fix: final review wave (reminder-400, contact-400, soon-30, cron-claim, edge-session)

This commit is contained in:
opencode committed 2026-10-18 10:38:57 +02:00
1 parent 39f782057c
commit 98387bd2d2
10 files changed
+122 -29

No files matched your search

+36 -2
View File
@@ -9,7 +9,40 @@ async function alreadySent(db: ReturnType<typeof getDb>, ruleId: number, kanal:
const found = await db.notificationLog.findUnique({
where: { ruleId_kanal_empfaenger_zielDatum: { ruleId, kanal, empfaenger, zielDatum } },
});
return found?.status === "gesendet";
return found?.status === "gesendet" || found?.status === "versendet";
}
// Atomarer Claim vor dem Versand: erster Gewinner legt eine "versendet"-Zeile an,
// ein Konkurrent mit Unique-Konflikt (P2002) wird abgewiesen. "fehler" bleibt retrybar.
async function claimSlot(
db: ReturnType<typeof getDb>,
ruleId: number,
kanal: string,
empfaenger: string,
zielDatum: string
): Promise<boolean> {
try {
await db.notificationLog.create({
data: { ruleId, kanal, empfaenger, zielDatum, status: "versendet" },
});
return true;
} catch (e) {
if (typeof e === "object" && e !== null && "code" in e && (e as { code: string }).code === "P2002") {
const existing = await db.notificationLog.findUnique({
where: { ruleId_kanal_empfaenger_zielDatum: { ruleId, kanal, empfaenger, zielDatum } },
});
// Fehler-Logs erneut versuchen: auf pending zurücksetzen und senden.
if (existing?.status === "fehler") {
await db.notificationLog.update({
where: { id: existing.id },
data: { status: "versendet", fehler: null },
});
return true;
}
return false;
}
throw e;
}
}
async function logResult(
@@ -75,8 +108,9 @@ export async function GET(req: NextRequest) {
if (vc.contact.email) targets.push({ kanal: "mail", empfaenger: vc.contact.email });
if (vc.contact.telegramChatId) targets.push({ kanal: "telegram", empfaenger: vc.contact.telegramChatId });
for (const t of targets) {
// Unique vor Versand prüfen (Idempotenz: kein Doppelversand).
// Atomar claimen vor Versand (Idempotenz: kein Doppelversand bei parallelen Läufen).
if (await alreadySent(db, ruleId, t.kanal, t.empfaenger, zielDatum)) continue;
if (!(await claimSlot(db, ruleId, t.kanal, t.empfaenger, zielDatum))) continue;
try {
if (t.kanal === "mail") {
await sendMail(t.empfaenger, `Gutschein "${v.name}" läuft bald ab`, text);