# 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/` – 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`) | | `npm run check-completeness` | Berichtet fehlende Pflichtangaben (`--list`) | | `npm run compare-csv-pdf` | Gesamtliste gegen die alten Merkblatt-PDFs abgleichen (`--detail`) | | `npm run import-policy -- data/policies/.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 | #### 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. 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 ```