import "server-only"; import type { Prisma, QrCode } from "@/generated/prisma/client"; import type { QrCodeDto } from "@/lib/dto"; import { landingUrl, 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` nicht ableiten, deshalb hier die * einzige Stelle mit einer expliziten Umdeutung. */ export function toJsonInput( data: Record, ): Prisma.InputJsonValue { return data as Prisma.InputJsonValue; } function asRecord(value: unknown): Record { return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; } /** Ein Code samt der Beziehungen, die für die Anzeige gebraucht werden. */ export type QrCodeWithRelations = QrCode & { group?: { name: string } | null; domain?: { hostname: string } | null; createdBy?: { name: string | null; email: string } | null; aliases?: { slug: string }[]; }; export function toQrCodeDto(code: QrCodeWithRelations): QrCodeDto { const data = asRecord(code.data); const qrType = getQrType(code.type); const istStatisch = code.mode === "static"; let target = ""; let summary = ""; if (qrType) { try { // Ein statischer Code führt nirgendwohin — sein Inhalt steckt in ihm // selbst. Als „Ziel“ bleibt deshalb nichts übrig; die Zusammenfassung // sagt trotzdem, was drinsteht. // Typen mit eigener Landingpage führen auf eine Seite dieser Anwendung. // Die ist als „Ziel“ das Ehrlichste — und obendrein anklickbar, um sie // sich anzusehen. target = istStatisch || !code.slug ? "" : qrType.hasLandingPage ? landingUrl(code.slug, code.domain?.hostname) : qrType.redirectTarget(data); summary = qrType.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, mode: istStatisch ? "static" : "dynamic", slug: code.slug, aliases: (code.aliases ?? []).map((alias) => alias.slug), type: code.type as QrTypeId, label: code.label, data, shortUrl: code.slug ? shortUrl(code.slug, code.domain?.hostname) : null, 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(), groupId: code.groupId, groupName: code.group?.name ?? null, domainId: code.domainId, domainHostname: code.domain?.hostname ?? null, createdByName: code.createdBy ? (code.createdBy.name?.trim() || code.createdBy.email) : null, }; }