129 lines
6.2 KiB
JavaScript
129 lines
6.2 KiB
JavaScript
import { config } from '../config.js'
|
|
import { createDocument } from './appwriteAdmin.js'
|
|
import { actingPerson, texteHash } from './featureConsents.js'
|
|
import { neueAnsicht } from './kundenansicht/schalter.js'
|
|
import { KUNDENARTEN } from './kundenart.js'
|
|
import { FEHLER_NACHWEIS, WORTLAUT_STATUS } from './bestelltexte.js'
|
|
|
|
/**
|
|
* Weiche der Fassungen und Nachweis der Bestellseiten (Kundenansicht neu, P7, V12).
|
|
*
|
|
* fassungDerAnfrage entscheidet, ob eine Anfrage die Fassung 6 (neue Ansicht) oder die bisherige Fassung meint; alle Routen
|
|
* verzweigen nur bei 'neu'. speichereVertragsNachweis schreibt den angezeigten Wortlaut als Zeile in featureConsents (ohne Projekt),
|
|
* bevor die unumkehrbare Handlung geschieht (Stripe-Sitzung, Wechsel, Speicheraenderung, Antrag).
|
|
*/
|
|
|
|
const ARTEN = ['abo', 'wechsel', 'speicher', 'gutschrift']
|
|
|
|
// Grenzen der Collection featureConsents (wie featureConsents.js)
|
|
const MAX_ID = 36
|
|
const MAX_FASSUNGEN = 1000
|
|
const MAX_TEXTE = 20000
|
|
const MAX_IP = 64
|
|
const MAX_USER_AGENT = 300
|
|
const MAX_KONTO = 255
|
|
|
|
const nichtLeer = (wert) => typeof wert === 'string' && wert.trim() !== ''
|
|
|
|
/**
|
|
* 'alt' | 'neu' | null. 'neu': termsVersion ist nichtleer, gleich config.abo.termsVersionNeu UND die Anfrage sieht die neue Ansicht.
|
|
* 'alt': termsVersion ist gleich config.abo.termsVersion. Alles andere (auch eine fehlende Neu-Fassung) ist null. Die Neu-Fassung
|
|
* wird zuerst geprueft: bei gleichen Werten gilt neueAnsicht(req) ? 'neu' : null (P10 aendert hier nichts).
|
|
*/
|
|
export function fassungDerAnfrage(req, termsVersion) {
|
|
if (!nichtLeer(termsVersion)) return null
|
|
if (istNeueFassung(termsVersion)) return neueAnsicht(req) ? 'neu' : null
|
|
return termsVersion === config.abo.termsVersion ? 'alt' : null
|
|
}
|
|
|
|
/** Nichtleer und gleich config.abo.termsVersionNeu (ohne Sitzungsbezug; fuer Mails und Ereignisse). */
|
|
export function istNeueFassung(termsVersion) {
|
|
return nichtLeer(termsVersion) && nichtLeer(config.abo.termsVersionNeu) && termsVersion === config.abo.termsVersionNeu
|
|
}
|
|
|
|
function httpError(status, message) {
|
|
const err = new Error(message)
|
|
err.status = status
|
|
return err
|
|
}
|
|
|
|
const kurz = (wert, max) => String(wert ?? '').slice(0, max)
|
|
|
|
function zeitpunkt(at) {
|
|
const ms = at instanceof Date ? at.getTime() : typeof at === 'string' && /^\d{4}-\d\d-\d\dT/.test(at) ? Date.parse(at) : Number.NaN
|
|
if (Number.isNaN(ms)) throw httpError(400, 'Vertragsnachweis: Zeitpunkt fehlt oder ist ungültig')
|
|
return new Date(ms).toISOString()
|
|
}
|
|
|
|
function pruefeTexte(texte) {
|
|
if (!Array.isArray(texte) || texte.length === 0) throw httpError(400, 'Vertragsnachweis: Texte fehlen')
|
|
for (const eintrag of texte) {
|
|
const ok =
|
|
eintrag !== null &&
|
|
typeof eintrag === 'object' &&
|
|
!Array.isArray(eintrag) &&
|
|
nichtLeer(eintrag.id) &&
|
|
nichtLeer(eintrag.text) &&
|
|
(eintrag.haken === undefined || typeof eintrag.haken === 'boolean')
|
|
if (!ok) throw httpError(400, 'Vertragsnachweis: Text ungültig')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nachweis der Bestellseiten anlegen. featureKey ist die Art, projectId und stripeSessionId bleiben leer (der Nachweis entsteht vor der
|
|
* Handlung; die Zuordnung laeuft ueber Zeit, Konto und bezug). fassungen bekommt bezug und den Status des Wortlauts dazu.
|
|
* Pruefungen vor dem Schreiben (400, nichts geschrieben); jeder Fehler beim Schreiben wird zu einem 500 mit FEHLER_NACHWEIS, die Ursache
|
|
* steht nur im Log.
|
|
* @param {{ customerId: string, art: 'abo'|'wechsel'|'speicher'|'gutschrift', bezug?: string, texte: Array<{ id: string, text: string, haken?: boolean }>,
|
|
* fassungen: object, kundenart: 'privat'|'unternehmen', req: object, at: string|Date }} p
|
|
* @returns {Promise<object>} das angelegte Dokument
|
|
*/
|
|
export async function speichereVertragsNachweis({ customerId, art, bezug = '', texte, fassungen, kundenart, req, at } = {}) {
|
|
if (!ARTEN.includes(art)) throw httpError(400, 'Vertragsnachweis: Art ungültig')
|
|
if (!nichtLeer(customerId) || customerId.length > MAX_ID) throw httpError(400, 'Vertragsnachweis: Kunde fehlt oder ist ungültig')
|
|
if (!KUNDENARTEN.includes(kundenart)) throw httpError(400, 'Vertragsnachweis: Kundenart ungültig')
|
|
if (typeof bezug !== 'string') throw httpError(400, 'Vertragsnachweis: Bezug ungültig')
|
|
if (fassungen === null || typeof fassungen !== 'object' || Array.isArray(fassungen)) throw httpError(400, 'Vertragsnachweis: Fassungen fehlen')
|
|
pruefeTexte(texte)
|
|
const acceptedAt = zeitpunkt(at)
|
|
const fassungenJson = JSON.stringify({ ...fassungen, bezug, wortlaut: WORTLAUT_STATUS })
|
|
const texteJson = JSON.stringify(texte)
|
|
if (fassungenJson.length > MAX_FASSUNGEN) throw httpError(400, 'Vertragsnachweis: Fassungen zu lang')
|
|
if (texteJson.length > MAX_TEXTE) throw httpError(400, 'Vertragsnachweis: Texte zu lang')
|
|
try {
|
|
const person = actingPerson(req.session)
|
|
const konto = person?.name && person?.email ? `${person.name} <${person.email}>` : person?.name || person?.email || ''
|
|
return await createDocument(config.collections.featureConsents, {
|
|
customerId,
|
|
projectId: '',
|
|
featureKey: art,
|
|
stripeSessionId: '',
|
|
acceptedAt,
|
|
kundenart,
|
|
fassungen: fassungenJson,
|
|
texte: texteJson,
|
|
textHash: texteHash(texte),
|
|
ip: kurz(req.ip, MAX_IP),
|
|
userAgent: kurz(req.get('user-agent'), MAX_USER_AGENT),
|
|
konto: kurz(konto, MAX_KONTO),
|
|
})
|
|
} catch (err) {
|
|
console.error(`[vertragsnachweis] ${art} nicht gespeichert: ${err?.name ?? 'Fehler'} ${err?.status ?? ''} ${err?.message ?? ''}`.trim())
|
|
throw Object.assign(new Error(FEHLER_NACHWEIS), { status: 500 })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wie speichereVertragsNachweis, fuer die Routen: jeder Fehler (auch eine Pruefung der Eingaben) wird zu einem 500 mit FEHLER_NACHWEIS,
|
|
* damit der Kunde nie einen internen Text sieht und die Handlung (Stripe-Sitzung, Wechsel, Speicheraenderung, Antrag) unterbleibt.
|
|
*/
|
|
export async function speichereNachweisFuerRoute(args) {
|
|
try {
|
|
return await speichereVertragsNachweis(args)
|
|
} catch (err) {
|
|
if (err?.status === 500 && err.message === FEHLER_NACHWEIS) throw err
|
|
console.error(`[vertragsnachweis] Nachweis abgelehnt: ${err?.name ?? 'Fehler'} ${err?.status ?? ''} ${err?.message ?? ''}`.trim())
|
|
throw Object.assign(new Error(FEHLER_NACHWEIS), { status: 500 })
|
|
}
|
|
}
|