Files
Webklar-Kundenbereich/server/services/previewReconcile.js
T

78 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// R18: Abgleich der Preview-Zustaende mit dem Runner (alle 2 Minuten, nur mit PREVIEW_RUNNER=on).
// (a) 'building' ohne laufenden Verfolger und aelter als 70 Minuten => 'failed'
// (b) Dev-Projekte: Zustand laut Runner (laeuft => online/starting, sonst sleeping); Modus 'off' bleibt unberuehrt
// (c) Dev-Spezifikationen, die der Runner nicht kennt (z. B. nach Neustart), erneut hinterlegen
// Wirft nie: Fehler werden protokolliert und im Ergebnis gemeldet.
import { config } from '../config.js'
import { listDocuments, updateDocument, Query } from './appwriteAdmin.js'
import * as runnerClient from './previewRunnerClient.js'
import { hasLiveFollower, registerDevRound } from './previewDeploy.js'
export const STALE_BUILD_MS = 70 * 60 * 1000
const STALE_TEXT = 'Status unbekannt – bitte neu bauen'
const isDev = (d) => String(d.previewDetected || '').startsWith('dev')
export async function reconcilePreviews({ now = Date.now() } = {}) {
if (!config.previewRunner.enabled) return { skipped: true }
const result = { staleFailed: 0, stateUpdated: 0, reregistered: 0 }
let docs
try {
docs = await listDocuments(config.collections.websiteProjects, [
Query.equal('previewState', ['building', 'sleeping', 'starting', 'online']),
Query.limit(500),
])
} catch (err) {
console.error('[previewReconcile] Projekte nicht lesbar:', err.message)
return { ...result, error: err.message }
}
const patch = async (doc, data) => {
try { await updateDocument(config.collections.websiteProjects, doc.$id, data); return true } catch (err) {
console.error('[previewReconcile] Update fehlgeschlagen:', err.message)
return false
}
}
// (a)
for (const doc of docs) {
if (doc.previewState !== 'building' || !doc.repoFullName) continue
if (hasLiveFollower(doc.repoFullName)) continue
const since = Date.parse(doc.$updatedAt || doc.$createdAt || '')
if (!Number.isFinite(since) || now - since <= STALE_BUILD_MS) continue
if (await patch(doc, { previewState: 'failed', previewError: STALE_TEXT })) result.staleFailed++
}
// (b) + (c)
const devDocs = docs.filter((d) => isDev(d) && d.previewMode !== 'off' && d.previewState !== 'building' && d.subdomain)
if (!devDocs.length) return result
let st
try { st = await runnerClient.status() } catch (err) {
console.error('[previewReconcile] Runner-Status nicht abrufbar:', err.message)
return { ...result, error: err.message }
}
const entries = new Map((st?.dev || []).map((e) => [e.sub, e]))
const known = new Set([...(st?.known || []), ...entries.keys()])
const unknown = []
for (const doc of devDocs) {
const entry = entries.get(doc.subdomain)
const want = entry?.state === 'online' ? 'online' : entry?.state === 'starting' ? 'starting' : 'sleeping'
if (doc.previewState !== want && (await patch(doc, { previewState: want }))) result.stateUpdated++
if (!known.has(doc.subdomain)) unknown.push(doc)
}
if (unknown.length) {
try { await registerDevRound(unknown); result.reregistered = unknown.length } catch (err) {
console.error('[previewReconcile] Registrierung fehlgeschlagen:', err.message)
}
}
return result
}
/** Fuer setInterval: nie werfen. */
export function runReconcile() {
return reconcilePreviews().catch((err) => {
console.error('[previewReconcile] Lauf fehlgeschlagen:', err.message)
return { error: err.message }
})
}