import { readFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { Router } from 'express' import { config } from '../config.js' import { listDocuments, Query } from '../services/appwriteAdmin.js' import { baueIcs } from '../services/unterkunft/ical.js' import { plusTage } from '../services/unterkunft/datum.js' import { heute as berlinHeute } from '../services/termine/zeit.js' import { ipSchluessel, zaehle } from '../services/termine/limits.js' import { ladeFuerProjekt, SITE_KEY_RE } from '../services/unterkunft/kontext.js' import { unterkunftFuerKunde, unterkunftModus } from '../services/unterkunft/schalter.js' import { listeAnfragenNachStatus, listeSperren } from '../services/unterkunft/speicher.js' /** * Oeffentliche Standalone-Seiten des Buchungskalenders: /unterkunft/:siteKey und /unterkunft/:siteKey/buchung/:id * (Vorbild: shopSeite.js). Geruest (Task 5): Task 7 und Strang 3 fuellen die Seiten. * Davor steht der iCal-Export /unterkunft/ical/.ics (Task 9): die Adresse ist das Geheimnis (Token, 43 Zeichen). */ const DATEI_RE = /^[A-Za-z0-9_-]{20,64}\.ics$/ const EXPORT_JE_MINUTE = 60 const nicht = (res) => res.status(404).type('text/plain').set('Cache-Control', 'no-store').send('Not found') async function exportEreignisse(unterkunft, jetzt) { const gestern = plusTage(berlinHeute(new Date(jetzt)), -1) const out = [] for (const s of await listeSperren(unterkunft.$id)) { const quelle = String(s.quelle ?? 'manuell') if (quelle.startsWith('ical') || !s.von || !s.bis) continue // nie zurueckspielen, was importiert wurde if (s.bis <= gestern) continue // nur Ereignisse ab gestern out.push({ uid: `${s.$id}@project.webklar.com`, von: s.von, bis: s.bis }) } for (const a of await listeAnfragenNachStatus(unterkunft.$id, ['gebucht', 'bezahlt', 'freigegeben'])) { if (a.status === 'freigegeben' && !(Date.parse(a.fristAt) > jetzt)) continue if (!a.von || !a.bis || a.bis <= gestern) continue out.push({ uid: `${a.$id}@project.webklar.com`, von: a.von, bis: a.bis }) } return out.sort((x, y) => (x.von < y.von ? -1 : x.von > y.von ? 1 : x.uid < y.uid ? -1 : 1)) } /** * Oeffentliche Standalone-Seiten des Buchungskalenders (Vorbild: shopSeite.js): * GET /unterkunft/:siteKey Widget public/embed/unterkunft.js auf neutraler Seite * GET /unterkunft/:siteKey/buchung/:id Buchungsseite fuer Gaeste (public/unterkunft-buchung.js, Token ?t= liest nur das Skript) * Die Seite verraet nicht, ob Schluessel oder Buchung existieren; das Skript fragt die oeffentliche Schnittstelle. * Ungueltige Schluessel (z. B. "ical" der Export-Route /unterkunft/ical/:token.ics) und Ids: next(), am Ende 404. */ const ID_RE = /^[A-Za-z0-9_-]{1,36}$/ const DATEI = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'public', 'unterkunft-seite.html') const CSP = [ "default-src 'self'", "img-src 'self' data:", "script-src 'self'", "style-src 'self' 'unsafe-inline'", // das Widget legt ein