#!/usr/bin/env node /** * Einmalig (TP2 Abschlusspruefung): IP und Browser der Bestellung von den Funktionsdokumenten (portalFeatures.consentIp, * consentUserAgent) leeren. Neue Bestellungen speichern sie dort nicht mehr; der Nachweis (featureConsents) traegt sie und hat die * Loeschfrist der Datenschutzerklaerung Abschnitt 7 (drei Jahre nach Ablauf des Jahres, in dem das letzte Abo endet). Altbestand am * Funktionsdokument hatte keine Loeschung. * * Geleert wird nur dort, wo der Nachweis die Angabe wirklich traegt: ein featureConsents-Eintrag zur Stripe-Sitzung des Dokuments, dessen * ip bzw. userAgent gesetzt ist, wo das Dokument sie hat. Ohne solchen Nachweis (Bestellung vor dem Nachweis, keine Sitzung) ist die Angabe * am Dokument der einzige Beleg; sie bleibt stehen und wird im Bericht genannt (die Loeschfrist im Aufbewahrungslauf, services/retention.js, * leert sie spaeter). consentText und consentAt bleiben: sie sind kein Personenbezug, der Aufbewahrungslauf leert sie mit. * * Standard ist der Probelauf: zaehlt und listet, schreibt nichts. * node scripts/feature-consent-felder-leeren.mjs (= --dry-run) * node scripts/feature-consent-felder-leeren.mjs --dry-run * node scripts/feature-consent-felder-leeren.mjs --apply (leert consentIp und consentUserAgent; danach Bericht) * Im Container als Datei: docker exec -w /app node scripts/feature-consent-felder-leeren.mjs * NICHT gegen Produktion ausfuehren, ohne dass der Probelauf geprueft und die Freigabe da ist. */ import 'dotenv/config' import { pathToFileURL } from 'node:url' import { config } from '../server/config.js' import { listAllDocuments, updateDocument } from '../server/services/appwriteAdmin.js' import { zustimmungFuerSitzung } from '../server/services/featureConsents.js' /** Aufrufargumente: keine oder --dry-run => Probelauf, --apply => schreiben; alles andere (auch beide zusammen) ist ein Fehler. */ export function parseArgs(argv = []) { const flags = new Set(argv) const unknown = argv.filter((a) => a !== '--apply' && a !== '--dry-run') if (unknown.length) throw new Error(`Unbekanntes Argument: ${unknown.join(' ')} (erlaubt: --dry-run, --apply)`) if (flags.has('--apply') && flags.has('--dry-run')) throw new Error('--apply und --dry-run schliessen sich aus') return { apply: flags.has('--apply') } } /** Traegt der Nachweis jede Angabe, die am Funktionsdokument steht? */ const nachweisTraegt = (doc, nachweis) => (!doc.consentIp || Boolean(nachweis.ip)) && (!doc.consentUserAgent || Boolean(nachweis.userAgent)) /** * @param {{ apply?: boolean, log?: (line: string) => void }} [options] * @returns {Promise<{ applied: boolean, total: number, withData: number, clearable: string[], kept: string[], unreadable: string[], * cleared: number, failed: { id: string, reason: string }[] }>} */ export async function run({ apply = false, log = console.log } = {}) { const docs = await listAllDocuments(config.collections.portalFeatures) const withData = docs.filter((d) => d.consentIp || d.consentUserAgent) const clearable = [] const kept = [] const unreadable = [] for (const doc of withData) { try { const nachweis = await zustimmungFuerSitzung(doc.stripeSessionId) if (nachweis && nachweisTraegt(doc, nachweis)) clearable.push(doc.$id) else kept.push(doc.$id) } catch (err) { unreadable.push(doc.$id) log(`Nachweis zu Dokument ${doc.$id} nicht lesbar: ${err.message}`) } } log(`Funktionsdokumente: ${docs.length}, davon mit IP/Browser der Bestellung: ${withData.length}`) log(` zu leeren (der Nachweis in featureConsents traegt die Angabe): ${clearable.length}`) log(` bleiben (kein tragender Nachweis, die Angabe am Dokument ist der einzige Beleg): ${kept.length}${kept.length ? ` (${kept.join(', ')})` : ''}`) if (unreadable.length) log(` nicht geprueft (Nachweis nicht lesbar, bleiben): ${unreadable.length} (${unreadable.join(', ')})`) const failed = [] let cleared = 0 if (!apply) { log('Probelauf (--dry-run): es wird nichts geschrieben. Mit --apply werden die oben genannten Dokumente geleert.') return { applied: false, total: docs.length, withData: withData.length, clearable, kept, unreadable, cleared, failed } } for (const id of clearable) { try { await updateDocument(config.collections.portalFeatures, id, { consentIp: '', consentUserAgent: '' }) cleared += 1 } catch (err) { failed.push({ id, reason: err.message }) log(`Dokument ${id} nicht geleert: ${err.message}`) } } log(`Geleert: ${cleared} von ${clearable.length}${failed.length ? `, fehlgeschlagen: ${failed.length} (Skript erneut ausfuehren)` : ''}`) return { applied: true, total: docs.length, withData: withData.length, clearable, kept, unreadable, cleared, failed } } if (import.meta.url === pathToFileURL(process.argv[1] || '').href) { try { const { apply } = parseArgs(process.argv.slice(2)) const result = await run({ apply }) if (result.failed.length || result.unreadable.length) process.exitCode = 1 } catch (err) { console.error('Fehler:', err.message) process.exit(1) } }