V1: Next.js-App mit QR-Typ-Registry, Website-Generator und dynamischen Codes
- Erweiterbare Registry aller 15 QR-Typen; nur "Website" ist aktiv, die
uebrigen erscheinen als Platzhalter mit Badge "Bald verfuegbar".
- defineQrType() kapselt Zod-Schema, Kodierung und Redirect-Ziel je Typ,
sodass ein V2-Typ nur aus einem Registry-Eintrag plus Formular besteht.
- Serverseitige QR-Erzeugung (PNG/SVG) mit Farben, Groesse, Fehlerkorrektur
und Rand; Live-Vorschau ueber /api/qr/preview.
- Dynamische Codes: Kurz-URL {BASE_URL}/r/{slug}, 302-Redirect mit
Scan-Zaehler, CRUD unter /api/codes.
- Prisma 7 mit pg-Driver-Adapter und Initial-Migration fuer PostgreSQL.
- Deutsche Oberflaeche mit Tailwind CSS, helles Theme, responsiv.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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 { designSchema } from "@/lib/qr/design";
|
||||
import { getActiveQrType } from "@/lib/qr/registry";
|
||||
import { QrRequestError } from "@/lib/qr/request";
|
||||
import { QR_TYPE_IDS } 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<Response> {
|
||||
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<Response> {
|
||||
try {
|
||||
const parsed = createSchema.safeParse(await request.json());
|
||||
if (!parsed.success) {
|
||||
return jsonError(firstZodMessage(parsed.error));
|
||||
}
|
||||
|
||||
const { type, design, label } = parsed.data;
|
||||
const module = getActiveQrType(type);
|
||||
if (!module) {
|
||||
throw new QrRequestError(
|
||||
"Dieser QR-Typ ist in dieser Version noch nicht verfügbar.",
|
||||
404,
|
||||
);
|
||||
}
|
||||
if (!module.supportsDynamic) {
|
||||
throw new QrRequestError(
|
||||
`Der QR-Typ „${module.title}" unterstützt keine dynamischen Codes.`,
|
||||
);
|
||||
}
|
||||
|
||||
const validated = module.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: module.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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user