All checks were successful
Container-Image bauen und veröffentlichen / build-and-push (push) Successful in 1m23s
Die öffentliche API gab bei ?admin=true alle Datensätze heraus, auch die nicht veröffentlichten: von 117 Erklärungen waren 112 Entwürfe, die jeder Besucher abrufen konnte. Der Parameter setzt jetzt eine Admin-Sitzung voraus, sonst kommt die öffentliche Liste. Der Einzelabruf behandelt Entwürfe wie nicht vorhanden, damit sich über die fortlaufende ID nicht abklopfen lässt, welche Datensätze existieren. Markdown wurde mit sanitize: false gerendert und per dangerouslySetInnerHTML eingesetzt. Das Standard-Schema lässt jetzt Überschriften, Listen, Tabellen und http(s)/mailto-Links durch und entfernt Skripte, Event-Attribute, eingebettete Rahmen und javascript:-Ziele. Der Titel in der Druckfassung war an drei Stellen unmaskiert. Standardtexte - Zehn Bausteine, die für alle Erklärungen gelten, werden einmal zentral gepflegt. In der Erklärung steht nur eine Abweichung; ein leeres Feld bedeutet "folgt dem Standard". Ein Wechsel in der Amtsleitung wirkt damit sofort auf alle Erklärungen, die nicht bewusst abweichen. - Die Migration übernimmt den häufigsten Bestandswert als Standard und setzt übereinstimmende Felder auf NULL. Von 117 Erklärungen folgen danach 116 dem Standard, eine weicht ab (Online-Verfahren mit ZIT-SH als zusätzlichem Verantwortlichen). Pflichtangaben nach Art. 13/14 DSGVO - Neue Felder für Drittlandübermittlung (Art. 13 Abs. 1 lit. f), automatisierte Entscheidungsfindung (Art. 13 Abs. 2 lit. f), Datenkategorien (Art. 14 Abs. 1 lit. d) und berechtigte Interessen (Art. 13 Abs. 1 lit. d). Auf die ersten beiden muss auch dann hingewiesen werden, wenn es sie nicht gibt, deshalb sind sie Standardtexte mit ausdrücklichem "findet nicht statt". - Der Hinweis auf das Widerspruchsrecht steht nach Art. 21 Abs. 4 in einem eigenen, abgesetzten Kasten vor allen Abschnitten, nicht als Zeile im Fließtext der Betroffenenrechte. - Zweck, Rechtsgrundlage und Speicherdauer sind Pflicht. Geprüft wird beim Veröffentlichen, nicht beim Speichern: Ein Entwurf darf unvollständig sein, eine öffentlich sichtbare Erklärung nicht. So bleiben Bestandsdaten bearbeitbar, ohne dass die Anwendung Angaben erfindet, die nur die Fachabteilung kennt. Die Admin-Übersicht markiert unvollständige Erklärungen und nennt die fehlenden Angaben. Öffentliche Seiten - Jede Erklärung hat eine dauerhafte Adresse /erklaerung/<bezeichner>, serverseitig gerendert, zum Verlinken aus Antragsformularen. Der Bezeichner wird aus dem Titel abgeleitet und bleibt beim Bearbeiten erhalten, solange der Titel gleich bleibt. Entwürfe liefern 404. - Impressum, Datenschutzerklärung des Portals und Erklärung zur Barrierefreiheit werden im Admin als Markdown gepflegt und in der Fußzeile verlinkt. Die Fußzeile rendert serverseitig, weil das Impressum ohne JavaScript erreichbar sein muss. Ein leerer Text nimmt die Seite vom Netz, statt eine leere Seite auszuliefern. - Die Übersicht nutzt echte Links statt Klick-Handler und wird serverseitig vorbefüllt. APP_COMPANY_NAME ersetzt den Namen der Anwendung in Kopf- und Fußzeile, Seitentiteln, Anmeldeseite, Druckfassung und Startmeldung des Containers. Der Wert wird zur Laufzeit gelesen, ein Neubau des Images ist zum Umbenennen nicht nötig. Getestet gegen die 117 Bestandsdatensätze: Migrationen angewendet, der angezeigte Text blieb bei allen 117 x 7 Baustein-Feldern identisch. Alle 117 Bezeichner sind eindeutig und stimmen mit lib/slug.ts überein (SQLites LOWER() kennt nur ASCII, Großumlaute werden deshalb vorher ersetzt). ?admin=true liefert ohne Anmeldung 5 statt 117 Datensätze, Entwürfe 404. Über die API mit Anmeldung geprüft: unvollständig als Entwurf wird angelegt, dieselben Angaben mit isPublic abgelehnt, ebenso fehlende Datenkategorien bei angegebener Herkunft. Ohne APP_COMPANY_NAME erscheint "Syncova Policies", mit gesetztem Wert kein einziges Vorkommen mehr. Die Änderungen liegen in einem Commit, weil Schema, API-Routen und Formulare von allen Teilen berührt werden und eine thematische Aufteilung nicht lauffähige Zwischenstände ergäbe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
132 lines
6.1 KiB
Plaintext
132 lines
6.1 KiB
Plaintext
// This is your Prisma schema file,
|
||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
}
|
||
|
||
datasource db {
|
||
provider = "sqlite"
|
||
// Lokal: file:./dev.db (relativ zu diesem Verzeichnis)
|
||
// Container: file:/app/data/syncova.db
|
||
url = env("DATABASE_URL")
|
||
}
|
||
|
||
/// Standardtexte, die für alle Erklärungen gelten.
|
||
///
|
||
/// Genau ein Datensatz (id = 1). Die gleichnamigen Felder in `PrivacyPolicy`
|
||
/// sind Ausnahmen: Steht dort NULL, gilt der Standard von hier; steht dort ein
|
||
/// Wert, weicht diese eine Erklärung bewusst ab.
|
||
model PolicyDefaults {
|
||
id Int @id @default(1) // Singleton – immer 1
|
||
responsible String @default("") // Verantwortlicher im Sinne der DSGVO
|
||
contactDPO String @default("") // Kontaktdaten der/des Datenschutzbeauftragten
|
||
dataSubjectsRights String @default("") // Betroffenen-Rechte
|
||
complaintRight String @default("") // Beschwerderecht bei der Aufsichtsbehörde
|
||
withdrawalRight String @default("") // Widerrufsrecht bei Einwilligung
|
||
objectionRight String @default("") // Widerspruchsrecht (Art. 21 DSGVO)
|
||
thirdCountryTransfer String @default("") // Übermittlung in Drittländer (Art. 13 Abs. 1 lit. f)
|
||
automatedDecision String @default("") // Automatisierte Entscheidungsfindung (Art. 13 Abs. 2 lit. f)
|
||
provisionObligation String @default("") // Pflicht zur Bereitstellung der Daten
|
||
provisionConsequences String @default("") // Folgen, wenn die Daten nicht angegeben werden
|
||
updatedAt DateTime @updatedAt // Automatisch bei Update gesetzt
|
||
}
|
||
|
||
/// Inhalte der rechtlichen Seiten des Portals selbst.
|
||
///
|
||
/// Genau ein Datensatz (id = 1), gepflegt im Admin. Ein leerer Text bedeutet:
|
||
/// Die Seite wird nicht ausgeliefert und nicht in der Fußzeile verlinkt.
|
||
model SitePages {
|
||
id Int @id @default(1) // Singleton – immer 1
|
||
imprint String @default("") // Impressum (§ 5 DDG)
|
||
privacy String @default("") // Datenschutzerklärung für dieses Portal
|
||
accessibility String @default("") // Erklärung zur Barrierefreiheit (BITV 2.0)
|
||
updatedAt DateTime @updatedAt // Automatisch bei Update gesetzt
|
||
}
|
||
|
||
// Datenschutzerklärung model for structured privacy policies
|
||
model PrivacyPolicy {
|
||
id Int @id @default(autoincrement()) // Auto-Increment Primary Key
|
||
slug String @unique // Bezeichner in der öffentlichen URL /erklaerung/<slug>
|
||
title String // Pflichtfeld - Title of privacy policy
|
||
// NULL = Standard aus PolicyDefaults gilt, Wert = bewusste Abweichung
|
||
responsible String? // Verantwortlicher im Sinne der DSGVO
|
||
contactDPO String? // Kontaktdaten des Datenschutzbeauftragten
|
||
dataSubjectsRights String? // Betroffenen-Rechte
|
||
complaintRight String? // Beschwerderecht bei der Aufsichtsbehörde
|
||
withdrawalRight String? // Widerrufsrecht bei Einwilligung
|
||
objectionRight String? // Widerspruchsrecht (Art. 21 DSGVO)
|
||
thirdCountryTransfer String? // Übermittlung in Drittländer (Art. 13 Abs. 1 lit. f)
|
||
automatedDecision String? // Automatisierte Entscheidungsfindung (Art. 13 Abs. 2 lit. f)
|
||
processingDescription String // Bezeichnung der Verarbeitungstätigkeit
|
||
processingPurposes String? // Zweck der Verarbeitung (Art. 13 Abs. 1 lit. c) - Pflicht zur Veröffentlichung
|
||
legalBasis String? // Rechtsgrundlage (Art. 13 Abs. 1 lit. c) - Pflicht zur Veröffentlichung
|
||
legitimateInterests String? // Berechtigte Interessen, nur bei Art. 6 Abs. 1 lit. f (Art. 13 Abs. 1 lit. d)
|
||
recipients String // Wir beabsichtigen, Ihre Daten an folgende Empfänger weiterzuleiten
|
||
storageDuration String? // Speicherdauer oder Kriterien (Art. 13 Abs. 2 lit. a) - Pflicht zur Veröffentlichung
|
||
dataSource String? // Optional - Wir haben Ihre Daten aus folgender Quelle erhalten
|
||
dataCategories String? // Kategorien der Daten (Art. 14 Abs. 1 lit. d) - Pflicht, wenn dataSource gesetzt ist
|
||
provisionObligation String? // Abweichung vom Standard - Pflicht zur Bereitstellung (Art. 13 Abs. 2 lit. e DSGVO)
|
||
provisionConsequences String? // Abweichung vom Standard - Folgen, wenn die Daten nicht angegeben werden
|
||
createdAt DateTime @default(now()) // Automatisch bei Erstellung gesetzt
|
||
updatedAt DateTime @updatedAt // Automatisch bei Update gesetzt
|
||
isPublic Boolean @default(true) // Default true, bestimmt ob die Datenschutzerklärung öffentlich sichtbar ist
|
||
}
|
||
|
||
// User model for authentication
|
||
model User {
|
||
id Int @id @default(autoincrement())
|
||
email String @unique
|
||
name String?
|
||
password String
|
||
role UserRole @default(ADMIN)
|
||
isActive Boolean @default(true)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
createdBy Int?
|
||
createdByUser User? @relation("UserCreatedBy", fields: [createdBy], references: [id])
|
||
createdUsers User[] @relation("UserCreatedBy")
|
||
|
||
// NextAuth fields
|
||
accounts Account[]
|
||
sessions Session[]
|
||
}
|
||
|
||
model Account {
|
||
id Int @id @default(autoincrement())
|
||
userId Int
|
||
type String
|
||
provider String
|
||
providerAccountId String
|
||
refresh_token String?
|
||
access_token String?
|
||
expires_at Int?
|
||
token_type String?
|
||
scope String?
|
||
id_token String?
|
||
session_state String?
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([provider, providerAccountId])
|
||
}
|
||
|
||
model Session {
|
||
id Int @id @default(autoincrement())
|
||
sessionToken String @unique
|
||
userId Int
|
||
expires DateTime
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
}
|
||
|
||
model VerificationToken {
|
||
identifier String
|
||
token String @unique
|
||
expires DateTime
|
||
|
||
@@unique([identifier, token])
|
||
}
|
||
|
||
enum UserRole {
|
||
ADMIN
|
||
SUPER_ADMIN
|
||
} |