Website-Analyse: AVV als Seite, www/Apex-Domains, Abo-Check unabhängig vom Schalter, Dev-Hinweis

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-01 11:04:50 +00:00
1 parent 7ead59ff73
commit 714687fe3b
10 files changed
+267 -20

No files matched your search

+8 -9
View File
@@ -169,15 +169,14 @@ const server = app.listen(config.port, '0.0.0.0', () => {
.catch((err) => console.warn('[funktionen] Freischalten fehlgeschlagen:', err.message))
setInterval(activateFeatures, 5 * 60 * 1000).unref()
// Website-Analyse: aktive Analysen ohne laufendes Wartungs-Abo beenden (taeglich, 2 Minuten nach Start beginnend)
if (config.analyse.enabled) {
const analyseAboCheck = () =>
runAboCheck({ now: new Date() })
.then((r) => r.deactivated.length && console.log(`[analyse] ${r.deactivated.length} Analyse(n) ohne Abo beendet`))
.catch((err) => console.warn('[analyse] Abo-Prüfung fehlgeschlagen:', err.message))
setTimeout(analyseAboCheck, 2 * 60 * 1000).unref()
setInterval(analyseAboCheck, 24 * 60 * 60 * 1000).unref()
}
// Website-Analyse: aktive Analysen ohne laufendes Wartungs-Abo beenden (taeglich, 2 Minuten nach Start beginnend).
// Laeuft unabhaengig vom Schalter ANALYSE_ENABLED: er liest nur Analyse-Dokumente und ist ohne solche wirkungslos.
const analyseAboCheck = () =>
runAboCheck({ now: new Date() })
.then((r) => r.deactivated.length && console.log(`[analyse] ${r.deactivated.length} Analyse(n) ohne Abo beendet`))
.catch((err) => console.warn('[analyse] Abo-Prüfung fehlgeschlagen:', err.message))
setTimeout(analyseAboCheck, 2 * 60 * 1000).unref()
setInterval(analyseAboCheck, 24 * 60 * 60 * 1000).unref()
// Neue Gitea-Repos erkennen, Zuordnungs-Mail senden, Erinnerungen/Wiederholungen (alle 10 Minuten)
const repoAssign = () =>
+9 -2
View File
@@ -18,7 +18,8 @@ import {
ELIGIBILITY_MESSAGES,
activateAnalyse,
analyseDeps,
analyseDomains,
analyseHosts,
isDevPreview,
analyseEligibility,
deactivateAnalyse,
} from '../services/analyse.js'
@@ -41,8 +42,14 @@ const ANALYSE_ERROR = 'Die Website-Analyse konnte gerade nicht geändert werden.
async function analyseView(customerId, projectId) {
if (!config.analyse.enabled) return { comingSoon: true }
const [doc, eligibility] = await Promise.all([getFeatureDoc(customerId, projectId, 'analyse'), analyseEligibility(customerId, projectId)])
let devMode = false
if (doc?.state === 'active') {
const project = eligibility.project || (await getDocument(config.collections.websiteProjects, projectId).catch(() => null))
devMode = isDevPreview(project)
}
return {
state: doc?.state || 'off',
devMode,
accentColor: doc?.accentColor || '#111111',
eligible: eligibility.ok,
reason: eligibility.reason || null,
@@ -233,7 +240,7 @@ router.post('/analyse/heatmap-link', analyseGuard, async (req, res) => {
if (!doc?.siteKey || doc.state !== 'active') {
return res.status(409).json({ error: 'Die Website-Analyse ist für dieses Projekt nicht aktiv.' })
}
const domains = analyseDomains(project)
const domains = analyseHosts(project)
if (!domains.length) return res.status(409).json({ error: 'Keine gültige Domain' })
const host = project.liveDomain ? domains[domains.length - 1] : domains[0]
const token = analyseDeps.client.heatToken({ siteKey: doc.siteKey, path, vp })
+14 -3
View File
@@ -58,8 +58,8 @@ export async function analyseEligibility(customerId, projectId) {
const HOSTNAME_RE = /^(?=.{4,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$/
/** `<subdomain>.project.webklar.com` plus Live-Domain (klein, ohne Schema/Pfad), ohne Doppelte; ungueltige Hosts werden uebersprungen. */
export function analyseDomains(project) {
/** Gespeicherte Hosts: `<subdomain>.project.webklar.com` plus Live-Domain (klein, ohne Schema/Pfad), ohne Doppelte; ungueltige werden uebersprungen. */
export function analyseHosts(project) {
const domains = []
if (project.subdomain) domains.push(`${String(project.subdomain).trim().toLowerCase()}.project.webklar.com`)
const live = String(project.liveDomain || '')
@@ -71,6 +71,17 @@ export function analyseDomains(project) {
return [...new Set(domains)].filter((d) => HOSTNAME_RE.test(d))
}
/** Erlaubte Origin-Domains des Messdienstes: gespeicherte Hosts plus www-/Apex-Gegenstueck der Live-Domain (Besucher kommen mit und ohne www), max. 5. */
export function analyseDomains(project) {
const hosts = analyseHosts(project)
const live = project.liveDomain ? hosts[hosts.length - 1] : ''
const extra = live && hosts.length > 1 ? (live.startsWith('www.') ? live.slice(4) : `www.${live}`) : ''
return [...new Set([...hosts, extra].filter(Boolean))].filter((d) => HOSTNAME_RE.test(d)).slice(0, 5)
}
/** Dev-Server-Projekte (previewDetected 'dev…') bekommen kein Messskript: der Runner injiziert nur in veroeffentlichte Ordner. */
export const isDevPreview = (project) => String(project?.previewDetected || '').startsWith('dev')
async function customerOf(customerId) {
try {
return await getDocument(config.collections.customers, customerId)
@@ -111,7 +122,7 @@ async function mailSafe(customerId, template, project) {
function privacyTicket(customerId, project, siteKey) {
const name = project.projectName || project.subdomain || ''
const domains = analyseDomains(project)
const domains = analyseHosts(project)
return analyseDeps.ticket({
customerId,
project,