Die Anschreiben gab es bisher nur als Text zum Kopieren. Danach war
Handarbeit: in Word nachbauen, ausdrucken, abschicken - und noch einmal
von Hand als Dokument in die Akte legen. Der letzte Schritt wurde am
haeufigsten vergessen, und dann fehlt der Nachweis im Bericht.
Neu unter /schreiben/neu: bearbeiten, als Word-Datei herunterladen, in
die Akte legen, Wiedervorlage setzen - in einem Zug. Eine Seite fuer alle
Arten und alle drei Empfaengerwege, statt die Bearbeitung dreimal an
Vertrag, Vorgang und Freitext nachzubauen.
Der dritte Weg ist der eigentliche Zugewinn: Schreiben an eine Stelle,
zu der es noch keinen Datensatz gibt. Am Anfang einer Betreuung ist das
der Normalfall - man fragt ja an, um zu erfahren, ob dort ueberhaupt
etwas besteht. Aus der freien Anschrift laesst sich mit einem Klick ein
Kontakt anlegen.
Zwei Entscheidungen, die erklaert gehoeren:
Die Akte bekommt ein PDF, heruntergeladen wird .docx. Der
Objektspeicher nimmt nur PDF und Bilder an, geprueft an den Magic Bytes.
Eine .docx ist ein ZIP-Archiv; sie durchzulassen hiesse jedes ZIP
durchzulassen, und diese Pruefung ist eine bewusste Zusage der
Anwendung. Sachlich ist PDF fuer den Nachweis ohnehin richtiger: Es
aendert sich nicht mehr. Die Oberflaeche sagt dazu, dass eine spaeter in
Word geaenderte Fassung von der Akte abweicht.
Der Berichtssatz in berichte/docx.ts bleibt unangetastet. Er setzt einen
Bericht mit Kopfzeilen und Unterschriftenblock; ein Brief braucht
Absender, Anschriftenfeld, Datumszeile und Grussformel. Beides in eine
Funktion zu zwingen haette beide Ausgaben schlechter gemacht. Der
Umsetzungshinweis im Issue ("muss nur angebunden werden") war insoweit
falsch - von mir selbst geschrieben.
Die vorhandenen Erzeuger (Rueckkaufswert, Kuendigung, Sachstandsanfrage)
bleiben unveraendert; ihr Wortlaut ist getestet und erprobt.
Geprueft: zehn Einheitstests, darunter der Gleichschritt der
Wiedervorlagefristen ueber zwei Module hinweg. Fuenf End-to-End-Tests
auf beiden Geraeteprofilen: Entwurf, Vertragsbezug, Word-Download,
Ablage samt Wiedervorlage, Kontakt aus freier Anschrift.
Refs #4
596 lines
23 KiB
Markdown
596 lines
23 KiB
Markdown
# Betreuungs-Cockpit
|
|
|
|
Arbeits- und Dokumentationswerkzeug für die ehrenamtliche rechtliche Betreuung
|
|
eines Angehörigen: Fristen, Vorgänge, Belege, Vermögen, Sozialhilfe, Berichte.
|
|
|
|
> **Keine Rechtsberatung.** Die Anwendung rechnet Fristen aus, sortiert
|
|
> Unterlagen und erzeugt Berichte. Sie kennt den Einzelfall nicht. Jeder
|
|
> Hinweis mit Paragraphen-Bezug trägt den sichtbaren Vermerk, dass er die
|
|
> Rückfrage bei Betreuungsgericht, Sozialhilfeträger oder Anwalt **nicht**
|
|
> ersetzt. Wer sich darauf verlässt, statt beim Rechtspfleger nachzufragen,
|
|
> kann Geld und Ansprüche verlieren.
|
|
|
|
---
|
|
|
|
## Inhalt
|
|
|
|
- [Was die Anwendung kann](#was-die-anwendung-kann)
|
|
- [Die drei Dokumente, die du wirklich brauchst](#die-drei-dokumente-die-du-wirklich-brauchst)
|
|
- [Schnellstart zum Ausprobieren](#schnellstart-zum-ausprobieren)
|
|
- [Produktivbetrieb mit Docker Compose](#produktivbetrieb-mit-docker-compose)
|
|
- [Reverse Proxy](#reverse-proxy)
|
|
- [Alternative: Vercel und Neon](#alternative-vercel-und-neon)
|
|
- [Sicherheit und Datenschutz](#sicherheit-und-datenschutz)
|
|
- [Was rechtlich zu tun ist](#was-rechtlich-zu-tun-ist)
|
|
- [Sicherung und Wiederherstellung](#sicherung-und-wiederherstellung)
|
|
- [Schlüsselverwaltung](#schlüsselverwaltung)
|
|
- [Entwicklung](#entwicklung)
|
|
- [Bauprotokoll](#bauprotokoll)
|
|
|
|
---
|
|
|
|
## Was die Anwendung kann
|
|
|
|
| Bereich | Kurz gesagt |
|
|
|---|---|
|
|
| **Heute** | Eine Seite, die morgens die Frage beantwortet, was ansteht. Fristen-Ampel, wer am Zug ist, offene Nachforderungen des Bezirks, ungeprüfter Posteingang, Kennzahlen. |
|
|
| **Fristen** | Die gesetzlichen Pflichtfristen entstehen von selbst aus dem Bestellungsdatum. Liste, Kalender, ICS-Feed zum Abonnieren. |
|
|
| **Vorgänge** | Fallakte je Angelegenheit, mit Vorlagen (Heimeinzug, Hausverkauf, Widerspruch …) und der festen Ablaufkette für genehmigungspflichtige Geschäfte. |
|
|
| **Dokumente** | Foto oder Scan hochladen, Texterkennung, Volltextsuche über alle Schreiben, Posteingangsbuch, Belegmappe als eine PDF. |
|
|
| **Vermögen & Kasse** | Kassenbuch mit Belegzwang, Konten, Vermögensposten mit Wert-Historie, Vermögensverzeichnis und Rechnungslegung als PDF. |
|
|
| **Sozialhilfe** | Antragsakte, Nachforderungs-Tracker, Bescheid-Prüfung mit Rechtsbehelfsfrist, Heimkosten-Gegenüberstellung, Schonvermögens-Bewertung. |
|
|
| **Immobilie** | Stammdaten, Belastungen, Wert-Historie, Status im Sozialhilfeverfahren, Pflichten-Checkliste bei Leerstand. |
|
|
| **Verträge** | Versicherungen und laufende Verträge mit Kündigungsfristen, Rückkaufswert-Anfrage, Kündigungs-Assistent. |
|
|
| **Heim & Pflege** | Heimvertrag, Leistungszuschläge, Pflegegrad-Historie, Gesundheitsverlauf mit COPD-Bezug. |
|
|
| **Kontakte** | Verzeichnis aller Stellen, Ein-Klick-Kontaktprotokoll, Auswertung für den Jahresbericht. |
|
|
| **Berichte** | Vermögensverzeichnis, Jahresbericht, Rechnungslegung, Belegmappe, Unterlagenpaket für den Bezirk, Vollexport. |
|
|
|
|
---
|
|
|
|
## Die drei Dokumente, die du wirklich brauchst
|
|
|
|
| Wofür | Wo |
|
|
|---|---|
|
|
| **Die Anwendung aufsetzen** — auf einem Server, der Docker und einen Proxy schon hat, mit Kontrolle nach jedem Schritt | [`docs/INSTALLATION.md`](docs/INSTALLATION.md) |
|
|
| **Täglich damit arbeiten** — die zwölf Handgriffe, der Jahresrhythmus, was tun wenn etwas schiefgeht | [`docs/ANLEITUNG.md`](docs/ANLEITUNG.md), in der Anwendung unter `/anleitung` |
|
|
| **Wissen, was sich ändert** — was neu ist, was du vor einem Update beachten musst | [`CHANGELOG.md`](CHANGELOG.md) |
|
|
|
|
Dazu: [`docs/ROADMAP.md`](docs/ROADMAP.md) für das, was als Nächstes
|
|
kommt, und [`docs/BAUPROTOKOLL.md`](docs/BAUPROTOKOLL.md) für die Frage,
|
|
wie das hier entstanden ist.
|
|
|
|
Der Rest dieser Datei ist die Kurzfassung für alle, die schon wissen, was
|
|
sie tun.
|
|
|
|
---
|
|
|
|
## Schnellstart zum Ausprobieren
|
|
|
|
Voraussetzungen: **Node.js ≥ 20.11**, **Docker** und **Git**.
|
|
|
|
```bash
|
|
git clone https://git.wilde-server.de/florian.wilde/betreuungs-cockpit.git
|
|
cd betreuungs-cockpit
|
|
npm install
|
|
```
|
|
|
|
**1. Datenbank und Objektspeicher starten.** Beide horchen nur auf
|
|
`127.0.0.1`, gehen also nicht ins Netz hinaus:
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml up -d
|
|
```
|
|
|
|
**2. Konfiguration anlegen.** Die Geheimnisse erzeugst du dir:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
npm run schluessel # gibt AUTH_SECRET, ENC_KEY_V1, INDEX_KEY aus
|
|
```
|
|
|
|
Trage die ausgegebenen Werte in die `.env` ein und setze:
|
|
|
|
```
|
|
DATABASE_URL="postgresql://cockpit:entwicklung@localhost:5433/betreuungs_cockpit?schema=public"
|
|
```
|
|
|
|
**3. Datenbank einrichten:**
|
|
|
|
```bash
|
|
npx prisma migrate dev --name fundament
|
|
npm run db:sql # Volltextsuche und Audit-Schutz
|
|
npm run db:seed
|
|
```
|
|
|
|
**4. Starten:**
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Dann [http://localhost:3000](http://localhost:3000) öffnen. Beim ersten Aufruf
|
|
führt dich ein Assistent in drei Schritten durch Zugang, zweiten Faktor und
|
|
die Stammdaten der betreuten Person.
|
|
|
|
### Mit Beispieldaten ansehen
|
|
|
|
Um zu sehen, wie die Anwendung mit Inhalt aussieht:
|
|
|
|
```bash
|
|
echo "SEED_DEMO=true" >> .env
|
|
npm run db:seed
|
|
```
|
|
|
|
Das legt den Fall „Erika Musterfrau" an — COPD GOLD III, Pflegegrad 3,
|
|
Heimeinzug Januar 2026, leerstehendes Haus in Bindlach, zwei Versicherungen,
|
|
laufender Antrag beim Bezirk Oberfranken mit acht Nachforderungen.
|
|
**Der Demofall wird in der Produktion nicht angelegt** und lässt sich später
|
|
über den Papierkorb vollständig entfernen.
|
|
|
|
---
|
|
|
|
## Produktivbetrieb mit Docker Compose
|
|
|
|
> **Zum ersten Mal?** Nimm die [Schritt-für-Schritt-Anleitung](docs/INSTALLATION.md).
|
|
> Sie fängt beim leeren Server an, hat nach jedem Abschnitt eine Kontrolle
|
|
> und endet mit einer Abnahmeliste. Was hier steht, ist die Kurzfassung.
|
|
|
|
Das Compose-Setup bringt drei Dienste mit: die Anwendung, PostgreSQL und
|
|
MinIO als S3-kompatiblen Objektspeicher, dazu einen Sicherungsdienst.
|
|
|
|
**Einen Reverse Proxy enthält es bewusst nicht** — er ist auf dem Zielserver
|
|
bereits vorhanden. Die Anwendung horcht nur auf `127.0.0.1:3000`.
|
|
**Die Datenbank hat keine Portfreigabe** und ist ausschließlich über das
|
|
interne Docker-Netz erreichbar.
|
|
|
|
```bash
|
|
# 1. Konfiguration
|
|
cp .env.example .env
|
|
npm run schluessel
|
|
|
|
# In der .env mindestens setzen:
|
|
# POSTGRES_PASSWORD, AUTH_SECRET, AUTH_URL (die echte öffentliche Adresse),
|
|
# ENC_KEY_V1, INDEX_KEY,
|
|
# S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, SICHERUNG_PASSWORT
|
|
|
|
# 2. Bauen und starten
|
|
docker compose up -d --build
|
|
|
|
# 3. Datenbank vorbereiten
|
|
# (die Migrationen laufen beim Start der Anwendung automatisch;
|
|
# die beiden SQL-Skripte müssen einmalig dazu)
|
|
docker compose exec app node node_modules/prisma/build/index.js db execute \
|
|
--schema prisma/schema.prisma --file prisma/sql/volltextsuche.sql
|
|
docker compose exec app node node_modules/prisma/build/index.js db execute \
|
|
--schema prisma/schema.prisma --file prisma/sql/audit-schutz.sql
|
|
|
|
# 4. Bucket im Objektspeicher anlegen
|
|
docker compose exec minio mc alias set lokal http://localhost:9000 \
|
|
"$S3_ACCESS_KEY_ID" "$S3_SECRET_ACCESS_KEY"
|
|
docker compose exec minio mc mb lokal/betreuungs-cockpit
|
|
```
|
|
|
|
Danach im Browser über die eigene Domain auf `/ersteinrichtung` gehen und den
|
|
ersten Zugang anlegen. Die Seite sperrt sich selbst, sobald ein Zugang
|
|
existiert.
|
|
|
|
### Aktualisieren
|
|
|
|
```bash
|
|
./scripts/update.sh
|
|
```
|
|
|
|
> **Kommst du von 1.1.0 oder älter?** Dort lag `update.sh` ohne
|
|
> Ausführungsrecht im Repository — `./scripts/update.sh` endet dann in
|
|
> „Permission denied". Einmalig hilft:
|
|
>
|
|
> ```bash
|
|
> bash scripts/update.sh
|
|
> ```
|
|
>
|
|
> Ab 1.1.1 ist das Recht gesetzt und der Punktschrägstrich funktioniert.
|
|
|
|
Das Skript sichert zuerst, zeigt den Changelog zwischen altem und neuem
|
|
Stand, fragt nach, schaltet um, baut neu und führt danach die beiden
|
|
SQL-Skripte aus. Schlägt die Sicherung fehl, wird nicht aktualisiert.
|
|
|
|
`--pruefen` sieht nur nach, ohne etwas zu ändern. Eigene Änderungen an
|
|
versionierten Dateien werden vor dem Umschalten beiseite gelegt
|
|
(`git stash`), die `.env` bleibt unangetastet.
|
|
|
|
Einzelheiten und der Weg von Hand: [`docs/INSTALLATION.md`](docs/INSTALLATION.md).
|
|
|
|
---
|
|
|
|
## Reverse Proxy
|
|
|
|
Die Anwendung **beendet selbst kein TLS** und bringt keinen Proxy mit. Sie
|
|
horcht standardmäßig auf `127.0.0.1:3000`.
|
|
|
|
**Läuft der Proxy auf demselben Server**, ist damit alles gesagt: Er zeigt
|
|
auf `127.0.0.1:3000`, fertig.
|
|
|
|
**Läuft er auf einem anderen Server**, setz in der `.env` die Adresse, über
|
|
die er herankommt — am besten die eines privaten Netzes oder eines
|
|
WireGuard-Tunnels:
|
|
|
|
```bash
|
|
APP_BIND=10.0.0.5
|
|
APP_PORT=3000
|
|
```
|
|
|
|
`0.0.0.0` nur, wenn eine Firewall den Port ausschließlich für die IP des
|
|
Proxy-Servers öffnet. Zwischen Proxy und Anwendung läuft unverschlüsseltes
|
|
HTTP — dieser Weg darf nie durchs offene Netz gehen.
|
|
|
|
### Was der Proxy leisten muss
|
|
|
|
| Anforderung | Was ohne sie passiert |
|
|
|---|---|
|
|
| TLS beenden | Der Browser lehnt Cookies mit `Secure`-Kennzeichen ab, die Anmeldung schlägt stumm fehl |
|
|
| `Host` durchreichen | Auth.js vergleicht ihn mit `AUTH_URL` und weist die Anmeldung ab |
|
|
| `X-Forwarded-Proto: https` | Die Anwendung baut Adressen mit `http` |
|
|
| `X-Forwarded-For` | Die Login-Sperre zählt alle Zugriffe als denselben Anschluss |
|
|
| Uploads ≥ 30 MB | Fotos mehrseitiger Bescheide werden abgeschnitten |
|
|
| Zeitgrenze ≥ 10 Minuten | Belegmappe, Rechnungslegung und Vollexport brechen ab |
|
|
| Antwort nicht puffern | Der Vollexport kommt als Strom |
|
|
|
|
**Keine eigenen Sicherheitskopfzeilen setzen.** CSP, `X-Frame-Options`,
|
|
`nosniff` und HSTS kommen aus der Anwendung. Die CSP enthält eine Nonce,
|
|
die pro Anfrage neu ist — eine feste CSP aus dem Proxy blockiert die
|
|
eigenen Skripte.
|
|
|
|
**Fertige Konfigurationen** für Caddy, nginx und Traefik, mit Zeitgrenzen
|
|
und den Gegenproben, stehen in
|
|
[`docs/INSTALLATION.md`](docs/INSTALLATION.md), Schritt 7.
|
|
|
|
---
|
|
|
|
## Alternative: Vercel und Neon
|
|
|
|
Technisch möglich, hier aber **nicht empfohlen**. Der Grund ist nicht
|
|
technischer Natur:
|
|
|
|
> Vercel Inc. und Neon Inc. sind US-Unternehmen. Auch bei Wahl einer
|
|
> EU-Region unterliegen sie potenziell dem US-CLOUD-Act. Für
|
|
> Gesundheitsdaten einer nicht selbst entscheidungsfähigen Person — also
|
|
> besondere Kategorien nach Art. 9 DSGVO — ist das eine Abwägung, die man
|
|
> bewusst treffen und dokumentieren muss. Ein eigener Server in Deutschland
|
|
> nimmt einem diese Abwägung ab.
|
|
|
|
Wenn du es dennoch willst:
|
|
|
|
1. **Neon**-Projekt in der Region `eu-central-1` (Frankfurt) anlegen,
|
|
Verbindungszeichenfolge als `DATABASE_URL` hinterlegen.
|
|
2. In **Vercel** unter *Settings → Functions* die Region `fra1` festlegen.
|
|
3. Alle Umgebungsvariablen aus `.env.example` als Vercel-Secrets eintragen.
|
|
`ENC_KEY_V1` und `INDEX_KEY` gehören zwingend dazu.
|
|
4. **Objektspeicher**: nicht Vercel Blob, sondern ein EU-Anbieter, z. B.
|
|
Hetzner Object Storage (Falkenstein oder Nürnberg). `SPEICHER_TREIBER=s3`.
|
|
5. **OCR und PDF laufen nicht auf Vercel.** Tesseract braucht ein System-
|
|
Binary, das es in der Serverless-Umgebung nicht gibt. Setze `OCR_AKTIV=false`
|
|
oder betreibe einen kleinen Worker daneben. PDF-Erzeugung mit
|
|
`@react-pdf/renderer` funktioniert, kann bei großen Belegmappen aber ins
|
|
Zeitlimit laufen.
|
|
6. **Migrationen** laufen nicht automatisch: `npx prisma migrate deploy` als
|
|
Build-Schritt oder von Hand.
|
|
7. Mit **beiden** Anbietern ist ein Auftragsverarbeitungsvertrag nach
|
|
Art. 28 DSGVO abzuschließen.
|
|
|
|
---
|
|
|
|
## Sicherheit und Datenschutz
|
|
|
|
Hier liegen Gesundheits- und Finanzdaten einer Person, die nicht selbst
|
|
entscheiden kann. Was dafür umgesetzt ist:
|
|
|
|
**Verschlüsselung**
|
|
|
|
- Transport über TLS (durch den Reverse Proxy).
|
|
- Zusätzlich **anwendungsseitige Feldverschlüsselung** mit AES-256-GCM für
|
|
Gesundheitsdaten, Diagnosen, den ermittelten Willen, IBANs,
|
|
Versicherungsnummern, Renten- und Steuernummern sowie alle Freitext-Notizen.
|
|
Der Schlüssel liegt in der Umgebung, nicht in der Datenbank — ein
|
|
Datenbank-Dump allein nützt niemandem etwas.
|
|
- Der Feldname wird mitauthentifiziert: ein Geheimtext lässt sich nicht von
|
|
„Diagnosen" in „Notizen" umhängen.
|
|
- **Blindindex** (HMAC-SHA256 mit eigenem Schlüssel) für die IBAN, damit die
|
|
Frage „kenne ich dieses Konto schon?" beantwortbar bleibt, ohne sie im
|
|
Klartext zu speichern.
|
|
|
|
**Zugang**
|
|
|
|
- Passwörter mit **Argon2id** (19 MiB, t=2, p=1 — OWASP-Empfehlung).
|
|
- **Zweiter Faktor ist Pflicht** und lässt sich nicht abschalten. TOTP nach
|
|
RFC 6238, kompatibel mit Aegis, 2FAS, Bitwarden, 1Password.
|
|
- **Passkeys** (WebAuthn/FIDO2) als zweiter Anmeldeweg — auch für die
|
|
erneute Bestätigung vor sensiblen Aktionen. Sie *ersetzen* den zweiten
|
|
Faktor, statt zu ihm hinzuzukommen: Nutzerverifikation wird verlangt
|
|
(`userVerification: 'required'`) und serverseitig nachgeprüft, ein
|
|
Schlüssel ohne sie wird abgewiesen. Damit sind es dieselben zwei
|
|
Faktoren — Besitz des Geräts und dessen Entsperrung —, nur an die Domain
|
|
gebunden und deshalb nicht abphishbar. TOTP muss trotzdem eingerichtet
|
|
sein, bevor sich ein Passkey anlegen lässt: Er hängt an einem Gerät und
|
|
darf nicht der einzige Weg hinein sein.
|
|
- **Rate-Limiting** am Login, nach E-Mail *und* nach Anschluss. Die Sperre
|
|
wird geprüft, bevor Argon2 überhaupt rechnet.
|
|
- Die Antwortzeit ist auf eine Mindestdauer gestreckt, damit sie nicht
|
|
verrät, welche E-Mail-Adressen existieren.
|
|
- **Sitzungsablauf** nach 30 Minuten Leerlauf. Für Export, endgültiges
|
|
Löschen und die Benutzerverwaltung ist eine **erneute Anmeldung** nötig.
|
|
- Passwortwechsel beendet **alle** offenen Sitzungen, auch auf fremden
|
|
Geräten.
|
|
|
|
**Rollen**
|
|
|
|
- `betreuer` — voller Zugriff auf die zugeordneten Betreuten.
|
|
- `mitleser` — nur lesend, und nur die ausdrücklich je Modul freigegebenen
|
|
Bereiche (z. B. Geschwister, die den Kontaktverlauf sehen dürfen, aber
|
|
nicht die Finanzen).
|
|
- `admin` — Benutzerverwaltung.
|
|
|
|
Geprüft wird **immer serverseitig gegen die Betreuten-ID**, nie nur im UI
|
|
ausgeblendet.
|
|
|
|
**Audit-Log**
|
|
|
|
Jede schreibende Aktion und jeder Dokumentzugriff wird protokolliert: wer,
|
|
wann, was, alter Wert → neuer Wert. Die Änderungen liegen verschlüsselt.
|
|
|
|
Das Log ist **unveränderlich**: ein Datenbank-Trigger weist UPDATE und DELETE
|
|
ab — auch bei direktem Zugriff mit `psql`. Damit ist es der Nachweis
|
|
gegenüber Betreuungsgericht und möglichen Miterben. Nachprüfen lässt sich das
|
|
so:
|
|
|
|
```bash
|
|
docker compose exec db psql -U cockpit betreuungs_cockpit \
|
|
-c "UPDATE audit_log SET entitaet='x';"
|
|
# ERROR: Das Audit-Log ist unveraenderlich. UPDATE auf audit_log ist nicht zulaessig.
|
|
```
|
|
|
|
**Content-Security-Policy**
|
|
|
|
Streng gesetzt, mit einer Nonce pro Anfrage. Keine externen Schriften, kein
|
|
CDN, keine Analytics, keine Tracker. Der QR-Code für den zweiten Faktor wird
|
|
serverseitig erzeugt und als Data-URI eingebettet, nicht von einem fremden
|
|
Dienst geholt.
|
|
|
|
**Uploads**
|
|
|
|
Der Dateityp wird an den **Magic Bytes** geprüft, nicht an der Endung und
|
|
nicht am gemeldeten MIME-Typ. Erlaubt sind PDF, JPEG, PNG, WEBP, TIFF, HEIC.
|
|
Ausgeliefert wird nur über kurzlebige signierte URLs, immer mit
|
|
`Content-Disposition: attachment` und `X-Content-Type-Options: nosniff` —
|
|
ein hochgeladenes HTML-Dokument kann also nie als Seite ausgeführt werden.
|
|
|
|
**Löschen**
|
|
|
|
Papierkorb mit 30 Tagen, danach echtes Löschen. Endgültiges Löschen erfordert
|
|
eine frische Anmeldung.
|
|
|
|
---
|
|
|
|
## Was rechtlich zu tun ist
|
|
|
|
Das erledigt keine Software für dich:
|
|
|
|
1. **Auftragsverarbeitungsvertrag nach Art. 28 DSGVO** mit dem Hoster und mit
|
|
dem Betreiber des Objektspeichers. Bei Hetzner findest du ihn im
|
|
Kundenkonto unter *Verträge → AV-Vertrag*, bei Netcup und IONOS ähnlich.
|
|
Ohne AV-Vertrag ist der Betrieb formal rechtswidrig.
|
|
2. **Verarbeitungsverzeichnis** nach Art. 30 DSGVO. Ein Textbaustein liegt in
|
|
[`docs/verarbeitungsverzeichnis.md`](docs/verarbeitungsverzeichnis.md) und
|
|
muss nur ergänzt werden.
|
|
3. **Datenschutzhinweise** für die betreute Person und für Mitleser:
|
|
[`docs/datenschutzhinweise.md`](docs/datenschutzhinweise.md).
|
|
4. **Server in Deutschland oder mindestens in der EU.** Keine US-Dienste im
|
|
Datenpfad.
|
|
5. Bei einer Datenpanne: **Meldepflicht binnen 72 Stunden** (Art. 33 DSGVO)
|
|
an die zuständige Aufsichtsbehörde — in Bayern das BayLDA.
|
|
|
|
---
|
|
|
|
## Sicherung und Wiederherstellung
|
|
|
|
### Was automatisch läuft
|
|
|
|
Der Dienst `sicherung` legt **täglich um 03:15 Uhr** einen Stand nach
|
|
`./backups`:
|
|
|
|
- `pg_dump` im eigenen Format, damit auch einzelne Tabellen zurückholbar sind
|
|
- verschlüsselt mit AES-256 und PBKDF2 aus `SICHERUNG_PASSWORT`
|
|
- SHA-256-Prüfsumme daneben
|
|
- **Selbstprüfung**: direkt nach dem Schreiben wird geprüft, ob sich die
|
|
Datei wieder entschlüsseln lässt. Schlägt das fehl, endet der Lauf mit
|
|
Fehler statt mit einem stillen Scheinerfolg.
|
|
- Aufbewahrung 30 Tage, der jeweils erste Stand eines Monats bleibt dauerhaft
|
|
|
|
Von Hand anstoßen:
|
|
|
|
```bash
|
|
docker compose exec sicherung /bin/sh /sicherung.sh
|
|
```
|
|
|
|
### Zurückspielen
|
|
|
|
```bash
|
|
docker compose exec sicherung /bin/sh /wiederherstellung.sh \
|
|
/backups/cockpit_2026-08-22_0315.dump.enc
|
|
```
|
|
|
|
Das Skript prüft die Prüfsumme, fragt nach und spielt zurück. Danach:
|
|
|
|
```bash
|
|
docker compose exec app npm run db:sql # Trigger und Indizes erneut anlegen
|
|
```
|
|
|
|
### Wiederherstellung üben
|
|
|
|
**Ein Backup, das nie zurückgespielt wurde, ist kein Backup.** Nimm dir
|
|
einmal im Jahr zwanzig Minuten:
|
|
|
|
```bash
|
|
# 1. Testdatenbank neben der echten anlegen
|
|
docker compose exec db createdb -U cockpit probe
|
|
|
|
# 2. Sicherung dorthin zurückspielen
|
|
docker compose exec -e PGDATABASE=probe -e SICHERUNG_OHNE_RUECKFRAGE=ja \
|
|
sicherung /bin/sh /wiederherstellung.sh /backups/cockpit_<datum>.dump.enc
|
|
|
|
# 3. Stichprobe: sind die Daten da?
|
|
docker compose exec db psql -U cockpit probe \
|
|
-c "SELECT vorname, nachname FROM betreute;" \
|
|
-c "SELECT count(*) FROM audit_log;"
|
|
|
|
# 4. Aufräumen
|
|
docker compose exec db dropdb -U cockpit probe
|
|
```
|
|
|
|
Wichtig ist der letzte Schritt in der Anwendung selbst: eine betreute Person
|
|
öffnen und nachsehen, ob die **Diagnosen im Klartext** erscheinen. Erst dann
|
|
weißt du, dass nicht nur die Datenbank, sondern auch der Schlüssel stimmt.
|
|
|
|
### Was das Backup NICHT enthält
|
|
|
|
**Den Verschlüsselungsschlüssel.** `ENC_KEY_V1` liegt in der `.env`, nicht in
|
|
der Datenbank. Das ist Absicht: ein gestohlenes Backup ist damit wertlos.
|
|
Für dich bedeutet es aber: **ohne diesen Schlüssel ist auch dein eigenes
|
|
Backup halb blind.** Diagnosen, IBANs, Versicherungsnummern und Notizen
|
|
bleiben unlesbar.
|
|
|
|
Ebenfalls nicht enthalten: die **Dateien im Objektspeicher**. Die sichere
|
|
gesondert, zum Beispiel:
|
|
|
|
```bash
|
|
docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente
|
|
```
|
|
|
|
Oder du benutzt regelmäßig den **Vollexport** in der Anwendung — der packt
|
|
Dateien, Daten und Berichte in ein ZIP.
|
|
|
|
---
|
|
|
|
## Schlüsselverwaltung
|
|
|
|
`ENC_KEY_V1` ist der wichtigste Wert der ganzen Installation.
|
|
|
|
**Sichere ihn getrennt vom Backup.** Passwortmanager *und* ausgedruckt bei
|
|
den Betreuungsunterlagen. Wenn er verloren geht, sind die verschlüsselten
|
|
Felder unwiederbringlich weg — es gibt keine Hintertür.
|
|
|
|
### Schlüssel wechseln
|
|
|
|
Die Anwendung kann mehrere Schlüsselversionen gleichzeitig lesen:
|
|
|
|
```bash
|
|
# 1. Neuen Schlüssel erzeugen und als ENC_KEY_V2 in die .env eintragen
|
|
npm run schluessel
|
|
|
|
# 2. Umschalten - ab jetzt wird mit V2 verschlüsselt
|
|
ENC_KEY_AKTIV=2
|
|
|
|
# 3. Neu starten
|
|
docker compose up -d
|
|
```
|
|
|
|
**`ENC_KEY_V1` bleibt in der `.env` stehen**, solange noch Daten damit
|
|
verschlüsselt sind — sonst sind ältere Datensätze nicht mehr lesbar. Erst
|
|
wenn alle Felder auf die neue Version umgeschrieben wurden, darf der alte
|
|
Schlüssel weg.
|
|
|
|
---
|
|
|
|
## Entwicklung
|
|
|
|
```bash
|
|
npm run dev # Entwicklungsserver
|
|
npm run typecheck # TypeScript ohne Ausgabe prüfen
|
|
npm run lint
|
|
npm test # Vitest: Fachlogik
|
|
npm run test:e2e # Playwright: kritische Abläufe
|
|
npm run db:studio # Prisma Studio
|
|
npm run protokoll # Bauprotokoll aus der Git-Historie
|
|
npm run version -- patch # Version anheben, Changelog vorbereiten
|
|
```
|
|
|
|
Konventionen — verbindlich für alle Beiträge:
|
|
[`docs/KONVENTIONEN.md`](docs/KONVENTIONEN.md).
|
|
Schnittstellen zwischen den Modulen:
|
|
[`docs/SCHNITTSTELLEN.md`](docs/SCHNITTSTELLEN.md).
|
|
|
|
### Bildschirmfotos erzeugen
|
|
|
|
```bash
|
|
node scripts/screenshots.mjs /heute /fristen /vorgaenge
|
|
```
|
|
|
|
Meldet sich mit dem Entwicklungszugang an und fotografiert die Seiten am
|
|
Schreibtisch und am Handy, hell und dunkel. Setzt voraus, dass
|
|
`SEED_DEV_TOTP_SECRET` in der `.env` steht — das gilt nur außerhalb der
|
|
Produktion.
|
|
|
|
### Betriebswissen, das man einmal gebraucht hat
|
|
|
|
- **Nicht gleichzeitig `npm run dev` und `npm run build` laufen lassen.**
|
|
Beide schreiben nach `.next`; das Ergebnis ist ein Build, dem einzelne
|
|
Chunks fehlen, und der Fehler (`Cannot find module './638.js'`) zeigt
|
|
nicht auf die Ursache. Im Zweifel `rm -rf .next` und neu bauen.
|
|
- **`AUTH_URL` muss exakt der Adresse entsprechen, unter der du die
|
|
Anwendung aufrufst** — bis auf Port und Schreibweise. Steht dort
|
|
`localhost` und du rufst `127.0.0.1` auf, wird das Sitzungs-Cookie für
|
|
die falsche Herkunft gesetzt und die Anmeldung landet stumm wieder auf
|
|
dem Formular.
|
|
- Der Standalone-Server (`node server.js`, so startet der Container) liest
|
|
**keine `.env`**. Alle Variablen kommen von außen — im Compose-Setup
|
|
über den Abschnitt `environment:`.
|
|
- Beim ersten Start eines Containers laufen die Migrationen automatisch
|
|
mit. Die beiden SQL-Skripte (Volltextsuche, Audit-Schutz) sind
|
|
**einmalig von Hand** anzustoßen, siehe oben.
|
|
|
|
### Technische Grundlage
|
|
|
|
Next.js 15 (App Router) · TypeScript · Tailwind CSS · shadcn/ui-Bausteine ·
|
|
PostgreSQL 17 mit Prisma · Auth.js · Zod · Server Actions ·
|
|
`@react-pdf/renderer` · Tesseract (deutsch) · Vitest · Playwright
|
|
|
|
Bewusste Abweichungen von shadcn/ui im Original: Bedienelemente sind 44 px
|
|
hoch statt 36 px, Auswahlfelder benutzen das native `<select>`, und
|
|
Eingabefelder haben mindestens 16 px Schrift. Alles drei, damit die Anwendung
|
|
am Handy zuverlässig zu bedienen ist — im Heim und auf dem Amt.
|
|
|
|
---
|
|
|
|
## Bauprotokoll
|
|
|
|
Jeder Commit trägt die Trailer `Etappe:` und `Phase:`. Daraus lässt sich
|
|
nachvollziehen, wie lange welcher Bauabschnitt gedauert hat:
|
|
|
|
```bash
|
|
npm run protokoll # Tabelle auf der Konsole
|
|
npm run protokoll:md # Markdown für docs/BAUPROTOKOLL.md
|
|
```
|
|
|
|
Der aktuelle Stand liegt in [`docs/BAUPROTOKOLL.md`](docs/BAUPROTOKOLL.md) —
|
|
dort stehen auch die Laufzeiten der parallel gebauten Etappen und die
|
|
Fehler, die während des Baus aufgefallen sind.
|
|
|
|
---
|
|
|
|
## Stand
|
|
|
|
| | | nachzuzählen mit |
|
|
|---|---:|---|
|
|
| Zeilen TypeScript/TSX | 76.610 | `git ls-files "*.ts" "*.tsx" \| xargs wc -l` |
|
|
| Prisma-Modelle | 34 | `grep -c '^model ' prisma/schema.prisma` |
|
|
| Seiten | 81 | `find src/app -name page.tsx \| wc -l` |
|
|
| Unit-Tests (Vitest) | 570 | `npm test` |
|
|
| End-to-End-Tests (Playwright) | 73 | `npm run test:e2e` — 40 Tests über zwei Geräteprofile, geräteabhängige übersprungen |
|
|
| TypeScript-Fehler | 0 | `npm run typecheck` |
|
|
|
|
Die Befehle stehen dabei, weil die Zahlen sonst altern, ohne dass es
|
|
jemandem auffällt — bei den Modellen und Seiten war genau das passiert.
|
|
|
|
Alle acht Etappen sind umgesetzt. Der Produktionsbuild ist gegen den
|
|
fertigen Standalone-Server geprüft: strenge CSP aktiv, keine
|
|
Konsolenfehler, keine Anfrage an einen fremden Host.
|