TP2 Abschlussprüfung: Löschung (IP/Browser auf portalFeatures, Erstattungsspur, K22-Datum)
- markOrdered speichert IP und Browser der Bestellung nicht mehr am Funktionsdokument (nur im Nachweis featureConsents, dort mit Löschfrist); Altwerte werden bei einer neuen Bestellung geleert - Aufbewahrungslauf (retention): consentIp, consentUserAgent, consentText, consentAt werden an allen Funktionsdokumenten eines fälligen Kunden geleert, auch an abgeschalteten; Fehlschlag hält den Kunden wie die übrigen abhängigen Daten - Einmal-Skript scripts/feature-consent-felder-leeren.mjs (Standard Probelauf, --apply leert nur, wo featureConsents die Angabe trägt) - 30-Tage-Reset (jobs.js): auch eine bezahlte Einrichtung mit Grund abo_ende_vor_einrichtung und noch nicht gemeldetem T19 wird nicht zurückgesetzt, solange zahlungMeldung nicht gemeldet ist (die Spur der Erstattung bleibt) - K22: enddatum ist das Abo-Ende (lage.endedAt), sonst der Tag des Abschaltens; loeschdatum bleibt 30 Tage nach dem Abschalten Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b974b3a3ec
commit
2fc48df915
13 files changed
+425
-22
No files matched your search
@@ -0,0 +1,92 @@
|
||||
#!/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 <container> 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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user