- shop/jobs.js: taeglicherJob (Käuferdaten bezahlter Bestellungen und Widerrufe 3 Jahre nach Ende des Bestelljahres anonymisieren, Grenze 1. Januar Berlin; unbezahlte/stornierte Bestellungen nach 30 Tagen löschen; Shops seit 90 Tagen disabled samt Artikeln, Bestellungen, Widerrufen, Bildern und shopConfig löschen, Funktionsdokument bleibt disabled mit Vermerk), seitenweise mit Deckel je Lauf, Schritte und Dokumente einzeln abgesichert; stuendlicherJob räumt nie beanspruchte Bilder auf - shop/loeschen.js: gemeinsamer Baustein zum Löschen aller Daten eines Shops (Tagesjob und Löschfristen) - shop/kundenLoeschung.js + customerAdmin.js: Kundenlöschung schaltet den Webshop ab (kunde_geloescht) und schließt die Shops - retention.js: fällige Kunden verlieren auch ihre Shopdaten (vor den Abo-Dokumenten, bei Fehler held) - server/index.js: Minuten- (75 s), Stunden- (130 s) und Tagesjob (200 s) registriert - Tests: shopJobs.test.js, retention.test.js erweitert Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.8 KiB
JavaScript
61 lines
2.8 KiB
JavaScript
import { config } from '../../config.js'
|
|
import { deleteDocument, listDocuments, Query } from '../appwriteAdmin.js'
|
|
import { loescheAlleBilder } from './bilder.js'
|
|
|
|
/**
|
|
* Alle Daten EINES Shops loeschen (Plan 2026-10-05-webshop.md, J3): Bestellungen, Artikel, Widerrufe, Artikelbilder und zuletzt das
|
|
* shopConfig-Dokument. Gemeinsamer Baustein des Tagesjobs (Shop seit 90 Tagen abgeschaltet, jobs.js) und der Loeschfristen
|
|
* (retention.js, Kunde faellig). Bewusst ohne Import von bestellungen.js/Stripe: retention.js darf nichts Schweres nachladen.
|
|
* Das Stripe-Konto gehoert dem Haendler und wird nie angefasst.
|
|
*
|
|
* Seitenweise (100 je Abfrage, immer die erste Seite neu, denn gelesene Dokumente sind danach weg). Am Deckel endet der Lauf
|
|
* (vollstaendig: false), der naechste setzt fort. Ein Fehler beim Loeschen wirft (sonst wuerde dieselbe Seite endlos neu gelesen).
|
|
*/
|
|
|
|
const SEITE = 100
|
|
const GRUPPEN = [
|
|
['shopOrders', 'bestellungen'],
|
|
['shopProducts', 'artikel'],
|
|
['shopWiderrufe', 'widerrufe'],
|
|
]
|
|
|
|
/**
|
|
* @param {{ shopId: string, siteKey?: string, deckel?: number, nochGueltig?: () => Promise<boolean> }} opt
|
|
* deckel: hoechstens so viele Dokumente (Bestellungen + Artikel + Widerrufe); nochGueltig wird vor jeder Seite gefragt (false = abbrechen)
|
|
* @returns {Promise<{ bestellungen: number, artikel: number, widerrufe: number, geloescht: number, konfig: boolean, vollstaendig: boolean, abgebrochen: boolean }>}
|
|
* vollstaendig = alle Dokumente, Bilder und das shopConfig-Dokument sind weg
|
|
*/
|
|
export async function loescheShopDaten({ shopId, siteKey, deckel = Number.POSITIVE_INFINITY, nochGueltig = async () => true }) {
|
|
if (typeof shopId !== 'string' || shopId === '') throw new TypeError('loescheShopDaten: shopId fehlt')
|
|
const erg = { bestellungen: 0, artikel: 0, widerrufe: 0, geloescht: 0, konfig: false, vollstaendig: false, abgebrochen: false }
|
|
for (const [name, feld] of GRUPPEN) {
|
|
const sammlung = config.collections[name]
|
|
for (;;) {
|
|
if (!(await nochGueltig())) return { ...erg, abgebrochen: true }
|
|
const rest = deckel - erg.geloescht
|
|
if (rest <= 0) return erg
|
|
const seite = await listDocuments(sammlung, [Query.equal('shopId', shopId), Query.limit(Math.min(SEITE, rest))])
|
|
if (seite.length === 0) break
|
|
for (const doc of seite) {
|
|
try {
|
|
await deleteDocument(sammlung, doc.$id)
|
|
} catch (err) {
|
|
if (err?.status !== 404) throw err
|
|
}
|
|
erg[feld] += 1
|
|
erg.geloescht += 1
|
|
}
|
|
}
|
|
}
|
|
if (!(await nochGueltig())) return { ...erg, abgebrochen: true }
|
|
if (siteKey) await loescheAlleBilder(siteKey)
|
|
try {
|
|
await deleteDocument(config.collections.shopConfig, shopId)
|
|
erg.konfig = true
|
|
} catch (err) {
|
|
if (err?.status !== 404) throw err
|
|
}
|
|
erg.vollstaendig = true
|
|
return erg
|
|
}
|