import type { NextRequest } from "next/server"; import { z } from "zod"; import { toJsonInput, toQrCodeDto } from "@/lib/codes"; import { prisma } from "@/lib/db"; import { errorResponse, firstZodMessage, jsonError } from "@/lib/http"; import { requireAvailableQrType } from "@/lib/qr/availability"; import { designSchema } from "@/lib/qr/design"; import { QR_TYPE_IDS, QrRequestError } from "@/lib/qr/type-module"; import { generateSlug } from "@/lib/slug"; export const dynamic = "force-dynamic"; const createSchema = z.object({ type: z.enum(QR_TYPE_IDS, { error: "Unbekannter QR-Typ." }), data: z.record(z.string(), z.unknown()), design: designSchema, label: z .string() .trim() .max(120, { error: "Der Name darf höchstens 120 Zeichen lang sein." }) .optional(), }); function isUniqueViolation(error: unknown): boolean { return ( typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "P2002" ); } /** Alle dynamischen Codes, neueste zuerst. */ export async function GET(): Promise { try { const codes = await prisma.qrCode.findMany({ orderBy: { createdAt: "desc" } }); return Response.json({ codes: codes.map(toQrCodeDto) }); } catch (error) { return errorResponse(error); } } /** Legt einen dynamischen Code an und vergibt dafür eine freie Kurz-URL. */ export async function POST(request: NextRequest): Promise { try { const parsed = createSchema.safeParse(await request.json()); if (!parsed.success) { return jsonError(firstZodMessage(parsed.error)); } const { type, design, label } = parsed.data; const qrType = await requireAvailableQrType(type); if (!qrType.supportsDynamic) { throw new QrRequestError( `Der QR-Typ „${qrType.title}“ unterstützt keine dynamischen Codes.`, ); } const validated = qrType.validate(parsed.data.data); if (!validated.ok) { return jsonError(validated.message); } // Slug-Kollisionen sind extrem unwahrscheinlich, aber nicht unmöglich. for (let attempt = 0; attempt < 5; attempt += 1) { try { const created = await prisma.qrCode.create({ data: { slug: generateSlug(), type: qrType.id, label: label && label.length > 0 ? label : null, data: toJsonInput(validated.data), ...design, }, }); return Response.json({ code: toQrCodeDto(created) }, { status: 201 }); } catch (error) { if (!isUniqueViolation(error)) throw error; } } throw new QrRequestError( "Es konnte keine freie Kurz-URL erzeugt werden. Bitte versuche es erneut.", 500, ); } catch (error) { return errorResponse(error); } }