Next.js 15 App Router mit öffentlicher Übersicht und geschütztem Admin-Bereich zur Pflege von Datenschutzerklärungen nach Art. 12ff. DSGVO. - Oberfläche auf Basis von shadcn/ui, Inter lokal eingebunden - Prisma/SQLite, Auth.js mit Credentials-Provider und Rollen - Massenimport der Merkblätter des Amtes Leezen aus PDF - Docker-Image (standalone) und Gitea-Workflow zur Veröffentlichung Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
2.9 KiB
JavaScript
102 lines
2.9 KiB
JavaScript
/**
|
|
* Importiert eine Datenschutzerklärung aus einer JSON-Datei in die Datenbank.
|
|
*
|
|
* npm run import-policy -- data/policies/bauantrag.json
|
|
* npm run import-policy -- data/policies/bauantrag.json --update
|
|
*
|
|
* Ohne --update wird ein vorhandener Datensatz mit gleichem Titel nicht
|
|
* überschrieben, sondern der Import abgebrochen. Schlüssel mit führendem
|
|
* Unterstrich (z. B. "_quelle") sind Kommentare und werden ignoriert.
|
|
*/
|
|
const fs = require("fs")
|
|
const path = require("path")
|
|
const { PrismaClient } = require("@prisma/client")
|
|
|
|
const prisma = new PrismaClient()
|
|
|
|
const requiredFields = [
|
|
"title",
|
|
"responsible",
|
|
"contactDPO",
|
|
"dataSubjectsRights",
|
|
"complaintRight",
|
|
"withdrawalRight",
|
|
"processingDescription",
|
|
"processingPurposes",
|
|
"legalBasis",
|
|
"recipients",
|
|
]
|
|
|
|
const optionalFields = ["storageDuration", "dataSource"]
|
|
|
|
async function importPolicy() {
|
|
const file = process.argv.find((arg) => arg.endsWith(".json"))
|
|
const allowUpdate = process.argv.includes("--update")
|
|
|
|
if (!file) {
|
|
console.error("Bitte eine JSON-Datei angeben, z. B.:")
|
|
console.error(" npm run import-policy -- data/policies/bauantrag.json")
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const filePath = path.resolve(process.cwd(), file)
|
|
if (!fs.existsSync(filePath)) {
|
|
console.error(`Datei nicht gefunden: ${filePath}`)
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"))
|
|
|
|
const missing = requiredFields.filter((field) => !String(raw[field] ?? "").trim())
|
|
if (missing.length > 0) {
|
|
console.error(`Pflichtfelder fehlen oder sind leer: ${missing.join(", ")}`)
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const data = {}
|
|
for (const field of requiredFields) {
|
|
data[field] = String(raw[field]).trim()
|
|
}
|
|
for (const field of optionalFields) {
|
|
const value = String(raw[field] ?? "").trim()
|
|
data[field] = value || null
|
|
}
|
|
data.isPublic = raw.isPublic !== undefined ? Boolean(raw.isPublic) : true
|
|
|
|
const existing = await prisma.privacyPolicy.findFirst({
|
|
where: { title: data.title },
|
|
})
|
|
|
|
if (existing && !allowUpdate) {
|
|
console.error(
|
|
`Es existiert bereits eine Erklärung mit dem Titel „${data.title}“ (ID ${existing.id}).`
|
|
)
|
|
console.error("Zum Überschreiben erneut mit --update aufrufen.")
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const policy = existing
|
|
? await prisma.privacyPolicy.update({ where: { id: existing.id }, data })
|
|
: await prisma.privacyPolicy.create({ data })
|
|
|
|
console.log(
|
|
`${existing ? "Aktualisiert" : "Importiert"}: „${policy.title}“ (ID ${policy.id}, ${
|
|
policy.isPublic ? "öffentlich" : "privat"
|
|
})`
|
|
)
|
|
if (raw._quelle) console.log(`Quelle: ${raw._quelle}`)
|
|
}
|
|
|
|
importPolicy()
|
|
.catch((error) => {
|
|
console.error("Fehler beim Import:", error)
|
|
process.exitCode = 1
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect()
|
|
})
|