Some checks failed
Container-Image bauen und veröffentlichen / build-and-push (push) Failing after 21s
Gegen den geltenden Normbestand geprüft und berichtigt: - Landesbauordnung SH, neu gefasst zum 01.09.2022 und dabei umnummeriert. Bauvoranfrage § 61 -> § 75 (Vorbescheid), Befreiungs-/Abweichungsantrag § 61 -> § 67 LBO und § 31 BauGB, gemeindliches Einvernehmen § 61 -> § 36 BauGB und § 71 LBO. Beim Bauantrag § 68 ergänzt, § 64 bleibt. - Wohnberechtigungsschein: benannte das Dokument statt der Norm, jetzt § 27 WoFG i. V. m. § 5 WoBindG. - Bildung und Teilhabe: "BKKG" berichtigt zu § 6b BKGG, § 34 SGB XII präzisiert (drei Erklärungen). - Ausschreibungsunterlagen: überholte VOL/A entfernt. - Beihilfe, Mutterschutz und Arbeitsmedizin verarbeiten Gesundheitsdaten und haben zusätzlich Art. 9 Abs. 2 lit. b DSGVO erhalten. Nicht angefasst, weil ohne belegte Fassung: nicht zitierfähige Satzungen, die Anschlussbescheinigung, die Angelerlaubnis sowie alle fehlenden Fristen, Zwecke und Datenkategorien. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
241 lines
11 KiB
Markdown
241 lines
11 KiB
Markdown
# Syncova Policies
|
||
|
||
Verwaltung und Veröffentlichung von Datenschutzerklärungen nach DSGVO (Art. 12ff.).
|
||
Öffentliche Übersicht für Betroffene, geschützter Admin-Bereich zur Pflege.
|
||
|
||
Next.js 15 (App Router) · React 19 · Tailwind CSS 4 · shadcn/ui · Prisma + SQLite · Auth.js
|
||
|
||
---
|
||
|
||
## Schnellstart (lokal)
|
||
|
||
```bash
|
||
npm install
|
||
cp .env.example .env # NEXTAUTH_SECRET eintragen (siehe unten)
|
||
npx prisma migrate deploy
|
||
npm run seed-admin # legt den ersten Admin an und zeigt das Passwort einmalig
|
||
npm run dev
|
||
```
|
||
|
||
Die Anwendung läuft anschließend auf http://localhost:3000, der Admin-Bereich
|
||
unter `/admin`.
|
||
|
||
### Umgebungsvariablen
|
||
|
||
| Variable | Pflicht | Bedeutung |
|
||
|---|---|---|
|
||
| `APP_COMPANY_NAME` | nein | Name, unter dem die Anwendung auftritt – Kopfzeile, Fußzeile, Seitentitel und Druckfassung. Leer lassen für „Syncova Policies“. Wird zur Laufzeit gelesen, ein Neubau des Images ist nicht nötig. |
|
||
| `NEXTAUTH_SECRET` | ja | Signiert die Session-Token. Erzeugen mit `node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"` |
|
||
| `NEXTAUTH_URL` | nein | Nur nötig, wenn ein Reverse-Proxy keine `X-Forwarded-*`-Header setzt. Sonst erkennt die Anwendung Host und Port selbst – ein fester Wert erzwingt Weiterleitungen auf genau diesen Port. |
|
||
| `DATABASE_URL` | ja | SQLite-Pfad. Lokal `file:./dev.db`, im Container `file:/app/data/syncova.db` |
|
||
| `ADMIN_EMAIL` | nein | Vorgabe für `seed-admin` / `reset-admin` |
|
||
| `ADMIN_NAME` | nein | Anzeigename des Admin-Kontos |
|
||
| `ADMIN_PASSWORD` | nein | Leer lassen für ein generiertes Zufallspasswort |
|
||
|
||
> `.env` ist absichtlich nicht versioniert – sie enthält das Session-Secret.
|
||
> Ebenso wenig die SQLite-Datei, die Benutzerkonten und Passwort-Hashes enthält.
|
||
|
||
---
|
||
|
||
## Betrieb per Docker
|
||
|
||
```bash
|
||
docker run -d --name syncova-policies \
|
||
-p 3000:3000 \
|
||
-e NEXTAUTH_SECRET="$(node -e "console.log(require('crypto').randomBytes(32).toString('base64'))")" \
|
||
-e NEXTAUTH_URL="https://datenschutz.example.de" \
|
||
-v syncova-data:/app/data \
|
||
git.jfritzsche.de/jf/syncova-policies:latest
|
||
```
|
||
|
||
Oder mit Compose:
|
||
|
||
```bash
|
||
export NEXTAUTH_SECRET="…"
|
||
export NEXTAUTH_URL="https://datenschutz.example.de"
|
||
docker compose up -d
|
||
```
|
||
|
||
Beim Start wendet der Container ausstehende Migrationen selbst an
|
||
(`prisma migrate deploy`). Die Datenbank liegt im Volume unter `/app/data` –
|
||
**ohne dieses Volume gehen alle Daten beim Neustart verloren.**
|
||
|
||
Ersten Admin-Zugang anlegen:
|
||
|
||
```bash
|
||
docker exec -it syncova-policies node scripts/seed-admin.js
|
||
```
|
||
|
||
Passwort zurücksetzen:
|
||
|
||
```bash
|
||
docker exec -it syncova-policies node scripts/reset-admin.js --email admin@example.de
|
||
```
|
||
|
||
---
|
||
|
||
## Datenpflege
|
||
|
||
### Standardtexte
|
||
|
||
Sieben Bausteine sind in aller Regel für jede Erklärung gleich und werden
|
||
deshalb einmal zentral gepflegt – im Admin-Bereich unter **Standardtexte**:
|
||
|
||
| Baustein | Artikel | Pflicht |
|
||
|---|---|---|
|
||
| Verantwortlicher im Sinne der DSGVO | Art. 13 Abs. 1 lit. a | ja |
|
||
| Kontaktdaten der/des Datenschutzbeauftragten | Art. 13 Abs. 1 lit. b | ja |
|
||
| Betroffenen-Rechte | Art. 13 Abs. 2 lit. b | ja |
|
||
| Beschwerderecht bei der Aufsichtsbehörde | Art. 13 Abs. 2 lit. d | ja |
|
||
| Widerrufsrecht bei Einwilligung | Art. 13 Abs. 2 lit. c | ja |
|
||
| Widerspruchsrecht | Art. 21 Abs. 4 | ja |
|
||
| Übermittlung in Drittländer | Art. 13 Abs. 1 lit. f | ja |
|
||
| Automatisierte Entscheidungsfindung | Art. 13 Abs. 2 lit. f | ja |
|
||
| Ihre Pflicht zur Bereitstellung der Daten | Art. 13 Abs. 2 lit. e | nein |
|
||
| Folgen, wenn Sie die Daten nicht angeben | Art. 13 Abs. 2 lit. e | nein |
|
||
|
||
Auf Drittlandübermittlung und automatisierte Entscheidungen muss auch dann
|
||
hingewiesen werden, wenn es beides nicht gibt – ein ausdrückliches „findet nicht
|
||
statt“ ist die richtige Antwort, kein leeres Feld. Der Hinweis auf das
|
||
Widerspruchsrecht steht nach Art. 21 Abs. 4 in einem eigenen, abgesetzten
|
||
Kasten, getrennt von den übrigen Informationen.
|
||
|
||
Im Formular einer einzelnen Erklärung erscheinen diese Felder schreibgeschützt
|
||
mit der Markierung **Standard**. Erst „Für diese Erklärung abweichen“ macht
|
||
einen Baustein für genau diesen Datensatz bearbeitbar; „Standard übernehmen“
|
||
verwirft die Abweichung wieder.
|
||
|
||
Technisch steht in der Erklärung nur die Abweichung: Ein leeres Feld bedeutet
|
||
„folgt dem Standard“. Eine Änderung an den Standardtexten – etwa ein Wechsel
|
||
in der Amtsleitung – wirkt damit sofort auf alle Erklärungen, die nicht bewusst
|
||
abweichen. Welche Felder abweichen, liefert die API im Feld `overrides`.
|
||
|
||
### Rechtliche Seiten
|
||
|
||
Impressum, Datenschutzerklärung des Portals und Erklärung zur Barrierefreiheit
|
||
werden im Admin unter **Rechtliche Seiten** als Markdown gepflegt und unter
|
||
`/impressum`, `/datenschutz` und `/barrierefreiheit` ausgeliefert. Sie erscheinen
|
||
in der Fußzeile aller öffentlichen Seiten. Ein leer gelassener Text nimmt die
|
||
betreffende Seite vom Netz – verlinkt wird nur, was auch gepflegt ist. Die
|
||
Eingabefelder enthalten Vorlagen mit den jeweils erforderlichen Angaben.
|
||
|
||
### Erklärungen
|
||
|
||
Jede veröffentlichte Erklärung hat eine eigene, dauerhafte Adresse unter
|
||
`/erklaerung/<bezeichner>` – etwa zum Verlinken aus einem Antragsformular. Der
|
||
Bezeichner wird aus dem Titel abgeleitet und bleibt beim Bearbeiten erhalten,
|
||
solange der Titel unverändert ist. Nicht veröffentlichte Erklärungen sind unter
|
||
ihrer Adresse nicht erreichbar.
|
||
|
||
#### Vollständigkeit
|
||
|
||
Pflichtangaben aus Art. 13/14 DSGVO werden **beim Veröffentlichen** geprüft,
|
||
nicht beim Speichern: Ein Entwurf darf unvollständig sein, eine öffentlich
|
||
sichtbare Erklärung nicht. Bestehende Datensätze lassen sich damit weiter
|
||
bearbeiten, ohne dass die Anwendung Angaben erfindet, die nur die Fachabteilung
|
||
kennt.
|
||
|
||
Zusätzlich zu den Bausteinen oben verlangt die Veröffentlichung:
|
||
|
||
| Angabe | Artikel |
|
||
|---|---|
|
||
| Zweck der Datenerhebung | Art. 13 Abs. 1 lit. c |
|
||
| Rechtsgrundlage | Art. 13 Abs. 1 lit. c |
|
||
| Geplante Empfänger | Art. 13 Abs. 1 lit. e |
|
||
| Speicherdauer oder Kriterien | Art. 13 Abs. 2 lit. a |
|
||
| Kategorien der verarbeiteten Daten | Art. 14 Abs. 1 lit. d – nur, wenn eine Herkunft der Daten angegeben ist, die Daten also nicht bei der betroffenen Person selbst erhoben wurden |
|
||
|
||
Die Übersicht im Admin markiert jede Erklärung mit fehlenden Pflichtangaben und
|
||
nennt sie im Tooltip; die Kopfzeile zählt sie.
|
||
|
||
Datenschutzerklärungen werden normalerweise im Admin-Bereich gepflegt. Für
|
||
Massenimporte gibt es zwei Wege:
|
||
|
||
| Befehl | Zweck |
|
||
|---|---|
|
||
| `npm run import-csv` | **Führender Weg:** DSE-Gesamtliste einlesen (`--dry-run`, `--prune`, `--no-json`) |
|
||
| `npm run check-completeness` | Berichtet fehlende Pflichtangaben (`--list`) |
|
||
| `npm run enrich-legal-basis` | Ergänzt die DSGVO-Rechtsgrundlage zur Fachnorm (`--dry-run`) |
|
||
| `npm run fix-legal-citations` | Korrigiert geprüfte Fachrechts-Zitate (`--dry-run`) |
|
||
| `npm run compare-csv-pdf` | Gesamtliste gegen die alten Merkblatt-PDFs abgleichen (`--detail`) |
|
||
| `npm run import-policy -- data/policies/<datei>.json` | Einzelne Erklärung aus JSON importieren (`--update` überschreibt) |
|
||
| `npm run analyze-pdfs` | PDFs in `data/import/` analysieren, ohne zu schreiben |
|
||
| `npm run import-pdfs` | Älterer Weg: PDFs aus `data/import/` importieren |
|
||
|
||
#### Rechtsprüfung
|
||
|
||
Die Liste nennt je Verarbeitung nur die Fachnorm. Art. 13 Abs. 1 lit. c DSGVO
|
||
verlangt darüber hinaus die datenschutzrechtliche Erlaubnisnorm.
|
||
`enrich-legal-basis` ergänzt sie nach Art der Verarbeitung – hoheitlich
|
||
Art. 6 Abs. 1 lit. e i. V. m. § 3 LDSG SH, Beschäftigtendaten lit. b i. V. m.
|
||
§ 15 LDSG SH, dazu Einwilligung und Vertrag. `fix-legal-citations` korrigiert
|
||
einzelne, gegen den geltenden Normbestand geprüfte Zitate; die Begründung steht
|
||
je Eintrag im Skript.
|
||
|
||
Beide Skripte fassen nur an, wofür eine belegte Fassung vorliegt. Fehlende
|
||
Speicherfristen, Zwecke und Datenkategorien werden **nicht** ergänzt – sie
|
||
ergeben sich nicht aus dem Gesetz, sondern aus der Arbeitsweise des
|
||
Fachbereichs.
|
||
|
||
#### DSE-Gesamtliste (CSV)
|
||
|
||
Führende Quelle ist der Excel-Export `DSE_Gesamtliste_Amt_Leezen.CSV`. Er wird
|
||
unter `data/source/` abgelegt – **nicht versioniert**, weil die Spalte
|
||
„Zuständige/r MitarbeiterIn“ Namen von Beschäftigten enthält. Diese Spalte wird
|
||
bewusst nicht in die Erklärungen übernommen.
|
||
|
||
Der Export ist Windows-1252 kodiert und enthält Felder mit eingebetteten
|
||
Zeilenumbrüchen; `scripts/lib/csv-policy.js` bringt beides mit. Zuordnung
|
||
bestehender Datensätze erfolgt über den Bezeichner, nicht über den Titel – die
|
||
aus PDF gelesenen Titel unterscheiden sich teils nur in Leerzeichen um
|
||
Schrägstriche und ergäben sonst Dubletten.
|
||
|
||
Zwei Angaben stehen nicht in der Liste und werden ergänzt:
|
||
|
||
- **Empfänger:** Der in allen Merkblättern enthaltene Satz „Bei Ein- und
|
||
Auszahlungen: Finanzbuchhaltung“ wird vorangestellt. Ohne ihn hätten 27
|
||
Erklärungen überhaupt keine Empfängerangabe.
|
||
- **Bezeichnung der Verarbeitungstätigkeit:** wird aus Titel, Fachbereich,
|
||
Fachdienst, Aufgabenbereich, ZuFiSH-Dienstleistung und Formularnummer gebildet.
|
||
|
||
Werte wie „entfällt“ in der Spalte Datenquelle gelten als leer – sonst würde die
|
||
Art.-14-Pflicht zu den Datenkategorien fälschlich ausgelöst.
|
||
|
||
Die Datenbank wird zuerst geschrieben, die JSON-Dateien danach. Sie dienen nur
|
||
der Nachvollziehbarkeit im Repository – schlägt das Schreiben fehl, etwa weil
|
||
das Verzeichnis im Container nicht beschreibbar ist, meldet der Lauf das und
|
||
der Import bleibt trotzdem vollständig. Mit `--no-json` lässt sich die Ablage
|
||
ganz abschalten; im Container ist das der Normalfall.
|
||
|
||
Der PDF-Import (`scripts/lib/pdf-policy.js`) bleibt für die alten Merkblätter
|
||
erhalten und dient als Gegenprobe.
|
||
|
||
Beide Importwege gleichen die sieben Bausteine gegen die Standardtexte ab:
|
||
Was dem Standard entspricht, wird nicht je Erklärung dupliziert, sondern als
|
||
„folgt dem Standard“ gespeichert. Nur echte Abweichungen landen im Datensatz.
|
||
|
||
---
|
||
|
||
## Veröffentlichung des Images
|
||
|
||
`.gitea/workflows/publish-image.yml` baut bei jedem Push auf `main` sowie bei
|
||
`v*`-Tags ein Image und lädt es in die Gitea-Registry.
|
||
|
||
Voraussetzung: im Repository unter **Settings → Actions → Secrets** ein Secret
|
||
`REGISTRY_TOKEN` mit einem Token hinterlegen, das `write:package` darf.
|
||
|
||
---
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
app/ Routen (öffentlich, /erklaerung, /admin, /auth, /api)
|
||
components/ UI-Komponenten; components/ui = shadcn/ui
|
||
hooks/ SWR-Datenzugriff, Theme
|
||
lib/ Prisma/Auth-Helfer, Markdown, Formatierung
|
||
prisma/ Schema und Migrationen
|
||
scripts/ Admin- und Importskripte
|
||
data/import/ Quell-PDFs für den Massenimport
|
||
data/policies/ Aufbereitete Datensätze als JSON
|
||
```
|