From 42a3befc5fb949de3c3501a2a4cb2ebf2dc94ad9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 19:34:37 +0000 Subject: [PATCH] Terminplaner TP2 C4a: Nachbesserung 1 Die Wahl des Live-Hosts steht jetzt nur noch in liveHost() (kontext.js); websiteUrl() und der Betriebsvorschlag des Fragebogens (routes/termine.js) nutzen dieselbe Funktion. Co-Authored-By: Claude Sonnet 5.5 --- server/routes/termine.js | 9 +++------ server/services/termine/kontext.js | 26 ++++++++++++++++++++------ test/termineKontext.test.js | 21 ++++++++++++++++++++- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/server/routes/termine.js b/server/routes/termine.js index 27abf80..92dd208 100644 --- a/server/routes/termine.js +++ b/server/routes/termine.js @@ -2,7 +2,6 @@ import { Router } from 'express' import { config } from '../config.js' import { getSessionCustomerId, requireSession } from '../middleware/session.js' import { aboMemo } from '../services/aboStatus.js' -import { analyseHosts } from '../services/analyse.js' import { getDocument } from '../services/appwriteAdmin.js' import { analysePrivacyData } from '../services/analysePrivacy.js' import { featureConfig } from '../services/featureRegistry.js' @@ -15,7 +14,7 @@ import { EXPORT_SEITE, exportSeite, hole, imZeitraum, zaehleAnfragen, zaehleKuen import { fehlendePflichtangaben, neuerFeedToken, normalisiereEinstellung } from '../services/termine/einstellungen.js' import { loeschTag, loeschzeit } from '../services/termine/frist.js' import { BRANCHEN, bestellpruefung, bestellungAnsicht, leerFragebogen } from '../services/termine/fragebogen.js' -import { kontextFuerPortal, kontextPerFeature, speichereEinstellung } from '../services/termine/kontext.js' +import { kontextFuerPortal, kontextPerFeature, liveHost, speichereEinstellung } from '../services/termine/kontext.js' import { zustellFehler } from '../services/termine/outbox.js' import { termineFuerKunde } from '../services/termine/schalter.js' import { belegungAus, freieZeiten } from '../services/termine/slots.js' @@ -305,11 +304,9 @@ async function betriebAusKonto(customerId) { return { name: daten.company, adresse: daten.address, email: daten.email, telefon: typeof kunde?.phone === 'string' ? kunde.phone : '' } } -/** Adresse der Live-Website des Projekts (nur aus liveDomain, nie die Vorschau-Adresse .project.webklar.com), sonst ''. */ +/** Adresse der Live-Website des Projekts (liveHost: nur aus liveDomain, nie die Vorschau-Adresse .project.webklar.com), sonst ''. */ function liveWebsite(project) { - if (!project?.liveDomain) return '' - const vorschau = project.subdomain ? `${String(project.subdomain).trim().toLowerCase()}.project.webklar.com` : null - const host = analyseHosts(project).find((h) => h !== vorschau) + const host = liveHost(project) return host ? `https://${host}` : '' } diff --git a/server/services/termine/kontext.js b/server/services/termine/kontext.js index 45a8433..335e094 100644 --- a/server/services/termine/kontext.js +++ b/server/services/termine/kontext.js @@ -301,10 +301,26 @@ export async function speichereFragebogen(feature, aendere, { by = '', now = new }) } +/** Vorschau-Host des Projekts (.project.webklar.com, klein) oder null; gleiche Schreibweise wie in analyseHosts. */ +function vorschauHost(project) { + return project.subdomain ? `${String(project.subdomain).trim().toLowerCase()}.project.webklar.com` : null +} + +/** + * Host der echten (Live-)Website eines Projekts: nur wenn project.liveDomain gesetzt ist, der erste gueltige Host aus + * analyseHosts(project), der nicht die Vorschau-Adresse .project.webklar.com ist; sonst undefined. + * Einzige Quelle fuer websiteUrl() und den Betriebsvorschlag des Fragebogens (routes/termine.js). + */ +export function liveHost(project) { + if (!project?.liveDomain) return undefined + const vorschau = vorschauHost(project) + return analyseHosts(project).find((host) => host !== vorschau) +} + /** * Adresse der Website des Betriebs (Link auf Gaesteseiten und im Mailfuss), immer die echte Seite statt der Vorschau: - * betrieb.websiteUrl; sonst https:// + Live-Domain des Projekts (wenn project.liveDomain einen gueltigen Host ergibt); - * sonst https:// + .project.webklar.com; sonst die Portal-Adresse (config.publicBaseUrl). + * betrieb.websiteUrl; sonst https:// + Live-Domain des Projekts (liveHost); sonst https:// + .project.webklar.com; + * sonst die Portal-Adresse (config.publicBaseUrl). */ export function websiteUrl(ctx) { const eigene = ctx?.einstellung?.betrieb?.websiteUrl @@ -312,10 +328,8 @@ export function websiteUrl(ctx) { const project = ctx?.project if (project) { // analyseHosts: [.project.webklar.com, Live-Domain], ungueltige und doppelte Hosts fehlen - const hosts = analyseHosts(project) - const vorschau = project.subdomain ? `${String(project.subdomain).trim().toLowerCase()}.project.webklar.com` : null - const live = project.liveDomain ? hosts.find((host) => host !== vorschau) : undefined - const host = live ?? hosts.find((h) => h === vorschau) + const vorschau = vorschauHost(project) + const host = liveHost(project) ?? analyseHosts(project).find((h) => h === vorschau) if (host) return `https://${host}` } return config.publicBaseUrl diff --git a/test/termineKontext.test.js b/test/termineKontext.test.js index 6c38865..011815c 100644 --- a/test/termineKontext.test.js +++ b/test/termineKontext.test.js @@ -31,7 +31,7 @@ const { leerFragebogen, normalisiereFragebogen, FRAGEBOGEN_MAX } = await import( const { activate } = await import('../server/services/siteFeatures.js') const K = await import('../server/services/termine/kontext.js') const M = await import('../server/services/termine/mails.js') -const { ladeEinstellung, speichereEinstellung, speichereFragebogen, kontextPerSiteKey, kontextPerFeature, kontextFuerPortal, websiteUrl, resetKontextForTests } = K +const { ladeEinstellung, speichereEinstellung, speichereFragebogen, kontextPerSiteKey, kontextPerFeature, kontextFuerPortal, websiteUrl, liveHost, resetKontextForTests } = K const { BETREFFE, werteFuer, anGast, anBetrieb } = M after(() => setMailTransportForTests(null)) @@ -554,6 +554,25 @@ test('websiteUrl: betrieb.websiteUrl, sonst Live-Domain, sonst Vorschau-Subdomai assert.equal(websiteUrl(null), config.publicBaseUrl) }) +test('liveHost: Live-Host nur aus liveDomain, nie die Vorschau; websiteUrl nutzt denselben Host', () => { + const faelle = [ + [{ subdomain: 'salon', liveDomain: 'www.salon.de' }, 'www.salon.de'], + [{ subdomain: 'salon', liveDomain: 'https://WWW.Salon.de/start?x=1' }, 'www.salon.de'], + [{ liveDomain: 'salon.de' }, 'salon.de'], + [{ subdomain: ' Salon ', liveDomain: 'salon.project.webklar.com' }, undefined], + [{ subdomain: 'salon', liveDomain: 'kein host!' }, undefined], + [{ subdomain: 'salon', liveDomain: '' }, undefined], + [{ subdomain: 'salon' }, undefined], + [{}, undefined], + [null, undefined], + ] + for (const [project, erwartet] of faelle) { + assert.equal(liveHost(project), erwartet, JSON.stringify(project)) + const ohneUrl = { einstellung: { betrieb: betriebMit({ websiteUrl: null }) }, project } + if (erwartet) assert.equal(websiteUrl(ohneUrl), `https://${erwartet}`, `websiteUrl folgt liveHost: ${JSON.stringify(project)}`) + } +}) + // ---------------------------------------------------------------- Fragebogen im Speicher und im Kontext (TP2) /** Normalisierter Fragebogen aus einer (teilweisen) Eingabe; die Eingabe muss gueltig sein. */