Verwaltung: QR-Typen einzeln freigeben oder ausblenden

Neue Seite Verwaltung -> QR-Typen mit einem Schalter je Typ. Abgeschaltete
Typen verschwinden vollstaendig von der Startseite und lassen sich nicht
mehr neu anlegen.

- Neue Tabelle QrTypeSetting; fehlender Eintrag heisst freigegeben, damit
  eine frische Installation ohne Vorbelegung laeuft und spaetere Typen
  automatisch erscheinen.
- Registry-Feld 'active' heisst jetzt 'implemented' und beschreibt nur noch,
  ob der Typ ausprogrammiert ist. Ob er erscheint, entscheidet zusaetzlich
  die Freigabe aus der Datenbank (src/lib/qr/availability.ts).
- Bestehende dynamische Codes bleiben von einer Abschaltung unberuehrt: die
  Weiterleitung funktioniert weiter und Bearbeiten bleibt moeglich. Ein
  gedruckter Code darf durch einen Schalter nicht ungueltig werden.
- resolveQrRequest ist dadurch asynchron geworden; QrRequestError liegt jetzt
  in type-module.ts, sonst gaebe es einen Ringschluss mit availability.ts.
- Startseite meldet jetzt auch den Fall, dass nur noch Platzhalter
  freigegeben sind - vorher standen dort 14 tote Kacheln ohne Erklaerung.
- Kopfzeile: Schriftzug auf schmalen Geraeten ausgeblendet, sonst passte die
  dritte Navigationsschaltflaeche nicht mehr in die Zeile (7px Ueberlauf).
This commit is contained in:
Florian Wilde
2026-08-14 10:48:21 +02:00
parent 1e9e37f08d
commit c6da20f419
18 changed files with 684 additions and 148 deletions
+15 -19
View File
@@ -1,8 +1,14 @@
import "server-only";
import { z } from "zod";
import { requireAvailableQrType } from "@/lib/qr/availability";
import { designSchema } from "@/lib/qr/design";
import { getActiveQrType } from "@/lib/qr/registry";
import { QR_TYPE_IDS, type QrTypeModule } from "@/lib/qr/type-module";
import {
QR_TYPE_IDS,
QrRequestError,
type QrTypeModule,
} from "@/lib/qr/type-module";
import { shortUrl } from "@/lib/env";
import { isValidSlug } from "@/lib/slug";
@@ -30,15 +36,9 @@ export const qrRequestSchema = z.object({
export type QrRequest = z.infer<typeof qrRequestSchema>;
/** Fehler mit einer Meldung, die unverändert an die Oberfläche geht. */
export class QrRequestError extends Error {
constructor(
message: string,
readonly status: number = 400,
) {
super(message);
}
}
// Liegt in type-module.ts, damit auch availability.ts ihn werfen kann, ohne
// dass ein Ringschluss zwischen den beiden Modulen entsteht.
export { QrRequestError };
export type ResolvedQrRequest = {
qrType: QrTypeModule;
@@ -51,14 +51,10 @@ export type ResolvedQrRequest = {
* Prüft eine Anfrage vollständig und liefert den zu kodierenden Inhalt:
* bei statischen Codes die Nutzdaten selbst, bei dynamischen die Kurz-URL.
*/
export function resolveQrRequest(request: QrRequest): ResolvedQrRequest {
const qrType = getActiveQrType(request.type);
if (!qrType) {
throw new QrRequestError(
"Dieser QR-Typ ist in dieser Version noch nicht verfügbar.",
404,
);
}
export async function resolveQrRequest(
request: QrRequest,
): Promise<ResolvedQrRequest> {
const qrType = await requireAvailableQrType(request.type);
const validated = qrType.validate(request.data);
if (!validated.ok) {