import type { NextRequest } from "next/server"; import { getActor } from "@/lib/auth/actor"; import { requirePermission } from "@/lib/auth/policy"; import { prisma } from "@/lib/db"; import { shortUrl } from "@/lib/env"; import { requireImplementedQrType } from "@/lib/qr/availability"; import { errorResponse } from "@/lib/http"; import { designSchema } from "@/lib/qr/design"; import { getQrType } from "@/lib/qr/registry"; import { ladeLogo } from "@/lib/qr/logo"; import { CONTENT_TYPES, renderPng, renderSvg } from "@/lib/qr/render"; import { downloadFilename, QrRequestError } from "@/lib/qr/request"; export const dynamic = "force-dynamic"; type Context = { params: Promise<{ id: string }> }; /** * Rendert einen gespeicherten dynamischen Code mit seiner hinterlegten * Gestaltung. Wird für die Vorschaubilder in „Meine Codes“ und für die * Download-Links dort verwendet. */ export async function GET( request: NextRequest, context: Context, ): Promise { try { const { id } = await context.params; const format = request.nextUrl.searchParams.get("format") === "png" ? "png" : "svg"; const isDownload = request.nextUrl.searchParams.get("download") === "1"; const code = await prisma.qrCode.findUnique({ where: { id }, include: { domain: { select: { hostname: true } } }, }); if (!code) { throw new QrRequestError("Dieser QR-Code existiert nicht (mehr).", 404); } // Das Bild verrät die Kurz-URL und damit das Ziel — es gehört hinter // dieselbe Prüfung wie die Liste. requirePermission(await getActor(), "codes:read", { groupId: code.groupId }); const design = designSchema.parse({ fgColor: code.fgColor, bgColor: code.bgColor, size: code.size, ecLevel: code.ecLevel, margin: code.margin, logoSource: code.logoSource, logoUploadId: code.logoUploadId, logoScale: code.logoScale, }); const logo = await ladeLogo(design); // Was im Bild steckt, haengt an der Betriebsart: Ein dynamischer Code // traegt die Kurz-URL, ein statischer seinen Inhalt selbst. const content = code.mode === "static" || !code.slug ? requireImplementedQrType(code.type).encode(code.data) : shortUrl(code.slug, code.domain.hostname); const body = format === "png" ? new Uint8Array(await renderPng(content, design, logo)) : await renderSvg(content, design, logo); const headers: Record = { "Content-Type": CONTENT_TYPES[format], // Das Ziel eines dynamischen Codes ist änderbar; die Kurz-URL im Bild // nicht. Trotzdem kein Caching, damit Gestaltungsänderungen sofort // sichtbar werden. "Cache-Control": "no-store", }; if (isDownload) { const qrType = getQrType(code.type); const name = qrType ? downloadFilename(qrType, code.data, format) : `qr-${code.slug}.${format}`; headers["Content-Disposition"] = `attachment; filename="${name}"`; } return new Response(body, { headers }); } catch (error) { return errorResponse(error); } }