# QR-Code-Generator Eine Webanwendung zum Erzeugen von QR-Codes — als Docker-Compose-Stack aus zwei Containern: der Next.js-Anwendung und einer PostgreSQL-Datenbank. **Version 1** unterstützt den QR-Typ **Website/URL**, statisch wie dynamisch. Die übrigen 14 Typen sind bereits in der Oberfläche sichtbar und für Version 2 vorbereitet. --- ## Inhalt - [Schnellstart](#schnellstart) - [Aktualisieren](#aktualisieren) - [Konfiguration](#konfiguration) - [Bedienung](#bedienung) - [Technischer Aufbau](#technischer-aufbau) - [Entwicklung ohne Docker](#entwicklung-ohne-docker) - [API](#api) - [Ausblick Version 2](#ausblick-version-2) --- ## Schnellstart Voraussetzung: Docker mit Docker Compose. ```bash cp .env.example .env # anschließend POSTGRES_PASSWORD und BASE_URL anpassen docker compose up -d --build ``` Danach ist die Anwendung unter erreichbar. Die Datenbankmigrationen laufen beim Start des App-Containers automatisch — es sind keine weiteren Schritte nötig. Nützliche Befehle: ```bash docker compose logs -f app # Protokoll der Anwendung docker compose ps # Status beider Container docker compose down # Stack stoppen (Daten bleiben erhalten) docker compose down -v # Stack stoppen und Datenbank löschen ``` --- ## Aktualisieren Für neue Versionen liegt ein Update-Skript bei — einmal für Linux/macOS und einmal für Windows. Es holt den aktuellen Stand aus Git, baut den Stack neu und wartet, bis die Anwendung wieder antwortet. ```bash ./update.sh # Linux / macOS ``` ```powershell .\update.ps1 # Windows ``` Beim ersten Aufruf unter Linux muss das Skript einmal ausführbar gemacht werden: `chmod +x update.sh`. Was das Skript tut: 1. prüft, ob `git`, `docker compose` und die `.env` vorhanden sind, 2. bricht ab, wenn es **lokale Änderungen** gibt — sonst würde `git pull` im Konflikt enden, 3. holt den neuen Stand mit `git merge --ff-only` und zeigt die neuen Commits, 4. baut den Stack nur neu, wenn es tatsächlich Änderungen gab, 5. wartet, bis die Anwendung auf `APP_PORT` antwortet, und zeigt sonst das Protokoll, 6. gibt die Migrationszeilen aus dem Start-Protokoll und den Containerstatus aus. Optionen: | Option | Wirkung | | ------------------- | -------------------------------------------------------------- | | `--force` / `-Force` | Auch ohne neue Commits neu bauen | | `--prune` / `-Prune` | Danach verwaiste Docker-Images entfernen (`docker image prune`) | | `--help` | Hilfe anzeigen | Die Datenbank bleibt beim Update erhalten — neue Migrationen wendet der App-Container beim Start automatisch an. Wer lieber von Hand aktualisiert: ```bash git pull docker compose up -d --build ``` --- ## Konfiguration Alle Einstellungen stehen in der `.env` im Projektverzeichnis (Vorlage: `.env.example`). | Variable | Bedeutung | Standard | | ------------------- | ------------------------------------------------------------- | ------------------------ | | `BASE_URL` | Öffentliche Adresse der App — landet in dynamischen QR-Codes | `http://localhost:3000` | | `APP_PORT` | Host-Port, auf dem die App veröffentlicht wird | `3000` | | `DATABASE_URL` | Verbindungsstring der App zur Datenbank | — | | `POSTGRES_USER` | Datenbankbenutzer | `qrcode` | | `POSTGRES_PASSWORD` | Datenbankpasswort (**muss gesetzt sein**) | — | | `POSTGRES_DB` | Name der Datenbank | `qrcode` | ### Wichtig: `BASE_URL` richtig setzen Ein dynamischer QR-Code enthält nicht das eigentliche Ziel, sondern die Kurz-URL `{BASE_URL}/r/{slug}`. Diese Adresse muss von den Geräten erreichbar sein, die den Code später scannen. - Nur am selben Rechner testen → `http://localhost:3000` genügt. - Im LAN mit dem Handy scannen → die LAN-Adresse des Servers eintragen, z. B. `http://192.168.1.50:3000`. Wird `BASE_URL` nachträglich geändert, zeigen bereits **gedruckte** dynamische Codes weiterhin auf die alte Adresse. Am besten also vor dem ersten produktiven Code festlegen. Nach einer Änderung an der `.env`: ```bash docker compose up -d ``` Ändern sich `POSTGRES_*`-Werte, muss `DATABASE_URL` entsprechend mitgezogen werden — der Hostname darin bleibt `db` (der Servicename aus `docker-compose.yml`). --- ## Bedienung ### 1. Typ wählen Die Startseite zeigt unter „Was möchtest du erstellen?“ ein Kachel-Grid aller 15 QR-Typen. In Version 1 ist **Website** aktiv (Badge „Beliebt“); die übrigen Kacheln sind sichtbar, aber deaktiviert (Badge „Bald verfügbar“). ### 2. Statisch oder dynamisch | | Statisch | Dynamisch | | ---------------------- | -------------------------------------------- | ---------------------------------------------------- | | Im Code kodiert | die Ziel-URL selbst | die Kurz-URL `{BASE_URL}/r/{slug}` | | Ziel später änderbar | nein | ja, jederzeit | | Braucht diesen Server | nein | ja — er leitet weiter | | Scans werden gezählt | nein | ja | | Wird gespeichert | nein | ja, in PostgreSQL | Ein dynamischer Code muss einmal **gespeichert** werden, denn erst dabei entsteht seine Kurz-URL. Bis dahin zeigt die Vorschau einen Platzhalter und der Download ist gesperrt — so kann kein Code entstehen, der ins Leere führt. ### 3. Gestalten - **Farben**: Vorder- und Hintergrund per Farbwähler oder als Hex-Wert. Fünf Voreinstellungen und ein Knopf zum Tauschen der Farben stehen bereit. - **Kontrastwarnung**: Die Anwendung berechnet das Kontrastverhältnis und warnt bei schlecht scanbaren Kombinationen — ebenso bei hellem Vordergrund auf dunklem Grund, den viele Scanner-Apps nicht erkennen. - **Größe**: 256 bis 2048 px (gilt für das PNG; das SVG skaliert verlustfrei). - **Fehlerkorrektur**: L, M (Standard), Q oder H. Höhere Level machen den Code robuster gegen Beschädigung, aber auch dichter. - **Rand**: Breite der Ruhezone in Modulen. Weniger als 4 kann das Scannen erschweren. Die Vorschau wird bei jeder Änderung serverseitig neu erzeugt — sie zeigt also exakt das, was auch heruntergeladen wird. ### 4. Herunterladen **PNG** für Druck und Office-Dokumente, **SVG** für verlustfreie Skalierung (Plakate, Schilder, Druckvorstufe). ### 5. Verwaltung: welche Typen erscheinen Unter **Verwaltung → QR-Typen** lässt sich jeder der 15 Typen einzeln ein- und ausblenden. Ausgeblendete Typen verschwinden vollständig von der Startseite und lassen sich nicht mehr neu anlegen — praktisch, um die noch nicht umgesetzten Typen gar nicht erst zu zeigen. Zwei Eigenschaften sind dabei zu unterscheiden: | | Bedeutung | Woher | | --- | --- | --- | | **umgesetzt** | Der Typ ist ausprogrammiert. In Version 1 gilt das nur für „Website“. | Registry im Code | | **freigegeben** | Die Verwaltung zeigt den Typ an. | Datenbank, jederzeit änderbar | Nur umgesetzt **und** freigegeben heißt benutzbar. Freigegeben, aber nicht umgesetzt, erscheint als Kachel „Bald verfügbar“. > **Bereits gespeicherte Codes bleiben unberührt.** Schaltet man einen Typ ab, > leiten vorhandene dynamische Codes weiter wie bisher und lassen sich weiter > bearbeiten. Ein gedruckter Code wird durch das Abschalten seines Typs also > nicht ungültig — gesperrt ist nur das Anlegen neuer Codes. Ohne Eintrag in der Datenbank gilt ein Typ als freigegeben. Eine frische Installation zeigt also alles, und Typen aus späteren Versionen erscheinen automatisch. ### 6. Meine Codes Unter „Meine Codes“ stehen alle dynamischen Codes mit Vorschaubild, Kurz-URL, aktuellem Ziel, Scan-Anzahl, Erstellungsdatum und letztem Scan. Dort lässt sich - das **Ziel ändern** — die Kurz-URL und damit der gedruckte Code bleiben gültig, - der Code **löschen** (mit Bestätigungsdialog), - das Bild erneut als PNG oder SVG **herunterladen**. Statische Codes erscheinen hier nicht, weil sie nicht gespeichert werden. > Version 1 ist für den Einzelplatz-/LAN-Betrieb gedacht und hat **keine > Benutzerverwaltung**. Wer die Anwendung erreicht, sieht und ändert alle > Codes. Die App gehört daher nicht ungeschützt ins offene Internet. --- ## Technischer Aufbau ### Stack | Baustein | Verwendet | | ------------ | ------------------------------------------------------- | | Framework | Next.js 16 (App Router, TypeScript), Frontend + API | | Oberfläche | React 19, Tailwind CSS 4 | | Datenbank | PostgreSQL 18 | | ORM | Prisma 7 mit `@prisma/adapter-pg` | | QR-Erzeugung | `qrcode`, serverseitig, Ausgabe als PNG und SVG | | Validierung | Zod 4, im Browser und noch einmal auf dem Server | Der Stack läuft ohne Reverse Proxy: Der App-Container wird direkt per Port-Mapping veröffentlicht. ### Verzeichnisse ``` prisma/ schema.prisma Datenmodell (Modell QrCode) migrations/ Versionierte Migrationen docker/ entrypoint.sh Migration + Serverstart im Container update.sh / update.ps1 Update-Skript (git pull + Neubau + Prüfung) src/ app/ page.tsx Startseite mit dem Kachel-Grid erstellen/[type]/ Generator je QR-Typ meine-codes/ Übersicht der dynamischen Codes r/[slug]/route.ts Weiterleitung dynamischer Codes (302) api/ qr/preview/ Live-Vorschau (SVG als Data-URL) qr/download/ Download als PNG oder SVG codes/ Anlegen und Auflisten dynamischer Codes codes/[id]/ Ändern und Löschen codes/[id]/qr/ Bild eines gespeicherten Codes components/ qr-forms/ Formulare je QR-Typ + deren Registry qr-generator.tsx Generator-Oberfläche codes-list.tsx „Meine Codes“ design-panel.tsx Farben, Größe, Fehlerkorrektur, Rand lib/ qr/ type-module.ts Vertrag eines QR-Typs (defineQrType) registry.ts Registry aller 15 Typen types/website.ts Der einzige aktive Typ in V1 design.ts Gestaltungsoptionen + Zod-Schema contrast.ts Kontrastbewertung render.ts PNG-/SVG-Erzeugung request.ts Anfrage-Schema und Auflösung des Code-Inhalts db.ts Prisma-Client env.ts Laufzeit-Konfiguration ``` ### Datenmodell Nur dynamische Codes werden gespeichert. Das Modell `QrCode` hält neben Slug, Zähler und Gestaltung ein generisches `data`-Feld (JSONB) sowie den Typ — so brauchen die Typen aus Version 2 **keine** Schemaänderung. ### Wie ein QR-Typ aufgebaut ist Jeder Typ ist ein Modul, das über `defineQrType` erzeugt wird und vier Dinge beisteuert: Metadaten (Titel, Beschreibung, Icon), ein Zod-Schema, die Kodierung des Inhalts und eine Kurzfassung für Listen. ```ts export const websiteType = defineQrType({ id: "website", title: "Website", description: "Verlinke eine beliebige Webadresse.", icon: "globe", active: true, supportsDynamic: true, schema: websiteDataSchema, defaultData: { url: "" }, encode: (data) => data.url, summary: (data) => describe(data.url), }); ``` Innerhalb des Moduls arbeitet man mit dem echten, aus dem Zod-Schema abgeleiteten Datentyp; nach außen zeigt die Registry eine einheitliche Schnittstelle. Deshalb kennen Vorschau, Download, Speicherung und Weiterleitung keinen einzigen typspezifischen Sonderfall. ### Warum 302 und kein 301? Die Weiterleitung unter `/r/{slug}` antwortet mit **302 (temporär)** und `Cache-Control: no-store`. Ein permanenter Redirect würde in Browsern hängen bleiben: Zieländerungen kämen nicht an und der Scan-Zähler bliebe stehen. --- ## Entwicklung ohne Docker Voraussetzung: Node.js 20.9+ und eine erreichbare PostgreSQL-Instanz. ```bash npm install # Datenbank z. B. als Einzelcontainer starten docker run -d --name qr-db -p 5432:5432 \ -e POSTGRES_USER=qrcode -e POSTGRES_PASSWORD=devpass -e POSTGRES_DB=qrcode \ postgres:18-alpine # .env anlegen und DATABASE_URL auf localhost zeigen lassen: # DATABASE_URL=postgresql://qrcode:devpass@localhost:5432/qrcode?schema=public npm run db:migrate # Migrationen anwenden und Prisma Client erzeugen npm run dev # http://localhost:3000 ``` Weitere Skripte: ```bash npm run build # Produktionsbuild npm run typecheck # TypeScript prüfen npm run lint # ESLint npm run db:studio # Prisma Studio (Datenbank ansehen) ``` > **Hinweis zu npm 11:** Installationsskripte sind standardmäßig blockiert. > Die Pakete, die welche ausführen dürfen (Prisma lädt darüber seine > Engine-Binaries), stehen im Feld `allowScripts` der `package.json`. --- ## API Alle Endpunkte antworten im Fehlerfall mit `{ "error": "…" }` und einer deutschen Meldung. | Methode | Pfad | Zweck | | -------- | ----------------------- | ---------------------------------------------- | | `POST` | `/api/qr/preview` | Vorschau als SVG-Data-URL | | `POST` | `/api/qr/download` | PNG- oder SVG-Datei | | `GET` | `/api/codes` | Alle dynamischen Codes | | `POST` | `/api/codes` | Dynamischen Code anlegen | | `GET` | `/api/codes/{id}` | Einzelnen Code lesen | | `PATCH` | `/api/codes/{id}` | Ziel, Name oder Gestaltung ändern | | `DELETE` | `/api/codes/{id}` | Code löschen | | `GET` | `/api/codes/{id}/qr` | Bild des Codes (`?format=png\|svg&download=1`) | | `GET` | `/r/{slug}` | Weiterleitung, erhöht den Scan-Zähler | | `GET` | `/api/admin/types` | Zustand aller QR-Typen | | `PATCH` | `/api/admin/types` | Typ freigeben oder abschalten | Beispiel: ```bash curl -X POST http://localhost:3000/api/codes \ -H 'Content-Type: application/json' \ -d '{ "type": "website", "data": { "url": "https://example.com" }, "design": { "fgColor": "#0F172A", "bgColor": "#FFFFFF", "size": 512, "ecLevel": "M", "margin": 4 }, "label": "Plakat Eingang" }' ``` --- ## Ausblick Version 2 Geplant sind die übrigen 14 Typen: vCard, App-Link, Mehrere Links, Social Media, WhatsApp, PDF, Bild, Video, E-Mail, SMS, Standort, Zahlung, WLAN und Feedback. Die Architektur ist darauf ausgelegt — ein neuer Typ besteht aus: 1. **einem Modul** in `src/lib/qr/types/` (Zod-Schema, Kodierung, Kurzfassung), 2. **einer Formular-Komponente** in `src/components/qr-forms/`, 3. **je einem Eintrag** in `src/lib/qr/registry.ts` (ersetzt dort den `defineComingSoonType`-Platzhalter) und in `src/components/qr-forms/index.ts`. Am bestehenden Code aus Version 1 ist dafür nichts zu ändern: Vorschau, Download, Gestaltung, Speicherung dynamischer Codes und die Weiterleitung arbeiten für alle Typen gleich. Typen, die statt einer Weiterleitung eine eigene Landingpage brauchen (etwa „Mehrere Links“ oder „Feedback“), setzen dafür `redirectTarget` auf eine interne Adresse — auch dafür ist keine Änderung am Datenmodell nötig, weil `data` als JSONB gespeichert wird.