import { laufeFristenAb } from './ablauf.js' import { config } from '../../config.js' import { listDocuments, Query } from '../appwriteAdmin.js' import { sendeHinweiseVorLoeschung } from './abschaltHinweis.js' import { anonymisiere, loescheAbgeschaltete } from './aufbewahrung.js' import { gleicheAlleAb } from './icalAbgleich.js' import { unterkunftModus } from './schalter.js' /** * Jobs des Buchungskalenders (Registrierung in server/index.js: Minuten 80 s Start / 60 s, Viertelstunden 140 s / 15 min, * taeglich 210 s / 24 h). Jeder Job laeuft hoechstens einmal gleichzeitig (ein Lauf, der noch haengt, ueberspringt den naechsten * Takt), wirft nie und kehrt bei UNTERKUNFT_ENABLED=off sofort zurueck (der Tagesjob nur, solange es keine Buchungsdaten mehr gibt). */ const laufend = new Set() async function einzeln(name, arbeit) { if (laufend.has(name)) { console.warn(`[unterkunft-jobs] ${name}: der vorige Lauf ist noch nicht fertig, dieser Takt wird übersprungen`) return { uebersprungen: true } } laufend.add(name) try { return await arbeit() } catch (err) { console.error(`[unterkunft-jobs] ${name} fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`) return { fehler: true } } finally { laufend.delete(name) } } /** Minutenjob: Fristen ablaufen lassen (Anfragen, Reservierungen). Bei UNTERKUNFT_ENABLED=off aus. */ export async function minutenJob(now = new Date()) { if (unterkunftModus() === 'off') return { aus: true } return einzeln('minutenJob', () => laufeFristenAb(now)) } /** Viertelstundenjob: iCal-Importe abgleichen. Bei UNTERKUNFT_ENABLED=off aus. */ export async function viertelstundenJob(now = new Date()) { if (unterkunftModus() === 'off') return { aus: true } return einzeln('viertelstundenJob', () => gleicheAlleAb(now)) } /** Gibt es irgendwelche Buchungsdaten? Die Fristen gelten auch bei UNTERKUNFT_ENABLED=off (fehlende Collections = nein). */ async function gibtDaten() { for (const name of ['ukEinstellungen', 'ukAnfragen']) { try { if ((await listDocuments(config.collections[name], [Query.select(['$id']), Query.limit(1)])).length > 0) return true } catch (err) { if (err?.type !== 'collection_not_found' && err?.code !== 'collection_not_found') throw err } } return false } /** * Tagesjob: Aufbewahrung (ipHash, Verfall von `neu`, Anonymisierung, Rechnungen nach 8 Jahren) und Loeschung abgeschalteter Funktionen * (90 Tage; davor einmal die Hinweis-Mail K35). Gemeinsamer Deckel config.unterkunft.aufbewahrungDeckel (500) je Lauf (die Loeschung bekommt, was die Aufbewahrung uebrig laesst). Bei UNTERKUNFT_ENABLED=off nur, wenn es noch * Buchungsdaten gibt (die Fristen gelten auch dann). Ein Teil, der scheitert, stoppt den anderen nicht. */ export async function taeglicherJob(now = new Date()) { return einzeln('taeglicherJob', async () => { if (unterkunftModus() === 'off' && !(await gibtDaten())) return { aus: true } const deckel = config.unterkunft.aufbewahrungDeckel ?? 500 let anon = { anfragen: 0, gaeste: 0, fehler: 1 } let weg = { funktionen: 0, dokumente: 0, fehler: 1, gedeckelt: false } let rest = deckel try { anon = await anonymisiere(now, { deckel }) rest = Math.max(0, deckel - (anon.verbraucht ?? 0)) } catch (err) { console.error(`[unterkunft-jobs] taeglicherJob: Aufbewahrung fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`) } // Vor der Loeschung: einmaliger Hinweis K35 (Rechnungen sichern); ein Fehler stoppt die Loeschung nicht (Marker fehlt, naechster Lauf wiederholt) try { await sendeHinweiseVorLoeschung(now) } catch (err) { console.error(`[unterkunft-jobs] taeglicherJob: Hinweis vor der Löschung fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`) } try { weg = await loescheAbgeschaltete(now, { deckel: rest }) } catch (err) { console.error(`[unterkunft-jobs] taeglicherJob: Löschung abgeschalteter Funktionen fehlgeschlagen: ${err?.name} ${err?.status ?? ''}`) } return { aufbewahrung: anon, abgeschaltet: weg } }) }