feat: Versionierung mit Changelog, Version 1.0.0
Bis jetzt stand nirgends, welcher Stand laeuft. Beim Aktualisieren auf einem Server ist das die erste Frage. CHANGELOG.md nach Keep a Changelog, Versionsnummern nach SemVer. Der Changelog sagt, was sich beim Arbeiten aendert - nicht, welche Datei angefasst wurde. Dazu eine Tabelle, was Major, Minor und Patch fuer den Betreiber praktisch bedeuten, und der Hinweis, dass sich eine Datenbank-Migration nicht zurueckrollen laesst. Die Version steht bewusst an zwei Stellen: package.json und src/lib/version.ts. Ein Import der package.json waere im Standalone-Build eine andere Datei als die des Projekts - die Zahl haenge dann an der Laufzeit. Ein Test haelt beide zusammen, und "npm run version" setzt sie gemeinsam samt Changelog-Abschnitt. Committet und getaggt wird nicht automatisch. Ein Changelog-Eintrag, den niemand gegengelesen hat, ist keinen Deut besser als keiner. Sichtbar wird die Version unten in der Seitenleiste und unter /api/gesundheit - dort mit Git-Stand, den der Docker-Build hineinreicht. Auf der Anmeldeseite steht sie nicht: wer nicht angemeldet ist, muss nicht wissen, welche Fassung hier laeuft. Etappe: 8 Phase: Haertung
This commit is contained in:
+192
@@ -0,0 +1,192 @@
|
|||||||
|
# Änderungen
|
||||||
|
|
||||||
|
Alle nennenswerten Änderungen an dieser Anwendung, in umgekehrter
|
||||||
|
Reihenfolge. Aufbau nach [Keep a Changelog](https://keepachangelog.com/de/1.1.0/),
|
||||||
|
Versionsnummern nach [Semantic Versioning](https://semver.org/lang/de/).
|
||||||
|
|
||||||
|
**Was hier steht:** was sich für dich beim Arbeiten ändert — nicht, welche
|
||||||
|
Datei angefasst wurde. Wer den technischen Verlauf braucht, findet ihn im
|
||||||
|
Git-Log; jeder Commit trägt seine Etappe.
|
||||||
|
|
||||||
|
**Was die Zahlen bedeuten:**
|
||||||
|
|
||||||
|
| Stelle | Wann sie steigt | Was du tun musst |
|
||||||
|
|---|---|---|
|
||||||
|
| **Major** (1.x.x → 2.0.0) | Etwas verhält sich anders als vorher, oder eine Migration lässt sich nicht zurücknehmen | Changelog lesen, **vorher sichern**, Zeit einplanen |
|
||||||
|
| **Minor** (1.0.x → 1.1.0) | Neue Funktionen, alles Bisherige bleibt wie es war | Changelog überfliegen, normal aktualisieren |
|
||||||
|
| **Patch** (1.0.0 → 1.0.1) | Fehler behoben, sonst nichts | Einfach aktualisieren |
|
||||||
|
|
||||||
|
Eine Datenbank-Migration lässt sich **nicht** zurückrollen. Vor jedem
|
||||||
|
Update ein Backup anstoßen — siehe README, Abschnitt *Sicherung und
|
||||||
|
Wiederherstellung*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [Unveröffentlicht]
|
||||||
|
|
||||||
|
_Noch nichts._
|
||||||
|
|
||||||
|
## [1.0.0] – 2026-08-22
|
||||||
|
|
||||||
|
Erste vollständige Fassung. Alle acht Etappen umgesetzt, alle
|
||||||
|
Abnahmekriterien als Test hinterlegt.
|
||||||
|
|
||||||
|
### Hinzugefügt
|
||||||
|
|
||||||
|
**Der Alltag**
|
||||||
|
|
||||||
|
- **Dashboard „Heute"** — beantwortet morgens, was ansteht: Fristen-Ampel
|
||||||
|
(überfällig / diese Woche / diesen Monat), wer gerade am Zug ist,
|
||||||
|
offene Nachforderungen des Bezirks, ungeprüfter Posteingang,
|
||||||
|
unverbuchte Belege, Kennzahlen zu Vermögen und Heimkosten.
|
||||||
|
- **Fristen** — die gesetzlichen Pflichtfristen entstehen von selbst aus
|
||||||
|
dem Bestellungsdatum: Vermögensverzeichnis und Anfangsbericht nach drei
|
||||||
|
Monaten, Jahresbericht und Vermögensübersicht zum Ende jedes
|
||||||
|
Betreuungsjahres, Aufwandspauschale, Landespflegegeld,
|
||||||
|
Zuzahlungsbefreiung, Rückkaufswerte. Liste, Monatskalender und ein
|
||||||
|
ICS-Feed zum Abonnieren im eigenen Kalender.
|
||||||
|
- **Vorgänge** als Fallakte mit Zeitstrahl über Dokumente, Protokolle,
|
||||||
|
Fristen und Buchungen. Neun Vorlagen mit vorbefüllter Aufgabenliste,
|
||||||
|
darunter Heimeinzug, Antrag Hilfe zur Pflege, Wohnungsauflösung,
|
||||||
|
Hausverkauf, Darlehen nach § 91 SGB XII und Widerspruch gegen einen
|
||||||
|
Bescheid.
|
||||||
|
- **Kontakte und Kontaktprotokoll** — Verzeichnis aller Stellen mit
|
||||||
|
Aktenzeichen, dazu eine Schnellerfassung, die am Handy in unter zwanzig
|
||||||
|
Sekunden zu bedienen ist.
|
||||||
|
|
||||||
|
**Papier**
|
||||||
|
|
||||||
|
- **Dokumentenablage** mit Drag & Drop, Sammel-Upload und Handy-Foto.
|
||||||
|
Texterkennung auf Deutsch, Volltextsuche über alle Schreiben,
|
||||||
|
Posteingangsbuch mit den Stufen neu → gesichtet → zugeordnet →
|
||||||
|
beantwortet, Feld für den Ablageort des Papieroriginals.
|
||||||
|
- Beim Import werden **Absender, Datum, Aktenzeichen, Dokumentart und
|
||||||
|
eine im Text genannte Frist vorgeschlagen** — mit der Textstelle
|
||||||
|
daneben, damit man prüfen kann. Übernommen wird nur auf Klick.
|
||||||
|
- **Belegmappe**: alle Dokumente eines Vorgangs oder Zeitraums als eine
|
||||||
|
chronologisch sortierte, paginierte PDF mit Deckblatt und
|
||||||
|
Inhaltsverzeichnis.
|
||||||
|
|
||||||
|
**Geld**
|
||||||
|
|
||||||
|
- **Kassenbuch** mit Belegzwang, Kategorien, CSV- und CAMT.053-Import.
|
||||||
|
Harte Trennung zwischen dem Vermögen der betreuten Person und eigenen
|
||||||
|
Auslagen, mit Warnung bei Buchungen auf ein als privat markiertes Konto.
|
||||||
|
- **Konten** mit verschlüsselter IBAN und Saldoständen je Stichtag,
|
||||||
|
**Vermögensposten** mit vollständiger Wert-Historie und Pflichtangabe
|
||||||
|
zur Wertquelle. Werte werden nie überschrieben.
|
||||||
|
- **Vermögensverzeichnis (§ 1835 BGB)** und **Vermögensübersicht bzw.
|
||||||
|
Rechnungslegung (§§ 1859, 1865 BGB)** als druckfertige PDF, in zwei
|
||||||
|
Ausgabevarianten für befreite und nicht befreite Betreuer.
|
||||||
|
|
||||||
|
**Behörde**
|
||||||
|
|
||||||
|
- **Sozialhilfe-Modul** für das Verfahren beim Bezirk: Antragsakte,
|
||||||
|
Nachforderungs-Tracker mit Fortschrittsbalken, vorbefüllte Checkliste
|
||||||
|
der Standardunterlagen mit Begründung, warum der Träger sie will.
|
||||||
|
- **Bescheid-Prüfung** mit Berechnung der Rechtsbehelfsfrist ab dem
|
||||||
|
Zugangsdatum, einschließlich Wochenende, bayerischer Feiertage und dem
|
||||||
|
Sonderfall fehlender Rechtsbehelfsbelehrung. Der Rechenweg wird
|
||||||
|
ausgegeben.
|
||||||
|
- **Heimkosten-Gegenüberstellung** Monat für Monat: Kosten gegen
|
||||||
|
Pflegekasse, Leistungszuschlag nach § 43c SGB XI, Rente und
|
||||||
|
Sozialhilfeanteil — die Deckungslücke wird sichtbar.
|
||||||
|
- **Schonvermögens-Bewertung** je Posten (verwertbar / geschützt /
|
||||||
|
strittig) mit Begründungsfeld und Textbausteinen zu § 90 SGB XII.
|
||||||
|
- **Katalog weiterer Leistungen**: Landespflegegeld, Zuzahlungsbefreiung,
|
||||||
|
Rundfunkbeitragsbefreiung, Schwerbehindertenausweis, Grundsicherung.
|
||||||
|
|
||||||
|
**Substanz**
|
||||||
|
|
||||||
|
- **Immobilie** mit Grundbuchdaten, Belastungen nach Abteilung II und
|
||||||
|
III, Wert-Historie, Status im Sozialhilfeverfahren und einer
|
||||||
|
Pflichten-Checkliste bei Leerstand — zehn Punkte, jeder mit Begründung.
|
||||||
|
- **Versicherungen und Verträge** mit Kündigungsfristen-Rechner, der den
|
||||||
|
spätesten Absendetag nennt, Anschreiben zur Rückkaufswert-Anfrage mit
|
||||||
|
automatischer Wiedervorlage und einem Kündigungs-Assistenten.
|
||||||
|
- **Heim und Pflege** mit Heimvertrag, Leistungszuschlag-Staffel,
|
||||||
|
Pflegegrad-Historie, Abwesenheitstagen und einem Gesundheitsverlauf mit
|
||||||
|
COPD-Bezug: Exazerbationen, Sauerstoff, Stürze, Krankenhaus,
|
||||||
|
Hilfsmittel, Impfungen nach STIKO-Plan.
|
||||||
|
|
||||||
|
**Ausgabe**
|
||||||
|
|
||||||
|
- **Jahresbericht (§ 1863 BGB)** als Entwurf zum Überarbeiten, gespeist
|
||||||
|
aus Kontaktprotokollen, Gesundheitsereignissen und Vorgangsstatus.
|
||||||
|
Ausgabe als PDF und als DOCX.
|
||||||
|
- **Unterlagenpaket für den Bezirk**: die im Tracker markierten
|
||||||
|
Dokumente als eine PDF mit Anschreiben und Inhaltsverzeichnis.
|
||||||
|
- **Vollexport** als ZIP mit allen Originaldateien, allen Daten als
|
||||||
|
entschlüsseltes JSON, den erzeugten Berichten, einer LIESMICH und
|
||||||
|
SHA-256-Prüfsummen.
|
||||||
|
- **Wissensbasis** mit 37 Hinweiskarten, pflegbar als JSON-Datei ohne
|
||||||
|
Neustart. Jede mit Rechtsgrundlage, Link und dem stehenden Vermerk,
|
||||||
|
dass sie keine Rechtsberatung ersetzt.
|
||||||
|
|
||||||
|
**Verwaltung**
|
||||||
|
|
||||||
|
- **Mitleser-Rolle** mit modulweiser Freigabe und ausdrücklicher Warnung
|
||||||
|
bei Gesundheit, Finanzen und Sozialhilfe.
|
||||||
|
- **Papierkorb** mit 30 Tagen Aufbewahrung.
|
||||||
|
- **Wartungslauf** über `/api/wartung`: Fristerinnerungen per E-Mail,
|
||||||
|
Papierkorb leeren, alte Anmeldeversuche entfernen.
|
||||||
|
|
||||||
|
### Sicherheit
|
||||||
|
|
||||||
|
- **Pflicht-Zweitfaktor** (TOTP nach RFC 6238). Lässt sich nicht
|
||||||
|
abschalten. Acht Wiederherstellungscodes, einzeln Argon2id-gehasht.
|
||||||
|
- **Anwendungsseitige Feldverschlüsselung** mit AES-256-GCM für
|
||||||
|
Diagnosen, den ermittelten Willen, IBANs, Versicherungs-, Renten- und
|
||||||
|
Steuernummern sowie alle Freitext-Notizen. Der Feldname wird
|
||||||
|
mitauthentifiziert — ein Geheimtext lässt sich nicht von einem Feld in
|
||||||
|
ein anderes umhängen. Schlüsselrotation ohne Datenverlust vorgesehen.
|
||||||
|
- **Argon2id** nach OWASP-Empfehlung, mit automatischem Nachziehen bei
|
||||||
|
späterer Parametererhöhung.
|
||||||
|
- **Rate-Limiting** am Login nach E-Mail und Anschluss. Die Sperre wird
|
||||||
|
geprüft, bevor überhaupt gerechnet wird, und die Antwortzeit ist
|
||||||
|
gestreckt — sie verrät nicht, welche Adressen existieren.
|
||||||
|
- **Unveränderliches Audit-Log**: ein Datenbank-Trigger weist UPDATE und
|
||||||
|
DELETE ab, auch bei direktem Zugriff mit `psql`.
|
||||||
|
- **Strenge Content-Security-Policy** mit Nonce pro Anfrage. Keine
|
||||||
|
externen Schriften, kein CDN, keine Tracker — nachgewiesen durch einen
|
||||||
|
Test, der jede Anfrage an einen fremden Host als Fehler wertet.
|
||||||
|
- **Uploads** werden an den Magic Bytes geprüft, nicht an Endung oder
|
||||||
|
gemeldetem MIME-Typ. Ausgeliefert wird nur über kurzlebige signierte
|
||||||
|
URLs, immer als Anhang mit `nosniff`.
|
||||||
|
- **Erneute Anmeldung** vor Export, endgültigem Löschen und
|
||||||
|
Rollenänderungen. Ein Passwortwechsel beendet alle offenen Sitzungen.
|
||||||
|
|
||||||
|
### Betrieb
|
||||||
|
|
||||||
|
- Docker Compose mit Anwendung, PostgreSQL, MinIO und Sicherungsdienst.
|
||||||
|
**Ohne Reverse Proxy** — der ist auf dem Zielserver vorhanden. Die
|
||||||
|
Datenbank hat keine Portfreigabe.
|
||||||
|
- **Tägliche verschlüsselte Sicherung** um 03:15 Uhr mit Selbstprüfung:
|
||||||
|
lässt sich die Datei nicht wieder entschlüsseln, endet der Lauf mit
|
||||||
|
Fehler statt mit einem stillen Scheinerfolg.
|
||||||
|
- Verarbeitungsverzeichnis und Datenschutzhinweise als ausfüllbare
|
||||||
|
Textbausteine.
|
||||||
|
- Bauprotokoll aus den Commit-Trailern.
|
||||||
|
|
||||||
|
### Bekannte Einschränkungen
|
||||||
|
|
||||||
|
- Die Texterkennung ist gegen ein echtes Tesseract **nicht** end-to-end
|
||||||
|
geprüft. Der Weg „nicht verfügbar, Upload funktioniert trotzdem" ist
|
||||||
|
getestet.
|
||||||
|
- Der CAMT.053-Import ist nur gegen selbst gebaute Beispiele geprüft, nicht
|
||||||
|
gegen einen echten Bankauszug.
|
||||||
|
- Das Kostenkonto der Immobilie wertet über Buchungskategorien aus und
|
||||||
|
wäre bei einem **zweiten** Grundstück falsch. Die Oberfläche sagt das
|
||||||
|
an der Auswertung dazu.
|
||||||
|
- Eine Aufgabe aus einer Vorgangs-Vorlage lässt sich nicht entfernen —
|
||||||
|
passt sie im Einzelfall nicht, bleibt sie offen stehen.
|
||||||
|
- `@react-pdf/renderer` braucht in diesem Abhängigkeitsbaum eine Krücke
|
||||||
|
(`src/lib/pdf/standardschrift.ts`), weil seine Unterabhängigkeiten für
|
||||||
|
Standardschriften kein Schriftobjekt mehr liefern.
|
||||||
|
|
||||||
|
Was davon wann angegangen wird, steht in [`docs/ROADMAP.md`](docs/ROADMAP.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Unveröffentlicht]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare/v1.0.0...HEAD
|
||||||
|
[1.0.0]: https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/releases/tag/v1.0.0
|
||||||
+10
@@ -43,6 +43,16 @@ COPY . .
|
|||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV SKIP_ENV_VALIDATION=1
|
ENV SKIP_ENV_VALIDATION=1
|
||||||
|
|
||||||
|
# Git-Stand und Bauzeitpunkt landen in der Oberflaeche und im
|
||||||
|
# Gesundheitsendpunkt. Beim Aktualisieren sieht man damit auf einen Blick,
|
||||||
|
# ob wirklich der neue Stand laeuft:
|
||||||
|
#
|
||||||
|
# docker build --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) .
|
||||||
|
ARG GIT_COMMIT=""
|
||||||
|
ARG BUILD_ZEIT=""
|
||||||
|
ENV NEXT_PUBLIC_GIT_COMMIT=$GIT_COMMIT
|
||||||
|
ENV NEXT_PUBLIC_BUILD_ZEIT=$BUILD_ZEIT
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# --- Stufe 3: Laufzeit -------------------------------------------------------
|
# --- Stufe 3: Laufzeit -------------------------------------------------------
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
# In der Oberflaeche und unter /api/gesundheit sichtbar. Setzen mit:
|
||||||
|
# GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
|
||||||
|
GIT_COMMIT: ${GIT_COMMIT:-}
|
||||||
|
BUILD_ZEIT: ${BUILD_ZEIT:-}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "betreuungs-cockpit",
|
"name": "betreuungs-cockpit",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -28,7 +28,8 @@
|
|||||||
"protokoll": "node scripts/bauprotokoll.mjs",
|
"protokoll": "node scripts/bauprotokoll.mjs",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
"protokoll:md": "node scripts/bauprotokoll.mjs --md",
|
"protokoll:md": "node scripts/bauprotokoll.mjs --md",
|
||||||
"wartungsschluessel": "node --env-file=.env scripts/wartungsschluessel.mjs"
|
"wartungsschluessel": "node --env-file=.env scripts/wartungsschluessel.mjs",
|
||||||
|
"version": "node scripts/version.mjs"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "tsx prisma/seed.ts"
|
"seed": "tsx prisma/seed.ts"
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Version anheben.
|
||||||
|
*
|
||||||
|
* npm run version -- patch 1.0.0 -> 1.0.1
|
||||||
|
* npm run version -- minor 1.0.0 -> 1.1.0
|
||||||
|
* npm run version -- major 1.0.0 -> 2.0.0
|
||||||
|
* npm run version -- 1.2.3 genau diese Version
|
||||||
|
*
|
||||||
|
* Was passiert:
|
||||||
|
* 1. package.json und src/lib/version.ts werden gesetzt - beide, damit
|
||||||
|
* sie nicht auseinanderlaufen.
|
||||||
|
* 2. Im CHANGELOG wird aus "## [Unveroeffentlicht]" ein Abschnitt mit
|
||||||
|
* Versionsnummer und heutigem Datum, darueber ein neuer leerer
|
||||||
|
* Abschnitt.
|
||||||
|
* 3. Der Vergleichslink am Dateiende wird nachgezogen.
|
||||||
|
*
|
||||||
|
* Was NICHT passiert: committen und taggen. Das machst du selbst, nachdem
|
||||||
|
* du den Changelog-Eintrag gegengelesen hast. Ein automatisch erzeugter
|
||||||
|
* Changelog, den niemand liest, ist keinen Deut besser als keiner.
|
||||||
|
*
|
||||||
|
* Danach:
|
||||||
|
* git add -A && git commit -m "chore: Version 1.0.1"
|
||||||
|
* git tag -a v1.0.1 -m "..." && git push --follow-tags
|
||||||
|
*/
|
||||||
|
import { readFileSync, writeFileSync } from 'node:fs'
|
||||||
|
|
||||||
|
const WURZEL = new URL('..', import.meta.url)
|
||||||
|
const pfad = (name) => new URL(name, WURZEL)
|
||||||
|
|
||||||
|
function lesen(name) {
|
||||||
|
return readFileSync(pfad(name), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
function schreiben(name, inhalt) {
|
||||||
|
writeFileSync(pfad(name), inhalt, 'utf8')
|
||||||
|
console.log(` ${name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ziel bestimmen ---------------------------------------------------------
|
||||||
|
|
||||||
|
const argument = process.argv[2]
|
||||||
|
if (!argument) {
|
||||||
|
console.error('Aufruf: npm run version -- <patch|minor|major|x.y.z>')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const paket = JSON.parse(lesen('package.json'))
|
||||||
|
const alt = paket.version
|
||||||
|
|
||||||
|
function naechste(aktuell, art) {
|
||||||
|
const treffer = aktuell.match(/^(\d+)\.(\d+)\.(\d+)$/)
|
||||||
|
if (!treffer) throw new Error(`Version "${aktuell}" ist kein x.y.z`)
|
||||||
|
const [haupt, neben, fehler] = treffer.slice(1).map(Number)
|
||||||
|
|
||||||
|
switch (art) {
|
||||||
|
case 'major':
|
||||||
|
return `${haupt + 1}.0.0`
|
||||||
|
case 'minor':
|
||||||
|
return `${haupt}.${neben + 1}.0`
|
||||||
|
case 'patch':
|
||||||
|
return `${haupt}.${neben}.${fehler + 1}`
|
||||||
|
default:
|
||||||
|
if (!/^\d+\.\d+\.\d+$/.test(art)) {
|
||||||
|
throw new Error(`"${art}" ist weder patch/minor/major noch x.y.z`)
|
||||||
|
}
|
||||||
|
return art
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const neu = naechste(alt, argument)
|
||||||
|
if (neu === alt) {
|
||||||
|
console.error(`Version ist bereits ${alt}.`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n${alt} -> ${neu}\n`)
|
||||||
|
console.log('Geaendert:')
|
||||||
|
|
||||||
|
// --- 1. package.json --------------------------------------------------------
|
||||||
|
|
||||||
|
paket.version = neu
|
||||||
|
schreiben('package.json', `${JSON.stringify(paket, null, 2)}\n`)
|
||||||
|
|
||||||
|
// --- 2. src/lib/version.ts --------------------------------------------------
|
||||||
|
|
||||||
|
const versionsdatei = lesen('src/lib/version.ts')
|
||||||
|
const ersetzt = versionsdatei.replace(
|
||||||
|
/export const VERSION = '[^']+'/,
|
||||||
|
`export const VERSION = '${neu}'`,
|
||||||
|
)
|
||||||
|
if (ersetzt === versionsdatei) {
|
||||||
|
console.error('FEHLER: VERSION in src/lib/version.ts nicht gefunden.')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
schreiben('src/lib/version.ts', ersetzt)
|
||||||
|
|
||||||
|
// --- 3. CHANGELOG.md --------------------------------------------------------
|
||||||
|
|
||||||
|
const heute = new Date().toISOString().slice(0, 10)
|
||||||
|
let changelog = lesen('CHANGELOG.md')
|
||||||
|
|
||||||
|
const UNVEROEFFENTLICHT = '## [Unveröffentlicht]'
|
||||||
|
if (!changelog.includes(UNVEROEFFENTLICHT)) {
|
||||||
|
console.error(`FEHLER: "${UNVEROEFFENTLICHT}" steht nicht im CHANGELOG.`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const leererAbschnitt = [
|
||||||
|
UNVEROEFFENTLICHT,
|
||||||
|
'',
|
||||||
|
'_Noch nichts._',
|
||||||
|
'',
|
||||||
|
`## [${neu}] – ${heute}`,
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
changelog = changelog.replace(UNVEROEFFENTLICHT, leererAbschnitt)
|
||||||
|
|
||||||
|
// Vergleichslinks am Dateiende nachziehen.
|
||||||
|
const basis = 'https://git.wilde-server.de/florian.wilde/betreuungs-cockpit/compare'
|
||||||
|
changelog = changelog.replace(
|
||||||
|
/^\[Unveröffentlicht\]:.*$/m,
|
||||||
|
`[Unveröffentlicht]: ${basis}/v${neu}...HEAD\n[${neu}]: ${basis}/v${alt}...v${neu}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
schreiben('CHANGELOG.md', changelog)
|
||||||
|
|
||||||
|
// --- Hinweis ----------------------------------------------------------------
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
Jetzt:
|
||||||
|
1. CHANGELOG.md gegenlesen - steht dort, was sich fuer den Nutzer
|
||||||
|
tatsaechlich aendert? Nicht, welche Datei angefasst wurde.
|
||||||
|
2. git add -A && git commit -m "chore: Version ${neu}"
|
||||||
|
3. git tag -a v${neu} -m "Version ${neu}"
|
||||||
|
4. git push --follow-tags
|
||||||
|
`)
|
||||||
@@ -7,6 +7,7 @@ import { aktiveBetreute, erfordereBenutzer, erfordereZweitenFaktor, meineBetreut
|
|||||||
import { darf } from '@/lib/auth/rollen'
|
import { darf } from '@/lib/auth/rollen'
|
||||||
import { ROLLEN_BESCHRIFTUNG } from '@/lib/auth/rollen'
|
import { ROLLEN_BESCHRIFTUNG } from '@/lib/auth/rollen'
|
||||||
import { db, nichtGeloescht } from '@/lib/db'
|
import { db, nichtGeloescht } from '@/lib/db'
|
||||||
|
import { versionstext } from '@/lib/version'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rahmen aller Fachseiten.
|
* Rahmen aller Fachseiten.
|
||||||
@@ -64,6 +65,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod
|
|||||||
return (
|
return (
|
||||||
<AppHuelle
|
<AppHuelle
|
||||||
navigation={navigation}
|
navigation={navigation}
|
||||||
|
version={versionstext()}
|
||||||
kopfzeile={
|
kopfzeile={
|
||||||
<BetreuteAuswahl
|
<BetreuteAuswahl
|
||||||
aktiveId={aktive.id}
|
aktiveId={aktive.id}
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { db } from '@/lib/db'
|
import { db } from '@/lib/db'
|
||||||
|
import { COMMIT, VERSION } from '@/lib/version'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Betriebsbereitschaft fuer den Healthcheck in docker-compose.
|
* Betriebsbereitschaft fuer den Healthcheck in docker-compose.
|
||||||
*
|
*
|
||||||
* Bewusst ohne Anmeldung, aber auch ohne jede Auskunft ueber Inhalte:
|
* Bewusst ohne Anmeldung, aber ohne jede Auskunft ueber Inhalte: geprueft
|
||||||
* geprueft wird nur, ob die Datenbank antwortet. Version und Datenbestand
|
* wird nur, ob die Datenbank antwortet.
|
||||||
* gehen niemanden etwas an, der die Adresse errät.
|
*
|
||||||
|
* Die Version steht dabei: sie wird beim Aktualisieren gebraucht, um zu
|
||||||
|
* sehen, ob der neue Stand tatsaechlich laeuft. Das ist keine Auskunft
|
||||||
|
* ueber die betreuten Daten, und die Adresse ist ohnehin nur ueber den
|
||||||
|
* eigenen Reverse Proxy erreichbar.
|
||||||
*/
|
*/
|
||||||
export const runtime = 'nodejs'
|
export const runtime = 'nodejs'
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
@@ -14,10 +19,13 @@ export const dynamic = 'force-dynamic'
|
|||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
await db.$queryRaw`SELECT 1`
|
await db.$queryRaw`SELECT 1`
|
||||||
return NextResponse.json({ status: 'bereit' }, { headers: { 'Cache-Control': 'no-store' } })
|
return NextResponse.json(
|
||||||
|
{ status: 'bereit', version: VERSION, commit: COMMIT || undefined },
|
||||||
|
{ headers: { 'Cache-Control': 'no-store' } },
|
||||||
|
)
|
||||||
} catch {
|
} catch {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ status: 'datenbank nicht erreichbar' },
|
{ status: 'datenbank nicht erreichbar', version: VERSION },
|
||||||
{ status: 503, headers: { 'Cache-Control': 'no-store' } },
|
{ status: 503, headers: { 'Cache-Control': 'no-store' } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ export function AppHuelle({
|
|||||||
kopfzeile,
|
kopfzeile,
|
||||||
schnellerfassung,
|
schnellerfassung,
|
||||||
benutzerbereich,
|
benutzerbereich,
|
||||||
|
version,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
navigation: NavEintrag[]
|
navigation: NavEintrag[]
|
||||||
kopfzeile: React.ReactNode
|
kopfzeile: React.ReactNode
|
||||||
schnellerfassung?: React.ReactNode
|
schnellerfassung?: React.ReactNode
|
||||||
benutzerbereich: React.ReactNode
|
benutzerbereich: React.ReactNode
|
||||||
|
/** Wird unten in der Seitenleiste angezeigt - hilft beim Aktualisieren. */
|
||||||
|
version?: string
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [menueOffen, setMenueOffen] = React.useState(false)
|
const [menueOffen, setMenueOffen] = React.useState(false)
|
||||||
@@ -73,7 +76,14 @@ export function AppHuelle({
|
|||||||
<Navigation eintraege={navigation} />
|
<Navigation eintraege={navigation} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t p-3">{benutzerbereich}</div>
|
<div className="border-t p-3">
|
||||||
|
{benutzerbereich}
|
||||||
|
{version && (
|
||||||
|
<p className="px-2 pt-2 text-xs text-muted-foreground">
|
||||||
|
Version {version}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{/* --- Kopfzeile am Handy ------------------------------------------- */}
|
{/* --- Kopfzeile am Handy ------------------------------------------- */}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Versionsangaben der Anwendung.
|
||||||
|
*
|
||||||
|
* VERSION wird vom Skript `npm run version` zusammen mit der package.json
|
||||||
|
* gesetzt - es gibt also genau eine Quelle, aus der beide gespeist werden.
|
||||||
|
* Ein Test (tests/version.test.ts) prueft, dass sie nicht auseinanderlaufen.
|
||||||
|
*
|
||||||
|
* Warum nicht einfach die package.json importieren? Im Standalone-Build
|
||||||
|
* liegt eine andere package.json neben dem Server als die des Projekts.
|
||||||
|
* Die Zahl waere dann je nach Laufzeit eine andere - genau das, was man
|
||||||
|
* bei einer Versionsangabe nicht gebrauchen kann.
|
||||||
|
*
|
||||||
|
* Die Angabe ist kein Geheimnis, aber auch keine oeffentliche Auskunft:
|
||||||
|
* sie steht in den Einstellungen und im Gesundheitsendpunkt, nicht auf
|
||||||
|
* der Anmeldeseite. Wer nicht angemeldet ist, muss nicht wissen, welche
|
||||||
|
* Fassung hier laeuft.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Muss zur "version" in der package.json passen. */
|
||||||
|
export const VERSION = '1.0.0'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kurzer Git-Stand, beim Bauen hineingereicht:
|
||||||
|
*
|
||||||
|
* docker build --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) .
|
||||||
|
*
|
||||||
|
* Fehlt die Angabe (etwa im Entwicklungsmodus), bleibt sie leer. Dann
|
||||||
|
* zeigt die Oberflaeche nur die Version.
|
||||||
|
*/
|
||||||
|
export const COMMIT = process.env.NEXT_PUBLIC_GIT_COMMIT ?? ''
|
||||||
|
|
||||||
|
/** Zeitpunkt des Bauens, ebenfalls beim Bauen hineingereicht. */
|
||||||
|
export const GEBAUT_AM = process.env.NEXT_PUBLIC_BUILD_ZEIT ?? ''
|
||||||
|
|
||||||
|
/** "1.0.0" oder "1.0.0 · 4b953e7" - fuer die Anzeige. */
|
||||||
|
export function versionstext(): string {
|
||||||
|
return COMMIT ? `${VERSION} · ${COMMIT}` : VERSION
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import { VERSION, versionstext } from '@/lib/version'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Version steht an zwei Stellen: in der package.json und in
|
||||||
|
* src/lib/version.ts. Das ist Absicht - im Standalone-Build liegt eine
|
||||||
|
* andere package.json neben dem Server als die des Projekts, ein Import
|
||||||
|
* waere also je nach Laufzeit eine andere Zahl.
|
||||||
|
*
|
||||||
|
* Damit die beiden nicht auseinanderlaufen, gibt es dieses Netz. Wer die
|
||||||
|
* Version von Hand aendert statt mit "npm run version", merkt es hier.
|
||||||
|
*/
|
||||||
|
describe('Version', () => {
|
||||||
|
const paket = JSON.parse(readFileSync('package.json', 'utf8')) as { version: string }
|
||||||
|
|
||||||
|
it('stimmt mit der package.json ueberein', () => {
|
||||||
|
expect(
|
||||||
|
VERSION,
|
||||||
|
'src/lib/version.ts und package.json weichen ab - benutze "npm run version"',
|
||||||
|
).toBe(paket.version)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ist eine gueltige Versionsnummer nach SemVer', () => {
|
||||||
|
expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('haengt den Git-Stand an, wenn er bekannt ist', () => {
|
||||||
|
expect(versionstext().startsWith(VERSION)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user