From 586e1f042f6fdd1b5ed2a8005a0e63971d33e96c Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Fri, 21 Aug 2026 01:58:24 +0200 Subject: [PATCH] Versionshinweis mit Aenderungsverlauf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher liess sich der laufenden Anwendung nicht ansehen, welche Fassung sie ist. Nach einem Update war damit auch nicht zu erkennen, ob es angekommen ist — die haeufigste Frage im Betrieb. Die Fussleiste nennt jetzt die Version und fuehrt auf /version mit dem vollstaendigen Verlauf. Zusammengetragen ist er aus dem, was es schon gab: dem Abschnitt „Gebaut" der Roadmap und der Git-Historie. Gepflegt wird der Verlauf an genau einer Stelle, src/lib/changelog.ts. Daraus entsteht sowohl die Seite als auch die CHANGELOG.md (npm run changelog); eine Pruefung mit --check wacht darueber, dass beides zusammenpasst. Zwei gepflegte Listen liefen unweigerlich auseinander, und ein Changelog, der nicht stimmt, ist schlimmer als keiner. Die Historie hatte keine Tags. Die Eintraege sind deshalb nach Erscheinungstag zu Versionen zusammengefasst — die Nummern sind nachtraeglich vergeben, die Daten stammen aus den Commits. Zu jeder Version gibt es jetzt einen annotierten Tag im Repo; v1.6.0 folgt beim Merge. Die Roadmap fuehrt den Verlauf nicht mehr doppelt, sondern verweist darauf und behaelt den Blick nach vorn. Refs #9 --- CHANGELOG.md | 74 +++++++++++++++++++ README.md | 5 +- docs/funktionen.md | 15 ++++ docs/roadmap.md | 96 +++---------------------- package.json | 3 +- scripts/changelog.mjs | 84 ++++++++++++++++++++++ src/app/(app)/layout.tsx | 18 ++++- src/app/(app)/version/page.tsx | 83 ++++++++++++++++++++++ src/lib/changelog.ts | 126 +++++++++++++++++++++++++++++++++ 9 files changed, 412 insertions(+), 92 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 scripts/changelog.mjs create mode 100644 src/app/(app)/version/page.tsx create mode 100644 src/lib/changelog.ts diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7c7de80 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,74 @@ +# Änderungen + +Was in welcher Fassung dazugekommen ist. Dieselbe Liste steht in der +Anwendung unter **Version und Änderungen**, erreichbar über die +Versionsnummer in der Fußzeile. + +> Diese Datei wird erzeugt: `npm run changelog`. +> Gepflegt wird der Verlauf in `src/lib/changelog.ts`. + +## 1.6.0 — 21.08.2026 + +Sichtbare Version mit Änderungsverlauf. + +- Die laufende Version steht in der Fußzeile und führt auf eine Seite mit dem vollständigen Änderungsverlauf. +- Der Verlauf wird an einer Stelle gepflegt; die CHANGELOG.md im Projekt entsteht daraus. +- Die Roadmap verweist für Vergangenes auf den Changelog und behält den Blick nach vorn. + +## 1.5.0 — 20.08.2026 + +Statische Codes lassen sich benennen und ablegen. + +- Das Feld „Name“ gilt in beiden Betriebsarten, nicht mehr nur bei dynamischen Codes. +- Der Name bestimmt den Dateinamen beim Herunterladen. +- Ein statischer Code lässt sich in „Meine Codes“ ablegen — als Schnappschuss ohne Kurz-Link und ohne Scan-Zähler. + +## 1.4.0 — 17.08.2026 + +Sprechende Kurz-Links und eine früh erkannte Fehlkonfiguration. + +- Kurz-Links selbst vergeben und nachträglich ändern, mit „/“ als Trenner. +- Die vorherige Adresse bleibt gültig und lässt sich einzeln freigeben — gedruckte Codes bleiben gültig. +- Vergeben und Ändern ist Administratoren vorbehalten und in der Verwaltung für Bearbeiter freigebbar. +- Weicht BASE_URL von der aufgerufenen Adresse ab, meldet die Anmeldung das sofort statt erst am Ende. +- Auf der Anmeldeseite steht nicht mehr, wie die Anmeldung konfiguriert ist. + +## 1.3.0 — 16.08.2026 + +Eigenes Erscheinungsbild und ein Dark Mode. + +- Name, Logo, Favicon und Akzentfarbe sind im Admin-Bereich einstellbar. +- Hell und dunkel folgen dem Betriebssystem, mit Umschalter für eine eigene Wahl. +- Das Update-Skript erklärt, warum ein Vorspulen scheitert, statt nur abzubrechen. +- Die Authentik-Anleitung sagt deutlicher, wie die Redirect URI zu bilden ist. + +## 1.2.0 — 15.08.2026 + +Single Sign-on, Dokumentation und eine Sicherheitsdurchsicht. + +- Anmeldung über Authentik und andere OIDC-Anbieter, mit PKCE und geprüftem ID-Token. +- Bedienungsanleitung mit Bildern, Funktionsübersicht und Roadmap. +- Anmeldung und Sitzungen nach OWASP durchgesehen und gehärtet: stärkere Passwort-Parameter, Längengrenze, Herkunftsprüfung, Schutzheader, absolute Sitzungsdauer. +- Auf der Startseite steht „nur dynamisch“ statt eines nichtssagenden Abzeichens an jeder Kachel. + +## 1.1.0 — 14.08.2026 + +Alle fünfzehn QR-Typen, Anmeldung, Gruppen und mehrere Domains. + +- Vierzehn weitere QR-Typen — von vCard und WLAN bis PDF, Bild und Feedback. +- Landingpages als eigene Gattung für Typen ohne fremdes Ziel. +- Datei-Upload mit Inhaltsprüfung, Größengrenze und Aufräumen. +- Anmeldung, Benutzer, Gruppen und die Rollen Administrator, Bearbeiter und Betrachter. +- Mehrere Domains für Kurz-URLs, den Gruppen zugeteilt. +- „Meine Codes“ als Liste mit Suche, Sortierung und Gruppenfilter. +- Einzelne QR-Typen lassen sich in der Verwaltung ausblenden. + +## 1.0.0 — 13.08.2026 + +Erste lauffähige Fassung. + +- Next.js-Anwendung und PostgreSQL als Docker-Compose-Stack, Migrationen beim Start. +- QR-Typ Website, statisch und dynamisch, mit Kurz-URL, Weiterleitung und Scan-Zähler. +- Gestaltung mit Farben, Kontrastwarnung, Größe, Fehlerkorrektur und Rand. +- Download als PNG und SVG, serverseitig gerendert. +- Update-Skript für Linux, macOS und Windows. diff --git a/README.md b/README.md index 4d1d7d5..86826ca 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ entscheidet die Verwaltung — nicht gebrauchte lassen sich ausblenden. eigene Wahl. - **Sprechende Kurz-Links:** `qr.firma.de/r/sommeraktion` statt Zufallszeichen, nachträglich änderbar — die vorherige Adresse bleibt gültig. +- **Sichtbare Version:** Die Fußzeile nennt die laufende Fassung und führt + zum Änderungsverlauf — nach einem Update sofort erkennbar, ob es ankam. - **Zwei Container, kein Reverse Proxy nötig.** Migrationen laufen beim Start von selbst; alles Nötige steht in einer `.env`. @@ -48,7 +50,8 @@ entscheidet die Verwaltung — nicht gebrauchte lassen sich ausblenden. - **[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 +- **[Änderungen](CHANGELOG.md)** — was in welcher Version dazugekommen ist +- **[Roadmap](docs/roadmap.md)** — was als Nächstes kommen könnte - **[Anmeldung und Sitzungen nach OWASP](docs/sicherheit.md)** — Durchsicht, Härtungen und was bewusst offen bleibt diff --git a/docs/funktionen.md b/docs/funktionen.md index 1f7977d..1662174 100644 --- a/docs/funktionen.md +++ b/docs/funktionen.md @@ -500,6 +500,21 @@ alles zeigt und Typen aus späteren Versionen automatisch erscheinen. --- +## Version und Änderungen + +Die Fußzeile nennt die laufende Version und führt auf eine Seite mit dem +vollständigen Änderungsverlauf. Nach einem Update ist das die schnellste +Antwort auf die Frage, ob der Container wirklich mit dem neuen Abbild läuft: +Steht dort noch die alte Nummer, ist er es nicht. + +Der Verlauf wird an **einer** Stelle gepflegt (`src/lib/changelog.ts`). +Daraus entstehen sowohl die Seite in der Anwendung als auch die +[CHANGELOG.md](../CHANGELOG.md) im Projekt (`npm run changelog`); eine +Prüfung wacht darüber, dass beides zusammenpasst. Zu jeder Version gibt es +einen Git-Tag. + +--- + ## Schnittstelle Alles, was die Oberfläche tut, geht auch über HTTP: diff --git a/docs/roadmap.md b/docs/roadmap.md index eacefc1..85d1092 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -7,94 +7,16 @@ Aufwände sind grobe Einschätzungen, keine Zusagen. ## Gebaut -### Version 1 — Grundgerüst (13.08.2026) +Der vollständige Verlauf mit Versionsnummern steht im +[Changelog](../CHANGELOG.md) — und in der Anwendung selbst unter +**Version und Änderungen**, erreichbar über die Versionsnummer in der +Fußzeile. -- 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. - -### Erscheinungsbild (16.08.2026) - -Name, Logo, Favicon und Akzentfarbe im Admin-Bereich einstellbar. Die Farbe -wirkt über die Farbvariablen, die Tailwind ohnehin benutzt — deshalb ohne -Umbau der rund 60 eingefärbten Stellen. - -### Dark Mode (16.08.2026) - -Folgt dem Betriebssystem, mit Umschalter für Hell, Dunkel und System. -Umgesetzt über `light-dark()` statt über `dark:`-Klassen — deshalb ohne -Änderung an den rund 440 Farbklassen und ohne JavaScript für die Systemwahl. - -### Sprechende Kurz-Links (17.08.2026) - -Kurz-Links selbst vergeben und nachträglich ändern, mit `/` als Trenner. Die -vorherige Adresse bleibt gültig und lässt sich einzeln freigeben — sonst würde -jedes Umbenennen gedruckte Codes entwerten. Standardmäßig Administratoren -vorbehalten, per Einstellung für Bearbeiter freigebbar. - -Schließt [Issue #1](https://git.wilde-server.de/florian.wilde/qrcode-generator/issues/1). - -### Statische Codes benennen und ablegen (20.08.2026) - -Das Namensfeld gilt in beiden Betriebsarten. Es bestimmt den Dateinamen beim -Herunterladen und erlaubt, einen statischen Code in „Meine Codes" abzulegen — -als Schnappschuss, dessen Inhalt sich nicht mehr ändert. - -Schließt [Issue #2](https://git.wilde-server.de/florian.wilde/qrcode-generator/issues/2). - -### Dokumentation (15.08.2026) - -Bedienungsanleitung mit Bildern, Authentik-Anleitung, Funktionsübersicht. -Die Bilder erzeugt ein Skript, damit sie nicht veralten. +Kurz gefasst: Version 1.0.0 (13.08.2026) brachte das Grundgerüst mit dem Typ +Website, 1.1.0 alle fünfzehn Typen samt Anmeldung, Gruppen und Domains, +1.2.0 Single Sign-on und die Sicherheitsdurchsicht, 1.3.0 Erscheinungsbild +und Dark Mode, 1.4.0 sprechende Kurz-Links, 1.5.0 benennbare statische Codes +und 1.6.0 diesen Versionshinweis. --- diff --git a/package.json b/package.json index 8a222e2..7423cb9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "qrcode-generator", - "version": "1.1.0", + "version": "1.6.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", + "changelog": "node scripts/changelog.mjs", "lint": "eslint", "typecheck": "tsc --noEmit", "db:generate": "prisma generate", diff --git a/scripts/changelog.mjs b/scripts/changelog.mjs new file mode 100644 index 0000000..16ff5cc --- /dev/null +++ b/scripts/changelog.mjs @@ -0,0 +1,84 @@ +/** + * Schreibt CHANGELOG.md aus src/lib/changelog.ts. + * + * node scripts/changelog.mjs erzeugen + * node scripts/changelog.mjs --check nur prüfen, ob die Datei aktuell ist + * + * Warum erzeugt und nicht von Hand gepflegt: Die Anwendung zeigt denselben + * Verlauf unter /version an. Zwei gepflegte Listen liefen unweigerlich + * auseinander — und ein Changelog, der nicht stimmt, ist schlimmer als keiner. + */ +import { readFileSync, writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const wurzel = join(dirname(fileURLToPath(import.meta.url)), ".."); +const ziel = join(wurzel, "CHANGELOG.md"); + +/** + * Die Quelle ist TypeScript, dieses Skript aber schlichtes Node. Statt einen + * Übersetzer dazuzunehmen, wird der Datenteil gelesen und ausgewertet — er + * besteht ausschließlich aus Zeichenketten und Listen. + */ +function ladeVerlauf() { + const quelle = readFileSync(join(wurzel, "src", "lib", "changelog.ts"), "utf8"); + const anfang = quelle.indexOf("export const CHANGELOG"); + if (anfang === -1) throw new Error("CHANGELOG-Liste nicht gefunden."); + + const start = quelle.indexOf("[", anfang); + const ende = quelle.indexOf("\n];", start); + if (start === -1 || ende === -1) throw new Error("Liste nicht abgrenzbar."); + + const literal = quelle.slice(start, ende + 2); + return Function(`"use strict"; return (${literal});`)(); +} + +function formatiereDatum(iso) { + const [jahr, monat, tag] = iso.split("-"); + return `${tag}.${monat}.${jahr}`; +} + +function baue(verlauf) { + const zeilen = [ + "# Änderungen", + "", + "Was in welcher Fassung dazugekommen ist. Dieselbe Liste steht in der", + "Anwendung unter **Version und Änderungen**, erreichbar über die", + "Versionsnummer in der Fußzeile.", + "", + "> Diese Datei wird erzeugt: `npm run changelog`.", + "> Gepflegt wird der Verlauf in `src/lib/changelog.ts`.", + "", + ]; + + for (const eintrag of verlauf) { + zeilen.push(`## ${eintrag.version} — ${formatiereDatum(eintrag.date)}`, ""); + zeilen.push(eintrag.summary, ""); + for (const änderung of eintrag.changes) zeilen.push(`- ${änderung}`); + zeilen.push(""); + } + + return zeilen.join("\n"); +} + +const inhalt = baue(ladeVerlauf()); + +if (process.argv.includes("--check")) { + let vorhanden = ""; + try { + vorhanden = readFileSync(ziel, "utf8"); + } catch { + console.error("CHANGELOG.md fehlt. Erzeugen mit: npm run changelog"); + process.exit(1); + } + if (vorhanden !== inhalt) { + console.error( + "CHANGELOG.md passt nicht mehr zu src/lib/changelog.ts. Neu erzeugen mit: npm run changelog", + ); + process.exit(1); + } + console.log("CHANGELOG.md ist aktuell."); +} else { + writeFileSync(ziel, inhalt, "utf8"); + console.log("CHANGELOG.md geschrieben."); +} diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx index 91f362c..c2cd6ee 100644 --- a/src/app/(app)/layout.tsx +++ b/src/app/(app)/layout.tsx @@ -6,6 +6,7 @@ import { ThemeToggle } from "@/components/theme-toggle"; import { actorLabel, getActor, isAuthEnabled } from "@/lib/auth/actor"; import { can } from "@/lib/auth/policy"; import { getBranding } from "@/lib/branding"; +import { APP_VERSION } from "@/lib/changelog"; // Der Kopfbereich hängt von der Anmeldung ab und darf nicht vorgerendert // werden. @@ -128,9 +129,20 @@ export default async function AppLayout({ children }: LayoutProps<"/">) {
{children}
diff --git a/src/app/(app)/version/page.tsx b/src/app/(app)/version/page.tsx new file mode 100644 index 0000000..26f6c23 --- /dev/null +++ b/src/app/(app)/version/page.tsx @@ -0,0 +1,83 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import { Icon } from "@/components/icons"; +import { Badge, Card } from "@/components/ui"; +import { requirePageActor } from "@/lib/auth/guard"; +import { + APP_VERSION, + CHANGELOG, + formatChangelogDate, +} from "@/lib/changelog"; + +export const metadata: Metadata = { title: "Version und Änderungen" }; + +export const dynamic = "force-dynamic"; + +export default async function VersionPage() { + await requirePageActor(); + + return ( +
+ + + Zur Typ-Auswahl + + +
+

+ Version und Änderungen +

+

+ Hier läuft Version{" "} + {APP_VERSION}. + Darunter steht, was in welcher Fassung dazugekommen ist. +

+

+ Nach einem Update lohnt ein Blick hierher: Steht oben noch die alte + Nummer, läuft der Container noch mit dem alten Abbild. +

+
+ +
    + {CHANGELOG.map((eintrag) => { + const laeuft = eintrag.version === APP_VERSION; + return ( +
  1. + +
    +

    + {eintrag.version} +

    + + {formatChangelogDate(eintrag.date)} + + {laeuft ? ( + läuft hier + ) : null} +
    + +

    {eintrag.summary}

    + +
      + {eintrag.changes.map((zeile) => ( +
    • +
    • + ))} +
    +
    +
  2. + ); + })} +
+
+ ); +} diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts new file mode 100644 index 0000000..d6d8c5e --- /dev/null +++ b/src/lib/changelog.ts @@ -0,0 +1,126 @@ +import pkg from "../../package.json"; + +/** + * Änderungsverlauf der Anwendung — die einzige Quelle dafür. + * + * Hieraus entsteht beides: die Seite unter `/version` und die `CHANGELOG.md` + * im Wurzelverzeichnis (`npm run changelog`). Zwei gepflegte Listen liefen + * unweigerlich auseinander; eine Prüfung wacht darüber, dass die erzeugte + * Datei zu dieser Quelle passt. + * + * Die Einträge sind aus dem Abschnitt „Gebaut“ der Roadmap und der + * Git-Historie zusammengetragen. Versionsnummern gab es dabei nie — es + * existieren keine Tags —, deshalb ist je **Erscheinungstag** eine Version + * gebildet. Das ist nachträglich vergeben, aber die Daten selbst stimmen. + */ +export type ChangelogEntry = { + version: string; + /** ISO-Datum, an dem der Stand entstanden ist. */ + date: string; + /** Eine Zeile zur Einordnung — was diese Version ausmacht. */ + summary: string; + changes: string[]; +}; + +export const CHANGELOG: readonly ChangelogEntry[] = [ + { + version: "1.6.0", + date: "2026-08-21", + summary: "Sichtbare Version mit Änderungsverlauf.", + changes: [ + "Die laufende Version steht in der Fußzeile und führt auf eine Seite mit dem vollständigen Änderungsverlauf.", + "Der Verlauf wird an einer Stelle gepflegt; die CHANGELOG.md im Projekt entsteht daraus.", + "Die Roadmap verweist für Vergangenes auf den Changelog und behält den Blick nach vorn.", + ], + }, + { + version: "1.5.0", + date: "2026-08-20", + summary: "Statische Codes lassen sich benennen und ablegen.", + changes: [ + "Das Feld „Name“ gilt in beiden Betriebsarten, nicht mehr nur bei dynamischen Codes.", + "Der Name bestimmt den Dateinamen beim Herunterladen.", + "Ein statischer Code lässt sich in „Meine Codes“ ablegen — als Schnappschuss ohne Kurz-Link und ohne Scan-Zähler.", + ], + }, + { + version: "1.4.0", + date: "2026-08-17", + summary: "Sprechende Kurz-Links und eine früh erkannte Fehlkonfiguration.", + changes: [ + "Kurz-Links selbst vergeben und nachträglich ändern, mit „/“ als Trenner.", + "Die vorherige Adresse bleibt gültig und lässt sich einzeln freigeben — gedruckte Codes bleiben gültig.", + "Vergeben und Ändern ist Administratoren vorbehalten und in der Verwaltung für Bearbeiter freigebbar.", + "Weicht BASE_URL von der aufgerufenen Adresse ab, meldet die Anmeldung das sofort statt erst am Ende.", + "Auf der Anmeldeseite steht nicht mehr, wie die Anmeldung konfiguriert ist.", + ], + }, + { + version: "1.3.0", + date: "2026-08-16", + summary: "Eigenes Erscheinungsbild und ein Dark Mode.", + changes: [ + "Name, Logo, Favicon und Akzentfarbe sind im Admin-Bereich einstellbar.", + "Hell und dunkel folgen dem Betriebssystem, mit Umschalter für eine eigene Wahl.", + "Das Update-Skript erklärt, warum ein Vorspulen scheitert, statt nur abzubrechen.", + "Die Authentik-Anleitung sagt deutlicher, wie die Redirect URI zu bilden ist.", + ], + }, + { + version: "1.2.0", + date: "2026-08-15", + summary: "Single Sign-on, Dokumentation und eine Sicherheitsdurchsicht.", + changes: [ + "Anmeldung über Authentik und andere OIDC-Anbieter, mit PKCE und geprüftem ID-Token.", + "Bedienungsanleitung mit Bildern, Funktionsübersicht und Roadmap.", + "Anmeldung und Sitzungen nach OWASP durchgesehen und gehärtet: stärkere Passwort-Parameter, Längengrenze, Herkunftsprüfung, Schutzheader, absolute Sitzungsdauer.", + "Auf der Startseite steht „nur dynamisch“ statt eines nichtssagenden Abzeichens an jeder Kachel.", + ], + }, + { + version: "1.1.0", + date: "2026-08-14", + summary: "Alle fünfzehn QR-Typen, Anmeldung, Gruppen und mehrere Domains.", + changes: [ + "Vierzehn weitere QR-Typen — von vCard und WLAN bis PDF, Bild und Feedback.", + "Landingpages als eigene Gattung für Typen ohne fremdes Ziel.", + "Datei-Upload mit Inhaltsprüfung, Größengrenze und Aufräumen.", + "Anmeldung, Benutzer, Gruppen und die Rollen Administrator, Bearbeiter und Betrachter.", + "Mehrere Domains für Kurz-URLs, den Gruppen zugeteilt.", + "„Meine Codes“ als Liste mit Suche, Sortierung und Gruppenfilter.", + "Einzelne QR-Typen lassen sich in der Verwaltung ausblenden.", + ], + }, + { + version: "1.0.0", + date: "2026-08-13", + summary: "Erste lauffähige Fassung.", + changes: [ + "Next.js-Anwendung und PostgreSQL als Docker-Compose-Stack, Migrationen beim Start.", + "QR-Typ Website, statisch und dynamisch, mit Kurz-URL, Weiterleitung und Scan-Zähler.", + "Gestaltung mit Farben, Kontrastwarnung, Größe, Fehlerkorrektur und Rand.", + "Download als PNG und SVG, serverseitig gerendert.", + "Update-Skript für Linux, macOS und Windows.", + ], + }, +]; + +/** + * Die laufende Version. + * + * Aus der `package.json`, damit es genau eine Stelle gibt, an der sie steht. + * Der Wert wird beim Bauen eingesetzt — im Container liegt kein Git, aus dem + * sich sonst etwas ableiten ließe. + */ +export const APP_VERSION: string = pkg.version; + +/** Der Eintrag zur laufenden Version, falls er im Verlauf steht. */ +export function currentEntry(): ChangelogEntry | undefined { + return CHANGELOG.find((eintrag) => eintrag.version === APP_VERSION); +} + +/** Datum als „21.08.2026“ — ohne Abhängigkeit von der Zeitzone des Servers. */ +export function formatChangelogDate(iso: string): string { + const [jahr, monat, tag] = iso.split("-"); + return `${tag}.${monat}.${jahr}`; +}