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,69 @@
|
||||
import "server-only";
|
||||
|
||||
import type { Prisma, QrCode } from "@/generated/prisma/client";
|
||||
import type { QrCodeDto } from "@/lib/dto";
|
||||
import { shortUrl } from "@/lib/env";
|
||||
import type { EcLevel } from "@/lib/qr/design";
|
||||
import { getQrType } from "@/lib/qr/registry";
|
||||
import type { QrTypeId } from "@/lib/qr/type-module";
|
||||
|
||||
export type { QrCodeDto };
|
||||
|
||||
/**
|
||||
* Die Typ-Module liefern validierte, JSON-serialisierbare Objekte. Prisma
|
||||
* kann das aus `Record<string, unknown>` nicht ableiten, deshalb hier die
|
||||
* einzige Stelle mit einer expliziten Umdeutung.
|
||||
*/
|
||||
export function toJsonInput(
|
||||
data: Record<string, unknown>,
|
||||
): Prisma.InputJsonValue {
|
||||
return data as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return value !== null && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
export function toQrCodeDto(code: QrCode): QrCodeDto {
|
||||
const data = asRecord(code.data);
|
||||
const module = getQrType(code.type);
|
||||
|
||||
let target = "";
|
||||
let summary = "";
|
||||
if (module) {
|
||||
try {
|
||||
target = module.redirectTarget(data);
|
||||
summary = module.summary(data);
|
||||
} catch {
|
||||
// Datensatz passt nicht mehr zum Typ-Schema (z. B. nach einem
|
||||
// Schema-Wechsel). Die Liste soll trotzdem anzeigbar bleiben.
|
||||
summary = "Ungültige Daten";
|
||||
}
|
||||
} else {
|
||||
summary = "Unbekannter Typ";
|
||||
}
|
||||
|
||||
return {
|
||||
id: code.id,
|
||||
slug: code.slug,
|
||||
type: code.type as QrTypeId,
|
||||
label: code.label,
|
||||
data,
|
||||
shortUrl: shortUrl(code.slug),
|
||||
target,
|
||||
summary,
|
||||
design: {
|
||||
fgColor: code.fgColor,
|
||||
bgColor: code.bgColor,
|
||||
size: code.size,
|
||||
ecLevel: code.ecLevel as EcLevel,
|
||||
margin: code.margin,
|
||||
},
|
||||
scans: code.scans,
|
||||
lastScanAt: code.lastScanAt?.toISOString() ?? null,
|
||||
createdAt: code.createdAt.toISOString(),
|
||||
updatedAt: code.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user