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:
@@ -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
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
@@ -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“.
|
||||
Reference in New Issue
Block a user