Go to file
Weapie 3633e8a4cf
All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 1m33s
Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen
Rechtsrahmen: § 13 Abs. 3 LBGG SH i. V. m. §§ 3 und 4 BITV 2.0. Eine
Erklaerung "nicht barrierefrei" ersetzt die Herstellungspflicht nicht,
sie dokumentiert den Stand nur voruebergehend.

Farben, alle Werte gegen die WCAG-Formel nachgerechnet:
- Sekundaertext 4.40:1 auf muted-Flaechen -> 4.55:1
- Rahmen von Eingabefeldern 1.24:1 -> 3:1 (WCAG 1.4.11); dekorative
  Trenner bleiben unveraendert
- Badge-Farben scheiterten auf der eigenen 10-Prozent-Flaeche
  (brand 4.20, success 4.18, destructive 3.88) -> jeweils ueber 4.5
- Warnfarbe im Admin 3.11:1 -> amber-700 mit 4.90:1

Fokus war faktisch unsichtbar: Der Ring wurde durchgaengig mit halber
Deckkraft gezeichnet (ring-ring/50), effektiv rund 1.5:1. Jetzt volle
Deckkraft in Markenfarbe, 12 Dateien betroffen.

Weiter: Sprungmarke zum Inhalt auf allen Seiten, Suchfelder beschriftet,
Druck-Schaltflaechen hatten als reine Icon-Buttons gar keinen
zugaenglichen Namen, Liste als echte Liste ausgezeichnet, doppelter Link
je Zeile entfernt, Trefferzahl als Live-Region.

Responsiv: Aussenabstaende und Kopfzeilen ab 320 Pixel, Listenzeilen
brechen um.

Neu nach § 4 BITV 2.0: Seiten fuer Leichte Sprache und Gebaerdensprache,
im Admin pflegbar. Leer gelassen und damit noch nicht online - die
Inhalte kommen vom Amt. In der Erklaerung als bekannte Luecke benannt.

Geprueft mit axe-core (WCAG 2.1 A und AA) ueber alle oeffentlichen Seiten
und den Adminbereich, hell und dunkel, dazu Tastaturbedienung, 200
Prozent Vergroesserung und 320 Pixel Breite: keine Verstoesse, kein
waagerechter Ueberlauf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 15:44:30 +02:00
.gitea/workflows Build-Spitzenbedarf halbieren und Platz im Log sichtbar machen 2026-09-08 12:09:55 +02:00
app Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen 2026-09-08 15:44:30 +02:00
components Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen 2026-09-08 15:44:30 +02:00
data Geprüfte Fachrechts-Zitate korrigieren 2026-09-08 11:46:13 +02:00
font/Inter Build-Kontext verkleinern und Dockerfile bereinigen 2026-09-08 11:58:32 +02:00
hooks Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
lib Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen 2026-09-08 15:44:30 +02:00
prisma Responsiv machen und Barrierefreiheit nach WCAG 2.1 AA herstellen 2026-09-08 15:44:30 +02:00
scripts Geprüfte Fachrechts-Zitate korrigieren 2026-09-08 11:46:13 +02:00
types Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
.dockerignore Build-Kontext verkleinern und Dockerfile bereinigen 2026-09-08 11:58:32 +02:00
.env.example Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
.gitattributes Syncova Policies: Portal für DSGVO-Datenschutzerklärungen 2026-09-07 16:23:51 +02:00
.gitignore Erklärungen aus der DSE-Gesamtliste neu aufbauen 2026-09-08 09:12:26 +02:00
components.json Syncova Policies: Portal für DSGVO-Datenschutzerklärungen 2026-09-07 16:23:51 +02:00
docker-compose.yml Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
docker-entrypoint.sh Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
Dockerfile Build-Spitzenbedarf halbieren und Platz im Log sichtbar machen 2026-09-08 12:09:55 +02:00
middleware.ts Datenschutzerklärungen veröffentlichungsreif machen 2026-09-07 20:35:05 +02:00
next.config.js Container-Betrieb: Port-Erkennung, Erst-Admin und Sitzungs-Cookies 2026-09-07 17:01:22 +02:00
package-lock.json Sortierung nach Titel und haengendes Overlay beheben 2026-09-08 13:37:58 +02:00
package.json Sortierung nach Titel und haengendes Overlay beheben 2026-09-08 13:37:58 +02:00
postcss.config.mjs Syncova Policies: Portal für DSGVO-Datenschutzerklärungen 2026-09-07 16:23:51 +02:00
README.md Geprüfte Fachrechts-Zitate korrigieren 2026-09-08 11:46:13 +02:00
tsconfig.json Syncova Policies: Portal für DSGVO-Datenschutzerklärungen 2026-09-07 16:23:51 +02:00

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)

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

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:

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:

docker exec -it syncova-policies node scripts/seed-admin.js

Passwort zurücksetzen:

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