Rechtstexte: Dokument-Registry und Fassungskonstante des Terminplaners
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
938b7099dd
commit
b20d0464c4
6 files changed
+414
-53
No files matched your search
@@ -1,54 +1,13 @@
|
||||
// Veroeffentlichte Rechtstexte der Website-Analyse (je Fassung feste Dateien unter public/rechtliches/)
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join } from 'node:path'
|
||||
import { Router } from 'express'
|
||||
import { config } from '../config.js'
|
||||
// Veroeffentlichte Rechtstexte der Website-Analyse (je Fassung feste Dateien unter public/rechtliches/):
|
||||
// duenner Wrapper um die Dokument-Registry legalDocs.js (Satz 'analyse')
|
||||
import { legalPaths as registryPaths, legalPdfAttachments as registryAttachments, legalPdfFiles as registryFiles, legalRouter } from './legalDocs.js'
|
||||
|
||||
const publicDir = fileURLToPath(new URL('../../public/', import.meta.url))
|
||||
const VERSION_RE = /^\d{4}-\d{2}-\d{2}$/
|
||||
export const legalPaths = (version) => registryPaths('analyse', version)
|
||||
|
||||
function checkVersion(version) {
|
||||
if (!VERSION_RE.test(String(version))) throw new Error('Analyse: ungültige Fassung')
|
||||
return String(version)
|
||||
}
|
||||
|
||||
export function legalPaths(version) {
|
||||
const v = checkVersion(version)
|
||||
return {
|
||||
avv: `/rechtliches/avv-website-analyse-${v}.html`,
|
||||
lb: `/rechtliches/leistungsbeschreibung-website-analyse-${v}.html`,
|
||||
}
|
||||
}
|
||||
|
||||
export function legalPdfFiles(version) {
|
||||
const v = checkVersion(version)
|
||||
return {
|
||||
avv: { filename: `AVV-Website-Analyse-${v}.pdf`, path: join(publicDir, 'rechtliches', `avv-website-analyse-${v}.pdf`) },
|
||||
lb: { filename: `Leistungsbeschreibung-Website-Analyse-${v}.pdf`, path: join(publicDir, 'rechtliches', `leistungsbeschreibung-website-analyse-${v}.pdf`) },
|
||||
}
|
||||
}
|
||||
export const legalPdfFiles = (version) => registryFiles('analyse', version)
|
||||
|
||||
/** Beide PDFs als Mail-Anhaenge; wirft, wenn eine Datei fehlt oder nicht lesbar ist. */
|
||||
export async function legalPdfAttachments(version) {
|
||||
const files = legalPdfFiles(version)
|
||||
return Promise.all(
|
||||
[files.avv, files.lb].map(async (f) => ({
|
||||
filename: f.filename,
|
||||
content: await readFile(f.path),
|
||||
contentType: 'application/pdf',
|
||||
}))
|
||||
)
|
||||
}
|
||||
export const legalPdfAttachments = (version) => registryAttachments('analyse', version)
|
||||
|
||||
/** Adressen ohne Fassung liefern immer die aktuelle geprüfte Fassung (config.analyse.avvVersion). */
|
||||
export function analyseLegalRouter() {
|
||||
const router = Router()
|
||||
const send = (key) => (_req, res, next) => {
|
||||
const file = join(publicDir, legalPaths(config.analyse.avvVersion)[key])
|
||||
res.sendFile(file, (err) => err && next(err.code === 'ENOENT' ? undefined : err))
|
||||
}
|
||||
router.get('/avv-website-analyse.html', send('avv'))
|
||||
router.get('/leistungsbeschreibung-website-analyse.html', send('lb'))
|
||||
return router
|
||||
}
|
||||
export const analyseLegalRouter = () => legalRouter({ saetze: ['analyse'] })
|
||||
@@ -0,0 +1,127 @@
|
||||
// Dokument-Registry der veroeffentlichten Rechtstexte (je Fassung feste Dateien unter public/rechtliches/):
|
||||
// Saetze 'analyse' (AVV, Leistungsbeschreibung der Website-Analyse) und 'termine' (Leistungsbeschreibung, AVV und
|
||||
// Widerrufsbelehrung mit Muster-Widerrufsformular des Terminplaners). analyseLegal.js ist ein duenner Wrapper darum.
|
||||
// Die Fassung ist der Berliner Kalendertag der Texte (JJJJ-MM-TT); eine ausgelieferte Fassung wird nie ueberschrieben.
|
||||
import { access, readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join } from 'node:path'
|
||||
import { Router } from 'express'
|
||||
import { config } from '../config.js'
|
||||
|
||||
const publicDir = fileURLToPath(new URL('../../public/', import.meta.url))
|
||||
const VERSION_RE = /^\d{4}-\d{2}-\d{2}$/
|
||||
|
||||
/**
|
||||
* Dokumente je Satz in Anhangsreihenfolge: slug = Dateiname der HTML-Seite und des PDFs (<slug>-<Fassung>.html|pdf),
|
||||
* pdf = Dateiname des Mail-Anhangs (<pdf>-<Fassung>.pdf). config.js baut die Adressen des Terminplaners aus denselben
|
||||
* Slugs (config.js darf diese Datei nicht importieren); test/legalDocs.test.js gleicht beide ab.
|
||||
*/
|
||||
const SAETZE = {
|
||||
analyse: {
|
||||
avv: { slug: 'avv-website-analyse', pdf: 'AVV-Website-Analyse' },
|
||||
lb: { slug: 'leistungsbeschreibung-website-analyse', pdf: 'Leistungsbeschreibung-Website-Analyse' },
|
||||
},
|
||||
termine: {
|
||||
lb: { slug: 'leistungsbeschreibung-terminplaner', pdf: 'Leistungsbeschreibung-Terminplaner' },
|
||||
avv: { slug: 'avv-terminplaner', pdf: 'AVV-Terminplaner' },
|
||||
widerruf: { slug: 'widerrufsbelehrung-funktionen', pdf: 'Widerrufsbelehrung-Muster-Widerrufsformular' },
|
||||
},
|
||||
}
|
||||
|
||||
/** Satz, dessen Dokumente eine Funktion (Registerschluessel) mit der Bestellung bekommt; News hat keine. */
|
||||
const FUNKTION_SATZ = { news: null, termine: 'termine' }
|
||||
|
||||
/** Aktuelle Fassung je Satz, bei jedem Aufruf aus config gelesen (Tests aendern sie zur Laufzeit). */
|
||||
const aktuelleFassung = (satz) => (satz === 'analyse' ? config.analyse.avvVersion : config.termine.avvVersion)
|
||||
|
||||
function satzDokumente(satz) {
|
||||
if (typeof satz !== 'string' || !Object.hasOwn(SAETZE, satz)) throw new Error('Rechtstexte: unbekannter Satz')
|
||||
return SAETZE[satz]
|
||||
}
|
||||
|
||||
function pruefeFassung(version) {
|
||||
if (!VERSION_RE.test(String(version))) throw new Error('Rechtstexte: ungültige Fassung')
|
||||
return String(version)
|
||||
}
|
||||
|
||||
/** Adressen der versionierten HTML-Seiten: { [doc]: '/rechtliches/<slug>-<Fassung>.html' }. */
|
||||
export function legalPaths(satz, version) {
|
||||
const dokumente = satzDokumente(satz)
|
||||
const v = pruefeFassung(version)
|
||||
return Object.fromEntries(Object.entries(dokumente).map(([doc, d]) => [doc, `/rechtliches/${d.slug}-${v}.html`]))
|
||||
}
|
||||
|
||||
/** PDF je Dokument: { [doc]: { filename (Anhangsname), path (Datei unter public/rechtliches/) } }. */
|
||||
export function legalPdfFiles(satz, version) {
|
||||
const dokumente = satzDokumente(satz)
|
||||
const v = pruefeFassung(version)
|
||||
return Object.fromEntries(
|
||||
Object.entries(dokumente).map(([doc, d]) => [doc, { filename: `${d.pdf}-${v}.pdf`, path: join(publicDir, 'rechtliches', `${d.slug}-${v}.pdf`) }])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* PDFs als Mail-Anhaenge in der Reihenfolge des Satzes (termine: lb, avv, widerruf; analyse: avv, lb); wirft, wenn eine
|
||||
* Datei fehlt oder nicht lesbar ist. ohne: Dokumente, die nicht angehaengt werden (sie muessen nicht vorhanden sein).
|
||||
*/
|
||||
export async function legalPdfAttachments(satz, version, { ohne = [] } = {}) {
|
||||
const dateien = legalPdfFiles(satz, version)
|
||||
for (const doc of ohne) {
|
||||
if (!Object.hasOwn(dateien, doc)) throw new Error('Rechtstexte: unbekanntes Dokument')
|
||||
}
|
||||
return Promise.all(
|
||||
Object.entries(dateien)
|
||||
.filter(([doc]) => !ohne.includes(doc))
|
||||
.map(async ([, f]) => ({ filename: f.filename, content: await readFile(f.path), contentType: 'application/pdf' }))
|
||||
)
|
||||
}
|
||||
|
||||
function funktionSatz(featureKey) {
|
||||
if (typeof featureKey !== 'string' || !Object.hasOwn(FUNKTION_SATZ, featureKey)) throw new Error('Rechtstexte: unbekannte Funktion')
|
||||
return FUNKTION_SATZ[featureKey]
|
||||
}
|
||||
|
||||
/**
|
||||
* Anhaenge der Bestellmail einer Funktion in der aktuellen Fassung: termine => Satz 'termine', news => []. Wirft bei
|
||||
* unbekannter Funktion und bei fehlender Datei (der Aufrufer entscheidet, was dann geschieht).
|
||||
*/
|
||||
export async function featureLegalAttachments(featureKey, opts) {
|
||||
const satz = funktionSatz(featureKey)
|
||||
return satz ? legalPdfAttachments(satz, aktuelleFassung(satz), opts) : []
|
||||
}
|
||||
|
||||
/**
|
||||
* true, wenn alle Rechtsdokumente der Funktion in der aktuellen Fassung vorliegen: HTML-Seiten (die die Bestellseite
|
||||
* verlinkt) und PDFs (die Mail anhaengt). Unbekannte Funktion und jeder Lesefehler: false (fail-closed).
|
||||
*/
|
||||
export async function featureLegalReady(featureKey) {
|
||||
try {
|
||||
const satz = funktionSatz(featureKey)
|
||||
if (!satz) return true
|
||||
const v = aktuelleFassung(satz)
|
||||
await Promise.all(Object.values(legalPaths(satz, v)).map((adresse) => access(join(publicDir, adresse))))
|
||||
await legalPdfAttachments(satz, v)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adressen ohne Fassung (/rechtliches/<slug>.html) liefern die Datei der aktuellen Fassung aus config. Nur fuer
|
||||
* Menschen und die WOMS-Karten: keine Mail und kein Zustimmungstext darf sie verlinken, denn sie aendern sich mit der
|
||||
* Fassung. Gehoert vor express.static; fehlt die Datei, geht die Anfrage weiter (404). saetze: welche Saetze bedient
|
||||
* werden, dir: Verzeichnis der Dateien (Standard public/rechtliches, Tests uebergeben ein eigenes).
|
||||
*/
|
||||
export function legalRouter({ saetze = Object.keys(SAETZE), dir = join(publicDir, 'rechtliches') } = {}) {
|
||||
const router = Router()
|
||||
for (const satz of saetze) {
|
||||
for (const d of Object.values(satzDokumente(satz))) {
|
||||
router.get(`/${d.slug}.html`, (_req, res, next) => {
|
||||
const datei = join(dir, `${d.slug}-${pruefeFassung(aktuelleFassung(satz))}.html`)
|
||||
res.sendFile(datei, (err) => err && next(err.code === 'ENOENT' ? undefined : err))
|
||||
})
|
||||
}
|
||||
}
|
||||
return router
|
||||
}
|
||||
Reference in new issue
Block a user