Buchungskalender: Strang widget zusammenführen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
cebde0e070
10 files changed
+1380
-2
No files matched your search
@@ -1,8 +1,11 @@
|
||||
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 { ladeFuerProjekt } from '../services/unterkunft/kontext.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'
|
||||
|
||||
@@ -30,6 +33,54 @@ async function exportEreignisse(unterkunft, jetzt) {
|
||||
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 <style>-Element an
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'none'",
|
||||
"form-action 'self'",
|
||||
].join('; ')
|
||||
|
||||
let vorlage = null
|
||||
export function resetUnterkunftSeiteForTests() {
|
||||
vorlage = null
|
||||
}
|
||||
|
||||
function escapeAttr(s) {
|
||||
return String(s).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>')
|
||||
}
|
||||
|
||||
function seite(res, next, siteKey, ansicht) {
|
||||
try {
|
||||
if (vorlage === null) vorlage = readFileSync(DATEI, 'utf8')
|
||||
} catch (err) {
|
||||
return next(err)
|
||||
}
|
||||
const html = vorlage.replaceAll('__SITEKEY__', escapeAttr(siteKey)).replaceAll('__ANSICHT__', escapeAttr(ansicht))
|
||||
res.set({
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Cache-Control': 'no-store',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'Referrer-Policy': 'no-referrer',
|
||||
'Content-Security-Policy': CSP,
|
||||
'X-Robots-Tag': 'noindex',
|
||||
})
|
||||
return res.status(200).send(html)
|
||||
}
|
||||
|
||||
export function unterkunftSeiteRouter() {
|
||||
const router = Router()
|
||||
|
||||
@@ -48,7 +99,14 @@ export function unterkunftSeiteRouter() {
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/unterkunft/:siteKey', (req, res, next) => {
|
||||
if (!SITE_KEY_RE.test(req.params.siteKey)) return next()
|
||||
return seite(res, next, req.params.siteKey, 'widget')
|
||||
})
|
||||
router.get('/unterkunft/:siteKey/buchung/:id', (req, res, next) => {
|
||||
if (!SITE_KEY_RE.test(req.params.siteKey) || !ID_RE.test(req.params.id)) return next()
|
||||
return seite(res, next, req.params.siteKey, 'buchung')
|
||||
})
|
||||
return router
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user