/** * Berichtet, welche Erklärungen die Pflichtangaben aus Art. 13/14 DSGVO noch * nicht erfüllen und deshalb nicht veröffentlicht werden können. * * node scripts/check-completeness.js Zusammenfassung * node scripts/check-completeness.js --list jede Erklärung einzeln * * Spiegelt die Regeln aus lib/policy-completeness.ts. */ const { PrismaClient } = require("@prisma/client") const { readDefaults } = require("./lib/policy-defaults") const prisma = new PrismaClient() /** Angaben, die die Erklärung selbst mitbringen muss. */ const EIGENE = [ ["title", "Titel", ""], ["processingDescription", "Bezeichnung der Verarbeitungstätigkeit", ""], ["processingPurposes", "Zweck der Datenerhebung", "Art. 13 Abs. 1 lit. c"], ["legalBasis", "Rechtsgrundlage", "Art. 13 Abs. 1 lit. c"], ["recipients", "Geplante Empfänger", "Art. 13 Abs. 1 lit. e"], ["storageDuration", "Speicherdauer", "Art. 13 Abs. 2 lit. a"], ] /** Bausteine, die aus der Erklärung oder dem Standardtext kommen müssen. */ const BAUSTEINE = [ ["responsible", "Verantwortlicher", "Art. 13 Abs. 1 lit. a"], ["contactDPO", "Datenschutzbeauftragte/r", "Art. 13 Abs. 1 lit. b"], ["dataSubjectsRights", "Betroffenen-Rechte", "Art. 13 Abs. 2 lit. b"], ["complaintRight", "Beschwerderecht", "Art. 13 Abs. 2 lit. d"], ["withdrawalRight", "Widerrufsrecht", "Art. 13 Abs. 2 lit. c"], ["objectionRight", "Widerspruchsrecht", "Art. 21 Abs. 4"], ["thirdCountryTransfer", "Übermittlung in Drittländer", "Art. 13 Abs. 1 lit. f"], ["automatedDecision", "Automatisierte Entscheidungsfindung", "Art. 13 Abs. 2 lit. f"], ] const hat = (v) => typeof v === "string" && v.trim().length > 0 async function main() { const list = process.argv.includes("--list") const defaults = await readDefaults(prisma) const policies = await prisma.privacyPolicy.findMany({ orderBy: { title: "asc" } }) const zaehler = new Map() const unvollstaendig = [] for (const policy of policies) { const fehlt = [] for (const [field, label, artikel] of EIGENE) { if (!hat(policy[field])) fehlt.push({ label, artikel, quelle: "Erklärung" }) } for (const [field, label, artikel] of BAUSTEINE) { if (!hat(policy[field]) && !hat(defaults[field])) { fehlt.push({ label, artikel, quelle: "Standardtext" }) } } // Art. 14 greift nur bei Erhebung aus anderer Quelle if (hat(policy.dataSource) && !hat(policy.dataCategories)) { fehlt.push({ label: "Kategorien der verarbeiteten Daten", artikel: "Art. 14 Abs. 1 lit. d", quelle: "Erklärung", }) } fehlt.forEach((f) => { const key = `${f.label}|${f.artikel}|${f.quelle}` zaehler.set(key, (zaehler.get(key) || 0) + 1) }) if (fehlt.length) unvollstaendig.push({ policy, fehlt }) } console.log(`Erklärungen gesamt: ${policies.length}`) console.log(`Veröffentlichungsreif: ${policies.length - unvollstaendig.length}`) console.log(`Unvollständig: ${unvollstaendig.length}`) console.log(`Derzeit veröffentlicht: ${policies.filter((p) => p.isPublic).length}`) console.log("") console.log("=== Fehlende Angaben nach Häufigkeit ===") const sortiert = [...zaehler.entries()].sort((a, b) => b[1] - a[1]) for (const [key, anzahl] of sortiert) { const [label, artikel, quelle] = key.split("|") const ort = quelle === "Standardtext" ? " [zentraler Standardtext]" : "" console.log(` ${String(anzahl).padStart(4)}× ${label}${artikel ? ` (${artikel})` : ""}${ort}`) } if (list) { console.log("") console.log("=== Erklärungen mit fehlenden Angaben ===") for (const { policy, fehlt } of unvollstaendig) { const eigene = fehlt.filter((f) => f.quelle === "Erklärung") if (!eigene.length) continue console.log(` ${policy.title}`) eigene.forEach((f) => console.log(` fehlt: ${f.label}${f.artikel ? ` (${f.artikel})` : ""}`)) } } } main() .catch((e) => { console.error(e) process.exitCode = 1 }) .finally(() => prisma.$disconnect())