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 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
|
||||
|
||||
# --- Stufe 3: Laufzeit -------------------------------------------------------
|
||||
|
||||
@@ -70,6 +70,11 @@ services:
|
||||
build:
|
||||
context: .
|
||||
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
|
||||
depends_on:
|
||||
db:
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "betreuungs-cockpit",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Arbeits- und Dokumentationswerkzeug fuer die ehrenamtliche rechtliche Betreuung",
|
||||
"engines": {
|
||||
@@ -28,7 +28,8 @@
|
||||
"protokoll": "node scripts/bauprotokoll.mjs",
|
||||
"test:e2e": "playwright test",
|
||||
"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": {
|
||||
"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 { ROLLEN_BESCHRIFTUNG } from '@/lib/auth/rollen'
|
||||
import { db, nichtGeloescht } from '@/lib/db'
|
||||
import { versionstext } from '@/lib/version'
|
||||
|
||||
/**
|
||||
* Rahmen aller Fachseiten.
|
||||
@@ -64,6 +65,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod
|
||||
return (
|
||||
<AppHuelle
|
||||
navigation={navigation}
|
||||
version={versionstext()}
|
||||
kopfzeile={
|
||||
<BetreuteAuswahl
|
||||
aktiveId={aktive.id}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { db } from '@/lib/db'
|
||||
import { COMMIT, VERSION } from '@/lib/version'
|
||||
|
||||
/**
|
||||
* Betriebsbereitschaft fuer den Healthcheck in docker-compose.
|
||||
*
|
||||
* Bewusst ohne Anmeldung, aber auch ohne jede Auskunft ueber Inhalte:
|
||||
* geprueft wird nur, ob die Datenbank antwortet. Version und Datenbestand
|
||||
* gehen niemanden etwas an, der die Adresse errät.
|
||||
* Bewusst ohne Anmeldung, aber ohne jede Auskunft ueber Inhalte: geprueft
|
||||
* wird nur, ob die Datenbank antwortet.
|
||||
*
|
||||
* 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 dynamic = 'force-dynamic'
|
||||
@@ -14,10 +19,13 @@ export const dynamic = 'force-dynamic'
|
||||
export async function GET() {
|
||||
try {
|
||||
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 {
|
||||
return NextResponse.json(
|
||||
{ status: 'datenbank nicht erreichbar' },
|
||||
{ status: 'datenbank nicht erreichbar', version: VERSION },
|
||||
{ status: 503, headers: { 'Cache-Control': 'no-store' } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,15 @@ export function AppHuelle({
|
||||
kopfzeile,
|
||||
schnellerfassung,
|
||||
benutzerbereich,
|
||||
version,
|
||||
children,
|
||||
}: {
|
||||
navigation: NavEintrag[]
|
||||
kopfzeile: React.ReactNode
|
||||
schnellerfassung?: React.ReactNode
|
||||
benutzerbereich: React.ReactNode
|
||||
/** Wird unten in der Seitenleiste angezeigt - hilft beim Aktualisieren. */
|
||||
version?: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [menueOffen, setMenueOffen] = React.useState(false)
|
||||
@@ -73,7 +76,14 @@ export function AppHuelle({
|
||||
<Navigation eintraege={navigation} />
|
||||
</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>
|
||||
|
||||
{/* --- 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