Funktionsuebersicht und Roadmap

Kurz im README: was die Anwendung kann, auf einen Blick. Lang in
docs/funktionen.md: jeder Typ mit seinen Grenzen, dazu Landingpages, Dateien,
Feedback, Rechte, Single Sign-on, Domains und Betrieb — samt der Frage, warum
manches so entschieden ist.

docs/roadmap.md haelt fest, was in welcher Reihenfolge gebaut wurde, und
sammelt Vorschlaege fuer danach. Bewusst als Vorschlaege mit grober
Aufwandsschaetzung, nicht als Zusage: was zuerst kommt, entscheidet, was im
Betrieb fehlt.
This commit is contained in:
Florian Wilde
2026-08-15 07:15:25 +02:00
parent 055d8caaa0
commit 7fcdcba694
3 changed files with 603 additions and 0 deletions
+28
View File
@@ -8,12 +8,40 @@ entscheidet die Verwaltung — nicht gebrauchte lassen sich ausblenden.
---
## Was die Anwendung kann — kurz
- **15 QR-Typen:** Website, vCard, App-Link, Mehrere Links, Social Media,
WhatsApp, PDF, Bild, Video, E-Mail, SMS, Standort, Zahlung, WLAN, Feedback.
- **Statisch oder dynamisch.** Dynamische Codes zeigen auf eine Kurz-URL:
Ziel jederzeit änderbar, ohne neu zu drucken — mit Scan-Zähler.
- **Eigene Seiten** für Linksammlungen, Profile, hinterlegte PDFs und Bilder
sowie Feedback-Formulare — öffentlich, ohne einen Hinweis auf die Anwendung.
- **Gestaltung:** freie Farben mit Kontrastwarnung, 256–2048 px,
Fehlerkorrektur L/M/Q/H, einstellbarer Rand. Download als PNG und SVG.
- **Meine Codes:** Liste oder Karten, Suche, Sortierung, Gruppenfilter,
Ziel ändern, löschen.
- **Feedback auswerten:** Durchschnitt, Verteilung und Kommentare — anonym
erhoben, ohne Name und IP-Adresse.
- **Anmeldung abschaltbar.** Mit Anmeldung: Benutzer, Gruppen und die Rollen
Administrator, Bearbeiter und Betrachter.
- **Mehrere Domains** für Kurz-URLs, den Gruppen zugeteilt.
- **Single Sign-on** über Authentik oder einen anderen OIDC-Anbieter.
- **Typen abschaltbar:** Was ihr nicht braucht, erscheint gar nicht erst.
- **Zwei Container, kein Reverse Proxy nötig.** Migrationen laufen beim Start
von selbst; alles Nötige steht in einer `.env`.
→ Ausführlich: **[Alle Funktionen im Detail](docs/funktionen.md)**
---
## Anleitungen
- **[Bedienungsanleitung mit Bildern](docs/anleitung.md)** — vom ersten Code
bis zur Auswertung von Rückmeldungen
- **[Anmeldung über Authentik (OIDC)](docs/authentik.md)** — Einrichtung,
Rollen und der Weg zurück
- **[Alle Funktionen im Detail](docs/funktionen.md)** — die lange Fassung
- **[Roadmap](docs/roadmap.md)** — was gebaut ist und was kommen könnte
---
+423
View File
@@ -0,0 +1,423 @@
# Alle Funktionen im Detail
Die lange Fassung der Übersicht aus dem [README](../README.md). Sie
beschreibt, was die Anwendung kann, wo die Grenzen liegen und warum manches
so entschieden ist. Wie man es bedient, steht in der
[Bedienungsanleitung](anleitung.md).
---
## Inhalt
- [QR-Typen](#qr-typen)
- [Statisch und dynamisch](#statisch-und-dynamisch)
- [Landingpages](#landingpages)
- [Dateien](#dateien)
- [Feedback](#feedback)
- [Gestaltung und Ausgabe](#gestaltung-und-ausgabe)
- [Meine Codes](#meine-codes)
- [Anmeldung, Gruppen und Rollen](#anmeldung-gruppen-und-rollen)
- [Single Sign-on](#single-sign-on)
- [Mehrere Domains](#mehrere-domains)
- [Verwaltung](#verwaltung)
- [Betrieb](#betrieb)
- [Schnittstelle](#schnittstelle)
- [Was die Anwendung bewusst nicht tut](#was-die-anwendung-bewusst-nicht-tut)
---
## QR-Typen
Alle 15 vorgesehenen Typen sind gebaut.
| Typ | Was hineinkommt | Statisch | Dynamisch | Eigene Seite |
| --- | --- | :-: | :-: | :-: |
| **Website** | eine Adresse | ✓ | ✓ | — |
| **vCard** | Name, Firma, Telefon, E-Mail, Anschrift, Web | ✓ | — | — |
| **App-Link** | je eine Adresse für iOS, Android und Ausweichfall | — | ✓ | — |
| **Mehrere Links** | Überschrift, Text, bis zu 20 beschriftete Links | — | ✓ | ✓ |
| **Social Media** | Überschrift, Text, bis zu 12 Profile aus 15 Netzwerken | — | ✓ | ✓ |
| **WhatsApp** | Rufnummer und vorbereitete Nachricht | ✓ | ✓ | — |
| **PDF** | eine hochgeladene Datei, Titel, Text | — | ✓ | ✓ |
| **Bild** | bis zu 10 hochgeladene Bilder mit Bildunterschriften | — | ✓ | ✓ |
| **Video** | YouTube, Vimeo oder eine direkte Videodatei | ✓ | ✓ | ✓ |
| **E-Mail** | Empfänger, Betreff, Text | ✓ | ✓ | — |
| **SMS** | Rufnummer und Text | ✓ | ✓ | — |
| **Standort** | Koordinaten oder Adresse | ✓ | ✓ | — |
| **Zahlung** | GiroCode (EPC069-12) **oder** Zahlungslink | ✓ | nur Link | — |
| **WLAN** | Netzwerkname, Passwort, Verschlüsselung, versteckt | ✓ | — | — |
| **Feedback** | Frage, Skala, Textfeld, Dankestext | — | ✓ | ✓ |
### Warum manche Typen nur eine Betriebsart können
- **vCard und WLAN** wertet das Gerät direkt aus — die Kamera legt einen
Kontakt an oder richtet das Netz ein. Über eine Kurz-URL ginge stattdessen
nur ein Browser auf.
- **App-Link** kann nicht statisch sein: Welches Ziel richtig ist, steht erst
beim Scannen fest. Die Weiterleitung wertet den User-Agent aus — iOS in den
App Store, Android zu Google Play, alles Übrige auf die Ausweichadresse.
(iPadOS meldet sich seit Version 13 als Macintosh und landet deshalb
mitunter auf dem Desktop-Weg; für einen Store-Link ist das hinnehmbar.)
- **GiroCode** ist kein Web-Ziel, sondern ein Datensatz für Banking-Apps. Als
dynamischer Code funktioniert nur die Variante „Zahlungslink“. Die
Anwendung weist das beim Speichern **und beim Bearbeiten** ab, statt einen
Code entstehen zu lassen, der erst beim Scannen auffällt — also dann, wenn
er schon gedruckt ist.
- **Die fünf Typen mit eigener Seite** brauchen eine Seite, die die Anwendung
ausliefert. Eine Linkliste oder ein Formular passt nicht in einen einzelnen
Code. Video ist die Ausnahme: statisch steht dort die Videoadresse selbst.
### Besonderheiten einzelner Typen
**Social Media.** Als Eingabe genügt das Kürzel — aus `@firma` wird beim
Speichern `https://instagram.com/firma`. Eine vollständige Adresse bleibt
unverändert, damit auch abweichende Profil-Adressen möglich sind. Jedes
Netzwerk kann nur einmal vorkommen. Der Katalog umfasst Instagram, Facebook,
LinkedIn, Xing, YouTube, TikTok, X, Threads, Mastodon, WhatsApp, Telegram,
Pinterest, Snapchat, GitHub und „Website“.
**Video.** Erkannt werden `youtube.com/watch`, `youtu.be`, `/shorts/`,
`/embed/`, `/live/`, Vimeo-Adressen sowie die Endungen `.mp4`, `.webm`,
`.ogv`, `.ogg`, `.mov` und `.m4v`. Alles Übrige erscheint als schlichter Link
— ehrlicher als ein eingebetteter Rahmen, den der Anbieter nicht erlaubt.
**WLAN.** Das Passwort steht im Klartext im Code; wer ihn scannt oder
abfotografiert, kennt es. Für Gästenetze ist das gewollt. Besteht Netzwerkname
oder Passwort nur aus Hex-Ziffern, setzt die Anwendung den Wert wie in der
Spezifikation vorgesehen in Anführungszeichen — sonst deuten manche Geräte ihn
als Hexzahl.
**Standort.** Statisch entsteht ein `geo:`-Link, den Karten-Apps direkt
öffnen. Dynamisch führt der Weg über OpenStreetMap, weil eine Kurz-URL im
Browser landet.
---
## Statisch und dynamisch
| | Statisch | Dynamisch |
| --- | --- | --- |
| Im Code steht | die Zieladresse selbst | `{BASE_URL}/r/{slug}` |
| Ziel später änderbar | nein | ja, jederzeit |
| Scans gezählt | nein | ja, mit Datum des letzten |
| Braucht diesen Server | nie | dauerhaft |
| In der Datenbank | nichts | Slug, Typ, Daten, Gestaltung, Zähler |
Der **Slug** ist acht Zeichen lang aus einem Alphabet ohne verwechselbare
Zeichen (kein `0`, `O`, `1`, `l`, `I`). Er ist **je Domain** eindeutig, nicht
global — derselbe Slug darf auf zwei Domains liegen und dort auf
Verschiedenes zeigen.
Die Weiterleitung antwortet mit **302** und `Cache-Control: no-store`. Ein
permanenter Redirect würde in Browsern und Zwischenspeichern hängen bleiben —
und genau die Änderbarkeit zunichtemachen, wegen der man dynamische Codes
nimmt. Der Scan-Zähler wird beiläufig erhöht; scheitert er, geht die
Weiterleitung trotzdem durch.
---
## Landingpages
Fünf Typen führen auf eine Seite unter `{BASE_URL}/s/{slug}`, die die
Anwendung selbst ausliefert.
Diese Seiten sind **öffentlich** und zeigen bewusst nichts von der Anwendung:
keine Navigation, kein „Verwaltung“, kein Hinweis auf den Generator, und im
Seitentitel steht der Inhalt statt des Produktnamens. Wer einen Aushang
scannt, hat mit dem Generator nichts zu tun.
Technisch liegt das App-Chrome in einer Routengruppe, die Landingpages
außerhalb — sie können es also gar nicht versehentlich erben.
Weitere Eigenschaften:
- Alle Texte kommen aus den Formulardaten und werden als **Text** ausgegeben,
nie als HTML. Eingegebene Zeichen wie `<b>` erscheinen als solche.
- Verlinkte Ziele bekommen `rel="noopener noreferrer"` — kein Zugriff auf das
Fenster, aus dem sie geöffnet wurden.
- Adressen müssen mit `http://` oder `https://` beginnen; `javascript:` und
Ähnliches wird abgewiesen.
- Passen die gespeicherten Daten nicht mehr zum Schema, erscheint eine
freundliche Erklärung statt eines Fehlers.
- Der Scan-Zähler zählt den Aufruf des **Codes**, nicht das Neuladen der
Seite.
**Video-Landingpage.** Der Rahmen zu YouTube oder Vimeo lädt erst nach einem
Klick, und davor steht, dass dabei Daten an den Anbieter übertragen werden.
Eingebettet wird die Variante ohne Cookies (`youtube-nocookie.com`). Direkte
Videodateien laden mit `preload="none"` — wer unterwegs scannt, zahlt das
Volumen.
---
## Dateien
PDFs und Bilder liegen **in der Datenbank**, nicht auf der Platte. Damit gibt
es weiterhin genau ein Volumen zu sichern: Wer die Datenbank sichert, hat
alles. Ein zweites Volumen wird beim Sichern zu leicht vergessen.
| | |
| --- | --- |
| Erlaubt | PDF; PNG, JPEG, WebP, GIF, AVIF |
| Nicht erlaubt | SVG und alles Übrige |
| Größe | `MAX_UPLOAD_MB`, Voreinstellung 10 MB |
| Geprüft wird | der Dateiinhalt, nicht die Endung |
**Warum kein SVG:** Eine SVG-Datei darf Skripte enthalten. Ausgeliefert von
derselben Adresse wie die Anwendung könnte sie damit auf angemeldete
Sitzungen zugreifen.
**Wie geprüft wird:** Der vom Browser gemeldete Typ kommt aus der Endung und
lässt sich beliebig setzen. Entschieden wird deshalb anhand der ersten Bytes
der Datei. Eine Textdatei namens `harmlos.pdf` wird abgewiesen.
**Wie ausgeliefert wird:** mit dem erkannten Typ, `X-Content-Type-Options:
nosniff` und einer `Content-Security-Policy: sandbox` — damit auch ein PDF mit
eingebetteten Skripten nichts anrichten kann. Ein Anhängen von `?download`
erzwingt das Speichern statt der Anzeige.
**Wem eine Datei gehört:** Sie hängt am Code. Wird er gelöscht, verschwindet
sie mit ihm. Eine ausgetauschte Datei verliert ihren Bezug und wird nach 24
Stunden weggeräumt — ebenso eine hochgeladene, deren Code nie gespeichert
wurde. Eine fremde Datei lässt sich nicht an den eigenen Code hängen; sonst
könnte man sie einem anderen Code entziehen.
Die Adresse einer Datei ist nicht erratbar, aber öffentlich abrufbar —
dieselbe Überlegung wie beim Slug einer Kurz-URL. Vertrauliche Unterlagen
gehören nicht hinter einen QR-Code, den man abfotografieren kann.
---
## Feedback
Ein Feedback-Code erzeugt ein Formular mit fünf **Sternen** oder fünf
**Gesichtern** und wahlweise einem Textfeld. Überschrift, Frage, Beschriftung
des Textfelds und der Text nach dem Absenden sind frei formulierbar.
Gespeichert werden **nur die Note von 1 bis 5 und der Text**. Kein Name, keine
IP-Adresse, keine Gerätekennung — das steht auch für die Antwortenden sichtbar
unter dem Formular.
Gegen Fluten gibt es eine Bremse von 10 Rückmeldungen pro Minute und Absender.
Die Adresse dient dabei nur als Schlüssel im Arbeitsspeicher und wird nirgends
gespeichert. Rückmeldungen zu Codes, die gar kein Feedback einsammeln, werden
abgewiesen.
Die **Auswertung** ist aus der Codeliste erreichbar und zeigt Durchschnitt,
Verteilung der Noten und alle Kommentare mit Datum (die 200 jüngsten). Sie
sieht nur, wer den Code auch sehen darf.
---
## Gestaltung und Ausgabe
| Einstellung | Bereich | Voreinstellung |
| --- | --- | --- |
| Vordergrundfarbe | frei (Hex oder Farbwähler) | `#0F172A` |
| Hintergrundfarbe | frei | `#FFFFFF` |
| Größe | 256–2048 px | 512 px |
| Fehlerkorrektur | L, M, Q, H | M |
| Rand | 0–16 Module | 4 |
Fünf Farbvorschläge (Klassisch, Indigo, Waldgrün, Bordeaux, Marine) und ein
Knopf zum Tauschen von Vorder- und Hintergrund. Reicht der Kontrast nicht,
warnt die Anwendung — ein hübscher, aber unlesbarer Code nützt niemandem.
Die **Vorschau** wird serverseitig gerendert und aktualisiert sich bei jeder
Änderung. Herunterladen geht als **PNG** (in der eingestellten Größe) und als
**SVG** (frei skalierbar). Die Größe betrifft nur das PNG.
---
## Meine Codes
Zeigt alle dynamischen Codes — statische erscheinen nicht, weil sie nicht
gespeichert werden.
- **Zwei Ansichten:** Liste (Voreinstellung, eine Zeile je Code) und Karten
mit großer Vorschau. Die Wahl merkt sich der Browser.
- **Suche** über Name, Ziel, Kurz-URL und Typ.
- **Sortierung** nach neueste, älteste, Name oder Scan-Anzahl.
- **Gruppenfilter**, sobald Codes in mehr als einer Gruppe liegen; dann gibt
es auch eine Gruppenspalte.
- **Kurz-URL kopieren** mit einem Klick.
- **Ziel ändern** — die Kurz-URL und damit der gedruckte Code bleiben gültig.
- **Löschen** mit Rückfrage, die den betroffenen Code nennt.
- **PNG und SVG** erneut herunterladen.
- Bei Feedback-Codes ein Weg zur **Auswertung**.
Auf schmalen Bildschirmen weichen die hinteren Spalten; die Kurz-URL rückt
unter den Namen, Bearbeiten und Löschen bleiben erreichbar.
---
## Anmeldung, Gruppen und Rollen
Die Anmeldung ist **abschaltbar** (`AUTH_ENABLED`). Bewusst über die
Konfiguration und nicht über die Oberfläche: So kann man sich nicht
aussperren.
**Ohne Anmeldung** darf jeder, der die Anwendung erreicht, alles. Für den
Einzelplatz- oder LAN-Betrieb ist das gewollt; ins offene Internet gehört die
Anwendung dann nicht.
**Mit Anmeldung** gibt es vier Ebenen:
1. **Benutzer** — meldet sich mit E-Mail und Passwort an.
2. **Gruppe** — bündelt Codes und die Menschen, die damit arbeiten. Jeder Code
gehört genau einer Gruppe.
3. **Rolle je Gruppe:**
| Rolle | Darf |
| --- | --- |
| Betrachter | sehen und herunterladen |
| Bearbeiter | zusätzlich anlegen, ändern, löschen |
| Administrator | zusätzlich Mitglieder und Rollen der Gruppe verwalten |
4. **Administrator der Installation** — legt Gruppen, Benutzer und Domains
überhaupt erst an.
Zur Umsetzung:
- Passwörter werden mit **scrypt** gehasht (Node-Bordmittel, kein natives
Modul). Mindestlänge 10 Zeichen.
- Nach **8 Fehlversuchen** ist das Konto 15 Minuten gesperrt.
- Sitzungen liegen in der **Datenbank**, nicht in einem signierten Cookie —
so lassen sie sich serverseitig beenden, etwa beim Deaktivieren eines
Benutzers. Im Cookie steht ein Zufallstoken, in der Datenbank nur dessen
SHA-256. Laufzeit 30 Tage.
- Der Cookie ist `httpOnly` und `SameSite=Lax`; `Secure` wird gesetzt, sobald
`BASE_URL` mit `https://` beginnt.
- Alle Berechtigungsregeln stehen an **einer** Stelle
(`src/lib/auth/policy.ts`) — nicht verteilt über die Oberfläche.
- Im **Konto** sieht man die eigenen Gruppen und Rollen, ändert sein Passwort
und meldet sich ab.
---
## Single Sign-on
Die Anmeldung lässt sich an einen **OpenID-Connect-Anbieter** abgeben —
getestet mit Authentik, ebenso nutzbar mit Keycloak, Zitadel oder Entra ID.
Alles Nötige kommt aus dem Discovery-Dokument des Anbieters.
- Anmeldefluss mit **PKCE** (S256), `state` und `nonce`.
- Das ID-Token wird gegen den **JWKS** des Anbieters geprüft: Signatur,
Aussteller, Empfänger, Ablauf und Nonce.
- Wiedererkannt wird jemand an der **Kennung des Anbieters** (`sub`), nicht an
der E-Mail-Adresse — Adressen ändern sich.
- Ein bestehendes Konto mit derselben Adresse wird **verknüpft statt
verdoppelt**, aber nur bei **bestätigter** Adresse. Sonst könnte sich beim
Anbieter jemand eine fremde Adresse eintragen und damit ein hiesiges Konto
übernehmen.
- Neue Konten bekommen die Rolle aus `OIDC_DEFAULT_ROLE` in der
Standardgruppe.
- `OIDC_ADMIN_GROUP` hängt die Instanz-Administration an eine Gruppe beim
Anbieter; sie wird dann bei jeder Anmeldung neu entschieden.
- `OIDC_ONLY=true` blendet die Passwort-Anmeldung aus.
**Bewusst nicht übernommen** werden Gruppen und Rollen. Die Gruppen im
Verzeichnis bedeuten etwas anderes als die hier: dort geht es um Organisation
und Zugang, hier darum, wer welche Codes sehen und ändern darf. Eine
automatische Übersetzung wäre eine Quelle stiller Überraschungen.
→ Einrichtung Feld für Feld: [authentik.md](authentik.md)
---
## Mehrere Domains
Kurz-URLs können unter verschiedenen Adressen laufen — etwa je Kunde eine.
Domains werden in der Verwaltung angelegt und Gruppen zugeteilt.
- Die Weiterleitung erkennt am `Host`-Header, welche Domain gemeint ist, und
**prüft ihn gegen die registrierten Domains**. Ein unbekannter Hostname
endet mit 404 — über einen gefälschten Header lässt sich also keine fremde
Domain ansprechen.
- Der **Slug ist je Domain eindeutig**, nicht global.
- Eine Domain kann **mehreren Gruppen** dienen. Die Codes bleiben trotzdem
strikt nach Gruppe getrennt.
- Je Gruppe lässt sich eine Domain als Voreinstellung festlegen.
- Eine Domain lässt sich abschalten, ohne sie zu löschen; ihre Codes
antworten dann mit einer Erklärung.
Damit das funktioniert, muss der Reverse Proxy jede dieser Domains auf diese
Anwendung leiten.
---
## Verwaltung
| Bereich | Was dort geht |
| --- | --- |
| **QR-Typen** | einzelne Typen ausblenden |
| **Benutzer** | anlegen, deaktivieren, Passwort setzen, Instanz-Admin vergeben |
| **Gruppen** | anlegen, umbenennen, Mitglieder und deren Rollen pflegen |
| **Domains** | anlegen, Gruppen zuteilen, abschalten |
Zu den **QR-Typen**: 15 Kacheln sind viel, wenn ihr nur drei davon braucht.
Ein abgeschalteter Typ verschwindet aus der Auswahl; **bereits erstellte Codes
funktionieren unverändert weiter** — sie leiten weiter, zählen Scans und
lassen sich bearbeiten. Nur neue lassen sich nicht mehr anlegen. Ohne Eintrag
in der Datenbank gilt ein Typ als freigegeben, damit eine frische Installation
alles zeigt und Typen aus späteren Versionen automatisch erscheinen.
---
## Betrieb
- **Zwei Container:** die Next.js-Anwendung und PostgreSQL. Kein Reverse Proxy
nötig — Port 3000 wird direkt veröffentlicht. Ein Proxy davor geht
selbstverständlich auch.
- **Migrationen laufen beim Start** von selbst. Es gibt keinen Extraschritt
nach `docker compose up -d --build`.
- Die Datenbank hat einen **Healthcheck**; die Anwendung startet erst, wenn
sie wirklich Anfragen annimmt.
- **Ein Volumen** für die Datenbank — dort liegen auch die hochgeladenen
Dateien.
- Die Konfiguration steht vollständig in einer **`.env`**; eine `.env.example`
ist mitgeliefert und dokumentiert jeden Wert.
- Die Anwendung liest die Werte **zur Laufzeit**, nicht beim Bauen — eine
geänderte `BASE_URL` braucht keinen neuen Build.
- Die Oberfläche ist durchgängig **deutsch**, hell, responsiv und ohne
externe Schriften oder Skripte.
---
## Schnittstelle
Alles, was die Oberfläche tut, geht auch über HTTP:
| Route | Zweck |
| --- | --- |
| `POST /api/qr/preview` | Vorschau erzeugen (statisch oder dynamisch) |
| `POST /api/qr/download` | PNG oder SVG erzeugen |
| `GET/POST /api/codes` | dynamische Codes auflisten und anlegen |
| `GET/PATCH/DELETE /api/codes/{id}` | einzelnen Code lesen, ändern, löschen |
| `GET /api/codes/{id}/qr` | Bild eines gespeicherten Codes |
| `POST /api/uploads` | Datei hochladen (`GET` nennt die Größengrenze) |
| `GET /api/uploads/{id}` | Datei ausliefern (`?download` erzwingt Speichern) |
| `POST /api/feedback` | Rückmeldung abgeben |
| `GET/POST /api/auth` | Zustand, Anmelden, Abmelden, Passwort ändern |
| `GET /api/auth/oidc/start` · `/callback` | Anmeldung über den Anbieter |
| `/api/admin/types` · `users` · `groups` · `domains` | Verwaltung |
| `GET /r/{slug}` | Weiterleitung eines dynamischen Codes |
| `GET /s/{slug}` | öffentliche Landingpage |
Fehler kommen als `{ "error": "…" }` mit einer deutschen Meldung, die sich
unverändert anzeigen lässt.
---
## Was die Anwendung bewusst nicht tut
- **Kein Logo im QR-Code.** Wer es braucht, nimmt das SVG, setzt es im
Layoutprogramm ein, wählt Fehlerkorrektur H und testet mit mehreren Geräten.
- **Keine Statistik über die Zeit.** Der Zähler weiß, wie oft und wann zuletzt
— kein Verlauf, keine Herkunft, keine Geräte. Das wäre eine eigene Tabelle
und die Frage, wie lange man so etwas aufbewahrt.
- **Keine Übernahme von Gruppen aus dem Verzeichnis.** Siehe oben.
- **Keine Stapelverarbeitung.** Codes entstehen einzeln.
- **Keine Mehrsprachigkeit.** Die Oberfläche ist deutsch.
- **Kein Papierkorb.** Ein gelöschter Code ist weg, samt seiner Dateien und
Rückmeldungen.
Was davon als Nächstes sinnvoll wäre, steht in der [Roadmap](roadmap.md).
+152
View File
@@ -0,0 +1,152 @@
# Roadmap
Was gebaut ist, und was danach sinnvoll wäre. Der zweite Teil ist eine
Sammlung von Vorschlägen mit einer groben Einschätzung des Aufwands — keine
Zusage und keine Reihenfolge. Was zuerst kommt, entscheidet, was im Betrieb
fehlt.
---
## Gebaut
### Version 1 — Grundgerüst (13.08.2026)
- Next.js-Anwendung und PostgreSQL als Docker-Compose-Stack, ohne Reverse
Proxy, Migrationen beim Start
- QR-Typ-Registry als Dispatch-Tabelle — ein neuer Typ ist ein Modul plus ein
Formular, ohne Eingriff in den übrigen Code
- Typ **Website**, statisch und dynamisch, mit Kurz-URL, 302-Weiterleitung und
Scan-Zähler
- Gestaltung: Farben mit Kontrastwarnung, Größe, Fehlerkorrektur, Rand
- Download als PNG und SVG, serverseitig gerendert
- „Meine Codes“ mit Ziel ändern und Löschen
- Deutsche Oberfläche, deutsche Fehlermeldungen, Validierung mit Zod
- Update-Skript für Linux/macOS und Windows
### Typen einzeln freigeben (14.08.2026)
Ausblenden nicht gebrauchter Typen in der Verwaltung. Bereits erstellte Codes
eines abgeschalteten Typs funktionieren weiter.
### Phase A — acht weitere Typen (14.08.2026)
vCard, App-Link, WhatsApp, E-Mail, SMS, Standort, Zahlung (GiroCode und
Zahlungslink), WLAN.
### Phase B1 — Anmeldung, Gruppen, Rollen (14.08.2026)
Abschaltbare Anmeldung, Benutzerverwaltung, Gruppen mit den Rollen
Administrator, Bearbeiter und Betrachter, Sitzungen in der Datenbank,
scrypt-Passwörter, Sperre nach Fehlversuchen.
### Phase B2 — mehrere Domains (14.08.2026)
Domains für Kurz-URLs, den Gruppen zugeteilt. Slug je Domain eindeutig,
Host-Header gegen die registrierten Domains geprüft.
### Listenansicht (14.08.2026)
„Meine Codes“ als Liste mit Suche, Sortierung und Gruppenfilter — gedacht für
viele Projekte nebeneinander. Karten bleiben als zweite Ansicht.
### Phase C — die letzten sechs Typen (14.08.2026)
Mehrere Links, Social Media, Video, PDF, Bild, Feedback. Dazu:
- Landingpages als eigene Gattung (`/s/{slug}`), ohne App-Navigation
- Datei-Upload mit Inhaltsprüfung, Größengrenze und Aufräumen
- Feedback mit anonymer Erhebung und Auswertung
Damit sind **alle 15 vorgesehenen Typen** gebaut.
### Single Sign-on (15.08.2026)
Anmeldung über Authentik und andere OIDC-Anbieter, mit PKCE und geprüftem
ID-Token.
### Dokumentation (15.08.2026)
Bedienungsanleitung mit Bildern, Authentik-Anleitung, Funktionsübersicht.
Die Bilder erzeugt ein Skript, damit sie nicht veralten.
---
## Denkbar als Nächstes
Die Einschätzungen sind grobe Hausnummern für eine Person, die den Code kennt.
### Naheliegend
**Statistik über die Zeit** · mittel
Der Zähler weiß heute nur, wie oft und wann zuletzt. Ein Verlauf je Tag wäre
eine eigene Tabelle plus ein Diagramm in der Codeansicht. Die eigentliche
Frage ist nicht die Technik, sondern wie lange man so etwas aufbewahrt — und
was man dabei über die Scannenden erfährt. Ein reiner Tageszähler ohne
Herkunft und Gerät ist unbedenklich und würde für „läuft die Kampagne?“
genügen.
**Codes exportieren** · klein
Eine CSV aller Codes einer Gruppe mit Name, Kurz-URL, Ziel und Scans — für
die Übergabe an eine Druckerei oder die Ablage.
**Papierkorb statt Löschen** · klein
Gelöschte Codes 30 Tage aufheben, dann endgültig entfernen. Heute ist ein
gedruckter Code nach einem Fehlklick unwiederbringlich tot.
**Codes duplizieren** · klein
„Wie dieser, aber mit anderem Ziel“ — bei vielen ähnlichen Projekten spart
das jedes Mal ein paar Minuten.
### Wenn der Bedarf da ist
**Stapelverarbeitung** · mittel
Eine CSV hochladen und daraus 200 Codes erzeugen, als ZIP zurück. Sinnvoll,
sobald jedes Produkt oder jeder Tisch einen eigenen Code bekommt.
**Gruppen aus dem Verzeichnis übernehmen** · mittel
Die OIDC-Anbindung übernimmt heute bewusst nur die Identität. Eine Zuordnung
von Verzeichnisgruppen zu Gruppen dieser Anwendung ist möglich, braucht aber
eine klare Regel dafür, was mit von Hand vergebenen Rollen geschieht — sonst
verliert jemand beim nächsten Anmelden stillschweigend seine Rechte.
**Logo im QR-Code** · mittel
Ein kleines Bild in die Mitte setzen, mit erzwungener Fehlerkorrektur H und
einer Warnung, wenn zu viel verdeckt wird. Braucht eine ehrliche Prüfung mit
mehreren Scanner-Apps, sonst entstehen Codes, die nur auf dem eigenen Handy
funktionieren.
**Ablaufdatum und Zeitsteuerung** · mittel
Ein Code, der ab einem Datum woandershin führt oder abläuft — für
Veranstaltungen und Aktionen.
**Passwortgeschützte Landingpages** · mittel
Für Unterlagen, die nicht jeder sehen soll, der den Aushang fotografiert.
Heute ist die Empfehlung klar: Vertrauliches gehört nicht hinter einen
offenen QR-Code.
### Größere Brocken
**Mehrsprachige Oberfläche** · groß
Die Anwendung ist durchgehend deutsch, auch in den Fehlermeldungen und den
Landingpages. Eine zweite Sprache berührt jede Datei.
**Eigene Gestaltung der Landingpages** · groß
Logo, Farben und Schrift je Gruppe, damit die Seite nach der eigenen Firma
aussieht. Technisch überschaubar, gestalterisch eine eigene Aufgabe — und es
braucht Grenzen, damit nichts Unlesbares entsteht.
**Dateien außerhalb der Datenbank** · mittel
Heute liegen Uploads in PostgreSQL, damit es bei einem Volumen bleibt. Wer
sehr viele oder sehr große Dateien ablegt, will sie irgendwann auf einem
Objektspeicher (S3) haben. Der Wechsel betrifft nur `src/lib/uploads.ts` und
die Auslieferungsroute — aber auch die Sicherungsstrategie.
---
## Wie ein neuer Typ dazukommt
Falls doch noch einer fehlt: Er besteht aus einem Modul in
`src/lib/qr/types/`, einer Formular-Komponente, je einem Registry-Eintrag und
— wenn er eine eigene Seite braucht — einer Landingpage. Am übrigen Code
ändert sich nichts. Näheres im [README](../README.md), Abschnitt „Einen
weiteren Typ ergänzen“.