From 7fcdcba6947689e1b222da9126f55379a779eb16 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Sat, 15 Aug 2026 07:15:25 +0200 Subject: [PATCH] Funktionsuebersicht und Roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 28 +++ docs/funktionen.md | 423 +++++++++++++++++++++++++++++++++++++++++++++ docs/roadmap.md | 152 ++++++++++++++++ 3 files changed, 603 insertions(+) create mode 100644 docs/funktionen.md create mode 100644 docs/roadmap.md diff --git a/README.md b/README.md index 197ae3d..b1f4649 100644 --- a/README.md +++ b/README.md @@ -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 --- diff --git a/docs/funktionen.md b/docs/funktionen.md new file mode 100644 index 0000000..9b92f7e --- /dev/null +++ b/docs/funktionen.md @@ -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 `` 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). diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..fb60cfe --- /dev/null +++ b/docs/roadmap.md @@ -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“.