Docker-Stack, README und Korrekturen aus dem ersten Durchlauf
- Dockerfile (4 Stufen) und docker-compose.yml mit Postgres-Healthcheck, Volume und direktem Port-Mapping 3000:3000, ohne Reverse Proxy. - Entrypoint wendet beim Start "prisma migrate deploy" an; die Prisma-CLI bekommt eine eigene Build-Stufe, weil sie mehr als nur @prisma/* braucht. - prisma.config.ts durch prisma.config.js ersetzt: laedt ohne TypeScript und ohne dotenv, damit sie im Laufzeit-Image funktioniert. - Prisma-Client wird erst beim ersten Zugriff erzeugt. Vorher scheiterte "next build" beim Einsammeln der Routen-Konfiguration ohne DATABASE_URL. - Postgres-18-Volume auf /var/lib/postgresql gemountet (neues Datenlayout). - npm 11 blockiert Installationsskripte: erlaubte Pakete stehen jetzt im Feld "allowScripts" der package.json. - Lokale Variable "module" ueberall in "qrType" umbenannt (verdeckte sonst die CommonJS-Variable) und Vorschau-Effekt ohne synchrones setState. - README.md auf Deutsch: Setup, Konfiguration, Bedienung, Aufbau, API und Ausblick auf Version 2.
This commit is contained in:
@@ -1,36 +1,332 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||
# QR-Code-Generator
|
||||
|
||||
## Getting Started
|
||||
Eine Webanwendung zum Erzeugen von QR-Codes — als Docker-Compose-Stack aus
|
||||
zwei Containern: der Next.js-Anwendung und einer PostgreSQL-Datenbank.
|
||||
|
||||
First, run the development server:
|
||||
**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)
|
||||
- [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
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
cp .env.example .env # anschließend POSTGRES_PASSWORD und BASE_URL anpassen
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
Danach ist die Anwendung unter <http://localhost:3000> erreichbar. Die
|
||||
Datenbankmigrationen laufen beim Start des App-Containers automatisch — es
|
||||
sind keine weiteren Schritte nötig.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
Nützliche Befehle:
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
```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
|
||||
```
|
||||
|
||||
## Learn More
|
||||
---
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
## Konfiguration
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
Alle Einstellungen stehen in der `.env` im Projektverzeichnis (Vorlage:
|
||||
`.env.example`).
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
| 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` |
|
||||
|
||||
## Deploy on Vercel
|
||||
### Wichtig: `BASE_URL` richtig setzen
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
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.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
- 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. 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
|
||||
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 |
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user