Merge remote-tracking branch 'origin/main' into feature/oeffnungszeiten
Zusammenführung von TP2-Abschluss und Webshop (origin/main cab9c33) mit Öffnungszeiten live. Beide
Funktionen bleiben vollständig: jeder Konflikt ist als Vereinigung gelöst, Reihenfolge der Funktionsschlüssel
überall news, termine, shop, zeiten.
Konfliktdateien (28): public/app.js, dashboard.html, funktionen.js, widerrufen.html; scripts/stripe-products.mjs;
server/config.js, index.js, middleware/kundenansichtGuard.js, routes/abo.js, routes/admin/funktionenAdmin.js,
routes/funktionen.js, services/customerAdmin.js, customerViewLog.js, featureRegistry.js, retention.js,
siteFeatures.js, stripeBilling.js; test/customerViewLog, featureFreischaltung, featureRegistry, funktionenRoutes,
funktionenTermine, kundenansichtGuard, mailTemplates, publicForms, siteFeatures, stripeProductsScript (je
.test.js) und test/support/schema.js.
Entscheidungen: Mailvorlagen G01–G15, K01–K30, T01–T21 lückenlos (K24–K26 Webshop, K27–K30 Öffnungszeiten);
Stripe-Katalog mit shopSetup und zeitenSetup; Widerruf kennt shop und zeiten; ABO_FUNKTIONEN bleibt analyse
und termine (kein zeiten); activate() schreibt die Zeiten-Felder nur über extra; kein Halter von
feature-activate:<id> ruft disable() auf; die Aufbewahrungs-Löschung löscht Zeiten-Daten vor dem Abschalten und
hält den Kunden bei einem Fehler (held); die Admin-Antwort trägt ersatzTicket und warnung; Hash-Routen webshop
und zeiten, Ansichten view-webshop und view-zeiten; assertOwnProject behält die Meldung von main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
e87140e5ba
245 files changed
+38799
-1050
No files matched your search
@@ -39,6 +39,7 @@ const MOUNTS = [
|
||||
['/api/abo', (await import('../server/routes/abo.js')).default],
|
||||
['/api/funktionen', (await import('../server/routes/funktionen.js')).default],
|
||||
['/api/termine', termineRouter],
|
||||
['/api/shop', (await import('../server/routes/shop.js')).default],
|
||||
['/api/zeiten', zeitenRouter],
|
||||
]
|
||||
// Oeffentliche Schnittstelle (ohne Session) wie in server/index.js hinter dem Waechter; nicht in MOUNTS, weil sie keine Kunden-Route ist
|
||||
@@ -145,7 +146,7 @@ test('Behalten der Öffnungszeiten ist in der Kundenansicht gesperrt (Route gefu
|
||||
assert.equal(res.body.code, 'view_blocked')
|
||||
})
|
||||
|
||||
test('Erlaubnisliste: genau News, Terminplaner, Öffnungszeiten, Analyse-Link, Ende, Logout und Login', () => {
|
||||
test('Erlaubnisliste: genau News, Terminplaner, Webshop-Verwaltung, Öffnungszeiten, Analyse-Link, Ende, Logout und Login', () => {
|
||||
const ok = [
|
||||
['POST', '/api/news'], ['POST', '/api/news/image'], ['PATCH', '/api/news/abc'], ['DELETE', '/api/news/abc'],
|
||||
['POST', '/api/funktionen/analyse/heatmap-link'], ['POST', '/api/auth/kundenansicht/ende'],
|
||||
@@ -153,10 +154,12 @@ test('Erlaubnisliste: genau News, Terminplaner, Öffnungszeiten, Analyse-Link, E
|
||||
['POST', '/api/termine/buchungen'], ['POST', '/api/termine/buchungen/abc/aktion'], ['POST', '/api/termine/ressourcen/abc/feed'],
|
||||
['PATCH', '/api/termine/einstellungen'], ['PATCH', '/api/termine/einstellungen/'],
|
||||
['DELETE', '/api/termine/buchungen/abc'], ['DELETE', '/api/termine/buchungen/abc/'],
|
||||
['POST', '/api/shop/produkte'], ['POST', '/api/shop/produkte/'], ['POST', '/api/shop/produkte/sortierung'], ['PUT', '/api/shop/produkte/abc'],
|
||||
['DELETE', '/api/shop/produkte/abc'], ['PUT', '/api/shop/einstellungen'], ['PUT', '/api/shop/rechtstexte'], ['POST', '/api/shop/bild'],
|
||||
['PUT', '/api/zeiten/abc'], ['PUT', '/api/zeiten/abc/'],
|
||||
]
|
||||
for (const [m, p] of ok) assert.ok(allowed(m, p), `${m} ${p}`)
|
||||
assert.equal(KUNDENANSICHT_ALLOWED.length, 8 + 6 + 1, 'genau sechs Termine-Eintraege und ein Zeiten-Eintrag dazu, keine Wildcard')
|
||||
assert.equal(KUNDENANSICHT_ALLOWED.length, 8 + 6 + 7 + 1, 'genau sechs Termine-, sieben Webshop- und ein Zeiten-Eintrag dazu, keine Wildcard')
|
||||
for (const [m, p] of [
|
||||
['POST', '/api/chat/read'], ['POST', '/api/news/abc'], ['POST', '/api/auth/password'], ['POST', '/api/abo/checkout'], ['POST', '/api/funktionen/news/checkout'],
|
||||
// Terminplaner: nur der eigene Pfad, nur diese Methoden und Formen
|
||||
@@ -168,6 +171,14 @@ test('Erlaubnisliste: genau News, Terminplaner, Öffnungszeiten, Analyse-Link, E
|
||||
['PUT', '/api/termine/einstellungen'], ['PATCH', '/api/termine/buchungen/abc'], ['PATCH', '/api/termine/einstellungen/x'],
|
||||
['DELETE', '/api/termine/einstellungen'], ['DELETE', '/api/termine/buchungen'], ['DELETE', '/api/termine/buchungen/abc/def'],
|
||||
['DELETE', '/api/termine/ressourcen/abc'], ['PUT', '/api/termine/buchungen/abc'],
|
||||
// Webshop: Anfrage stellen und zurueckziehen sind in der Kundenansicht gesperrt (nicht auf der Liste)
|
||||
['POST', '/api/shop/anfrage'], ['POST', '/api/shop/anfrage/zurueckziehen'],
|
||||
// Webshop: Oeffnen und Bestaetigen der Rechtstexte macht der Haendler selbst; Stripe und Erstattungen sind nie dabei
|
||||
['POST', '/api/shop/oeffnen'], ['POST', '/api/shop/rechtstexte/bestaetigen'], ['POST', '/api/shop/stripe/verbinden'], ['POST', '/api/shop/bestellungen/abc/erstatten'],
|
||||
// und nur die ausgeschriebenen Pfade und Methoden der Verwaltung
|
||||
['POST', '/api/shop/rechtstexte'], ['POST', '/api/shop/einstellungen'], ['PATCH', '/api/shop/produkte/abc'], ['POST', '/api/shop/produkte/abc'],
|
||||
['PUT', '/api/shop/produkte'], ['DELETE', '/api/shop/produkte'], ['PUT', '/api/shop/bild'], ['POST', '/api/shop/bild/x'],
|
||||
['POST', '/api/shopX/produkte'], ['PUT', '/api/shop/produkte/abc/def'], ['DELETE', '/api/shop/einstellungen'],
|
||||
// Oeffnungszeiten: nur PUT auf das Projekt, sonst nichts
|
||||
['PUT', '/api/zeiten'], ['PUT', '/api/zeiten/'], ['PUT', '/api/zeiten/abc/vorschau'], ['PUT', '/api/zeiten/abc/def/ghi'], ['PUT', '/api/zeitenX/abc'],
|
||||
['POST', '/api/zeiten/abc'], ['PATCH', '/api/zeiten/abc'], ['DELETE', '/api/zeiten/abc'], ['POST', '/api/zeiten'],
|
||||
@@ -203,6 +214,25 @@ test('unbekanntes POST/PUT unter /api/termine in der Kundenansicht -> 403 view_b
|
||||
}
|
||||
})
|
||||
|
||||
// Router-Abgleich Webshop: genau die sieben Verwaltungs-Schreib-Routen stehen auf der Liste, jeder Eintrag trifft genau eine echte Route
|
||||
test('Router-Abgleich: genau sieben Schreib-Routen des Webshops stehen auf der Erlaubnisliste', () => {
|
||||
const shopRouter = MOUNTS.find(([prefix]) => prefix === '/api/shop')[1]
|
||||
const routen = writeRoutes([['/api/shop', shopRouter]])
|
||||
assert.deepEqual(
|
||||
routen.filter(({ method, path: p }) => allowed(method, p)).map(({ method, path: p }) => `${method} ${p}`).sort(),
|
||||
['DELETE /api/shop/produkte/x', 'POST /api/shop/bild', 'POST /api/shop/produkte', 'POST /api/shop/produkte/sortierung',
|
||||
'PUT /api/shop/einstellungen', 'PUT /api/shop/produkte/x', 'PUT /api/shop/rechtstexte']
|
||||
)
|
||||
const shopEintraege = KUNDENANSICHT_ALLOWED.filter(([, re]) => re.source.includes('shop'))
|
||||
assert.equal(shopEintraege.length, 7)
|
||||
for (const [m, re] of shopEintraege) assert.equal(routen.filter((r) => r.method === m && re.test(r.path)).length, 1, `${m} ${re}`)
|
||||
// gesperrt bleiben insbesondere Anfrage, Oeffnen und Bestaetigen
|
||||
for (const p of ['/api/shop/anfrage', '/api/shop/anfrage/zurueckziehen', '/api/shop/oeffnen', '/api/shop/rechtstexte/bestaetigen']) {
|
||||
assert.ok(routen.some((r) => r.method === 'POST' && r.path === p), `Route ${p} existiert`)
|
||||
assert.ok(!allowed('POST', p), p)
|
||||
}
|
||||
})
|
||||
|
||||
// Router-Abgleich: die Erlaubnisliste und die echten Schreib-Routen des Terminplaners decken sich genau; eine neue Route faellt
|
||||
// im Test "jede Schreib-Route ausserhalb der Erlaubnisliste -> 403" auf (sie muss gesperrt sein), ein toter Eintrag hier
|
||||
test('Router-Abgleich: genau sechs Schreib-Routen des Terminplaners stehen auf der Erlaubnisliste', () => {
|
||||
|
||||
Reference in new issue
Block a user