"use server"; /** * Server-Actions des Adminbereichs. JEDE Action prueft zuerst selbst die * Anmeldung inkl. 2FA (requireAdminAction) — unabhaengig davon, ob die * Seite geschuetzt ist, denn Actions sind eigenstaendig aufrufbar. */ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { adminActor, requireAdminAction } from "@/lib/admin-auth"; import { campInputSchema, cancelBooking, confirmReviewedBooking, refundInstallment, resendConfirmation, saveVoucher, updateCamp, voucherInputSchema, type ActionResult, } from "@/lib/admin-service"; import { getDb } from "@/lib/db"; import { requireAppSecret } from "@/lib/env"; import { getGateway } from "@/lib/payment-gateway"; function back(path: string, result: ActionResult): never { const param = result.ok ? "ok" : "fehler"; redirect(`${path}?${param}=${encodeURIComponent(result.message)}`); } function str(form: FormData, key: string): string { const value = form.get(key); return typeof value === "string" ? value : ""; } function bool(form: FormData, key: string): boolean { return form.get(key) === "on"; } function firstIssue(error: { issues: { message: string }[] }): string { return error.issues[0]?.message ?? "Ungültige Eingabe."; } const ID = /^[A-Za-z0-9_-]{8,64}$/; export async function cancelBookingAction(form: FormData) { const admin = await requireAdminAction(); const id = str(form, "bookingId"); if (!ID.test(id)) back("/admin/buchungen", { ok: false, message: "Ungültige Buchung." }); const result = await cancelBooking(getDb(), getGateway(), { bookingId: id, reason: str(form, "reason") }, adminActor(admin)); revalidatePath(`/admin/buchungen/${id}`); back(`/admin/buchungen/${id}`, result); } export async function refundAction(form: FormData) { const admin = await requireAdminAction(); const bookingId = str(form, "bookingId"); const installmentId = str(form, "installmentId"); const requestKey = str(form, "requestKey"); if (!ID.test(bookingId) || !ID.test(installmentId) || !/^[0-9a-f-]{36}$/.test(requestKey)) { back("/admin/buchungen", { ok: false, message: "Ungültige Anfrage." }); } const euros = Number(str(form, "amount").replace(",", ".")); const amountCents = Math.round(euros * 100); const result = await refundInstallment( getDb(), getGateway(), { installmentId, amountCents, requestKey, reason: str(form, "reason") }, adminActor(admin), ); revalidatePath(`/admin/buchungen/${bookingId}`); back(`/admin/buchungen/${bookingId}`, result); } export async function confirmReviewAction(form: FormData) { const admin = await requireAdminAction(); const id = str(form, "bookingId"); if (!ID.test(id)) back("/admin/buchungen", { ok: false, message: "Ungültige Buchung." }); const result = await confirmReviewedBooking(getDb(), id, adminActor(admin)); revalidatePath(`/admin/buchungen/${id}`); back(`/admin/buchungen/${id}`, result); } export async function resendConfirmationAction(form: FormData) { const admin = await requireAdminAction(); const id = str(form, "bookingId"); if (!ID.test(id)) back("/admin/buchungen", { ok: false, message: "Ungültige Buchung." }); const result = await resendConfirmation(getDb(), id, requireAppSecret(), adminActor(admin)); back(`/admin/buchungen/${id}`, result); } export async function saveVoucherAction(form: FormData) { const admin = await requireAdminAction(); const id = str(form, "voucherId") || null; if (id && !ID.test(id)) back("/admin/gutscheine", { ok: false, message: "Ungültiger Gutschein." }); const parsed = voucherInputSchema.safeParse({ code: str(form, "code"), active: bool(form, "active"), validFrom: str(form, "validFrom"), validUntil: str(form, "validUntil"), maxRedemptions: str(form, "maxRedemptions"), boundEmail: str(form, "boundEmail"), internalNote: str(form, "internalNote"), }); const target = id ? `/admin/gutscheine/${id}` : "/admin/gutscheine"; if (!parsed.success) back(target, { ok: false, message: firstIssue(parsed.error) }); const result = await saveVoucher(getDb(), id, parsed.data, adminActor(admin)); revalidatePath("/admin/gutscheine"); back(result.ok && result.id ? `/admin/gutscheine/${result.id}` : target, result); } export async function updateCampAction(form: FormData) { const admin = await requireAdminAction(); const parsed = campInputSchema.safeParse({ salesEnabled: bool(form, "salesEnabled"), capacity: str(form, "capacity"), capacityConfirmed: bool(form, "capacityConfirmed"), stammmodelQuota: str(form, "stammmodelQuota"), stammmodelQuotaConfirmed: bool(form, "stammmodelQuotaConfirmed"), campStartDate: str(form, "campStartDate"), campEndDate: str(form, "campEndDate"), dateConfirmed: bool(form, "dateConfirmed"), locationName: str(form, "locationName"), locationRegion: str(form, "locationRegion"), locationConfirmed: bool(form, "locationConfirmed"), accommodationText: str(form, "accommodationText"), accommodationConfirmed: bool(form, "accommodationConfirmed"), servicesConfirmed: bool(form, "servicesConfirmed"), organizerName: str(form, "organizerName"), organizerAddress: str(form, "organizerAddress"), organizerEmail: str(form, "organizerEmail"), organizerPhone: str(form, "organizerPhone"), organizerConfirmed: bool(form, "organizerConfirmed"), taxNote: str(form, "taxNote"), taxConfirmed: bool(form, "taxConfirmed"), termsVersion: str(form, "termsVersion"), termsUrl: str(form, "termsUrl"), termsApproved: bool(form, "termsApproved"), withdrawalText: str(form, "withdrawalText"), withdrawalApproved: bool(form, "withdrawalApproved"), travelLawChecked: bool(form, "travelLawChecked"), installmentsApproved: bool(form, "installmentsApproved"), installmentRuleApproved: bool(form, "installmentRuleApproved"), reservationMinutes: str(form, "reservationMinutes"), finalInstallmentLeadDays: str(form, "finalInstallmentLeadDays"), minInstallmentGapDays: str(form, "minInstallmentGapDays"), testPhaseOverride: str(form, "testPhaseOverride"), }); if (!parsed.success) back("/admin/einstellungen", { ok: false, message: firstIssue(parsed.error) }); const result = await updateCamp(getDb(), parsed.data, adminActor(admin)); revalidatePath("/admin", "layout"); revalidatePath("/"); back("/admin/einstellungen", result); }