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:
Florian Wilde
2026-08-13 15:54:47 +02:00
commit ad4cd92f8e
52 changed files with 12348 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
import "server-only";
import QRCode, { type QRCodeRenderersOptions } from "qrcode";
import type { QrDesign } from "@/lib/qr/design";
/** Fehler beim Erzeugen des Codes, mit Meldung für die Oberfläche. */
export class QrRenderError extends Error {}
function toOptions(design: QrDesign): QRCodeRenderersOptions {
return {
errorCorrectionLevel: design.ecLevel,
margin: design.margin,
width: design.size,
color: {
dark: design.fgColor,
light: design.bgColor,
},
};
}
function wrap(error: unknown): never {
const detail = error instanceof Error ? error.message : String(error);
if (/too (long|big)|code length overflow|data too long/i.test(detail)) {
throw new QrRenderError(
"Der Inhalt ist zu lang für einen QR-Code. Kürze die Adresse oder wähle ein niedrigeres Fehlerkorrektur-Level.",
);
}
throw new QrRenderError(`Der QR-Code konnte nicht erzeugt werden: ${detail}`);
}
export async function renderSvg(
content: string,
design: QrDesign,
): Promise<string> {
try {
return await QRCode.toString(content, { ...toOptions(design), type: "svg" });
} catch (error) {
wrap(error);
}
}
export async function renderPng(
content: string,
design: QrDesign,
): Promise<Buffer> {
try {
return await QRCode.toBuffer(content, { ...toOptions(design), type: "png" });
} catch (error) {
wrap(error);
}
}
export type QrFormat = "png" | "svg";
export const CONTENT_TYPES: Record<QrFormat, string> = {
png: "image/png",
svg: "image/svg+xml",
};