fix: final review wave (reminder-400, contact-400, soon-30, cron-claim, edge-session)
This commit is contained in:
1 parent
39f782057c
commit
98387bd2d2
10 files changed
+122
-29
No files matched your search
@@ -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);
|
||||
|
||||
@@ -37,6 +37,23 @@ export async function POST(req: NextRequest) {
|
||||
const contactIds: number[] = Array.isArray(body.contactIds)
|
||||
? body.contactIds.map(Number).filter((n: number) => Number.isInteger(n))
|
||||
: [];
|
||||
if (contactIds.length) {
|
||||
const found = await db.contact.findMany({ where: { id: { in: contactIds } }, select: { id: true } });
|
||||
if (found.length !== new Set(contactIds).size) {
|
||||
return NextResponse.json({ errors: ["contactIds"] }, { status: 400 });
|
||||
}
|
||||
}
|
||||
const reminderRaw =
|
||||
typeof body.reminderDays === "string"
|
||||
? body.reminderDays
|
||||
: Array.isArray(body.reminderDays)
|
||||
? body.reminderDays.join(",")
|
||||
: undefined;
|
||||
const reminderProvided = reminderRaw !== undefined && reminderRaw.trim() !== "";
|
||||
const reminderDays = parseReminderDays(reminderRaw);
|
||||
if (reminderProvided && reminderDays.length === 0) {
|
||||
return NextResponse.json({ errors: ["reminderDays"] }, { status: 400 });
|
||||
}
|
||||
const created = await db.voucher.create({
|
||||
data: {
|
||||
name: input.name.trim(),
|
||||
@@ -49,9 +66,7 @@ export async function POST(req: NextRequest) {
|
||||
notiz: input.notiz,
|
||||
contacts: contactIds.length ? { create: contactIds.map((contactId) => ({ contactId })) } : undefined,
|
||||
rules: {
|
||||
create: parseReminderDays(
|
||||
typeof body.reminderDays === "string" ? body.reminderDays : Array.isArray(body.reminderDays) ? body.reminderDays.join(",") : undefined
|
||||
).map((tageVorher) => ({ tageVorher })),
|
||||
create: reminderDays.map((tageVorher) => ({ tageVorher })),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user