- 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.
60 lines
1.5 KiB
TypeScript
60 lines
1.5 KiB
TypeScript
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",
|
|
};
|