Phase A: acht weitere QR-Typen
E-Mail, SMS, WhatsApp, WLAN, Standort, vCard, Zahlung und App-Link sind umgesetzt. Damit sind 9 der 15 Typen nutzbar. Erweiterungen am Typ-Vertrag, die dafuer noetig waren: - supportsStatic: nicht jeder Typ kann statisch. Beim App-Link steht das Ziel erst beim Scannen fest, also gibt es ihn nur dynamisch. - RedirectContext: die Weiterleitung reicht den User-Agent durch, damit der App-Link iOS, Android und alles Uebrige unterscheiden kann. - assertDynamicPossible(): supportsDynamic ist eine Eigenschaft des Typs, aber nicht jede Eingabe taugt dafuer. Ein GiroCode ist kein Web-Ziel, obwohl der Typ 'Zahlung' als Zahlungslink sehr wohl dynamisch sein kann. Ohne diese Probe liess sich ein Code speichern, der erst beim Scannen aufgefallen waere - also nach dem Drucken. Greift beim Anlegen und beim Bearbeiten. Sorgfalt bei den Formaten, gesammelt in src/lib/qr/encoding.ts: - WLAN maskiert ; , : \\ und setzt rein hexadezimale Werte in Anfuehrungszeichen, sonst deuten Geraete sie als Hexzahl. - vCard 3.0 mit CRLF, Maskierung und Faltung nach 75 Oktetten, oktettweise gezaehlt, damit kein mehrbytiges Zeichen zerrissen wird. - GiroCode nach EPC069-12 mit fester Feldreihenfolge, IBAN-Pruefsumme nach Modulo 97 und der 331-Byte-Grenze. - Rufnummern werden nach E.164 normalisiert, '(0)' faellt weg. - Standort nimmt Koordinaten auch aus einem Google-Maps- oder Apple-Link an. Formular-Bausteine (Select, Textarea, Checkbox, FieldRow, FieldSet) fuer die mehrfeldrigen Typen ergaenzt. Geprueft: 225 Pruefungen, davon 80 neu - Dekodierung je Typ aus dem fertigen PNG, Validierung, Betriebsarten, geraeteabhaengige Weiterleitung und die Formulare im Browser.
This commit is contained in:
@@ -7,6 +7,7 @@ import { prisma } from "@/lib/db";
|
||||
import { errorResponse, firstZodMessage, jsonError } from "@/lib/http";
|
||||
import { requireImplementedQrType } from "@/lib/qr/availability";
|
||||
import { designSchema } from "@/lib/qr/design";
|
||||
import { assertDynamicPossible } from "@/lib/qr/request";
|
||||
import { QrRequestError } from "@/lib/qr/type-module";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -68,6 +69,10 @@ export async function PATCH(
|
||||
if (!validated.ok) {
|
||||
return jsonError(validated.message);
|
||||
}
|
||||
// Auch beim Bearbeiten: die neuen Daten müssen ein Weiterleitungsziel
|
||||
// hergeben. Sonst könnte man einen laufenden Code durch eine Änderung
|
||||
// stillschweigend unbrauchbar machen.
|
||||
assertDynamicPossible(qrType, validated.data);
|
||||
update.data = toJsonInput(validated.data);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { prisma } from "@/lib/db";
|
||||
import { errorResponse, firstZodMessage, jsonError } from "@/lib/http";
|
||||
import { requireAvailableQrType } from "@/lib/qr/availability";
|
||||
import { designSchema } from "@/lib/qr/design";
|
||||
import { assertDynamicPossible } from "@/lib/qr/request";
|
||||
import { QR_TYPE_IDS, QrRequestError } from "@/lib/qr/type-module";
|
||||
import { generateSlug } from "@/lib/slug";
|
||||
|
||||
@@ -51,17 +52,15 @@ export async function POST(request: NextRequest): Promise<Response> {
|
||||
|
||||
const { type, design, label } = parsed.data;
|
||||
const qrType = await requireAvailableQrType(type);
|
||||
if (!qrType.supportsDynamic) {
|
||||
throw new QrRequestError(
|
||||
`Der QR-Typ „${qrType.title}“ unterstützt keine dynamischen Codes.`,
|
||||
);
|
||||
}
|
||||
|
||||
const validated = qrType.validate(parsed.data.data);
|
||||
if (!validated.ok) {
|
||||
return jsonError(validated.message);
|
||||
}
|
||||
|
||||
// Erst hier, nach der Validierung: die Probe braucht die geprüften Daten.
|
||||
assertDynamicPossible(qrType, validated.data);
|
||||
|
||||
// Slug-Kollisionen sind extrem unwahrscheinlich, aber nicht unmöglich.
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
try {
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||
|
||||
<footer className="border-t border-slate-200 bg-white">
|
||||
<div className="mx-auto max-w-6xl px-4 py-6 text-sm text-slate-500 sm:px-6">
|
||||
QR-Code-Generator · Version 1 — QR-Typ „Website/URL“
|
||||
QR-Code-Generator · statische und dynamische QR-Codes
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
@@ -36,7 +36,7 @@ function notFound(message: string): Response {
|
||||
* Redirect würde in Browsern und Zwischenspeichern hängen bleiben.
|
||||
*/
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
request: NextRequest,
|
||||
context: Context,
|
||||
): Promise<Response> {
|
||||
const { slug } = await context.params;
|
||||
@@ -59,7 +59,11 @@ export async function GET(
|
||||
|
||||
let target: string;
|
||||
try {
|
||||
target = qrType.redirectTarget(code.data);
|
||||
// Der User-Agent geht mit: Typen wie der App-Link entscheiden daran,
|
||||
// wohin die Weiterleitung führt.
|
||||
target = qrType.redirectTarget(code.data, {
|
||||
userAgent: request.headers.get("user-agent") ?? undefined,
|
||||
});
|
||||
} catch {
|
||||
return notFound("Das Ziel dieses Codes ist nicht mehr gültig.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user