Preview: Deploy auf fremde Subdomain sperren

Ein kopiertes Repo (git clone + push, Vorlage) bringt die
.webklar-preview.json des Originals mit. Sein erster Push ueberschrieb
die Preview des Originals, danach scheiterte der Webhook mit 500
(Unique-Index auf subdomain). Fast passiert bei Olzinger -> HSK-Vorlage,
04.10.2026.

- foreignSubdomainOwner: Subdomain gehoert einem anderen Repo oder einem
  Projekt ohne Repo; ein in Gitea umgezogenes Repo (Weiterleitung) zaehlt
  nicht als fremd
- Webhook: fremde Subdomain -> kein Deploy, Projekt ohne Preview (neu:
  synced, Repo-Name statt fremdem Anzeigenamen); eine schon vergebene
  eigene Preview bleibt, WOMS zeigt den Grund als Preview-Fehler
- runDeploy prueft fuer alle anderen Wege (WOMS, Reaktivieren), vor dem
  Runner, damit ein gesperrter Aufruf keinen laufenden Bau verwaist;
  Appwrite-Fehler bei der Pruefung blockieren dort nicht (Webhook streng)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 19:46:27 +00:00
1 parent 4fc4c70b7d
commit 75794f14d5
4 files changed
+370 -5

No files matched your search

+27 -5
View File
@@ -1,11 +1,12 @@
import crypto from 'node:crypto'
import { Router } from 'express'
import { config } from '../../config.js'
import { upsertWebsiteProjectByRepo, getDocument } from '../../services/appwriteAdmin.js'
import { upsertWebsiteProjectByRepo, getDocument, listDocuments, Query } from '../../services/appwriteAdmin.js'
import {
fetchPreviewConfig,
defaultPreviewConfig,
runDeploy,
foreignSubdomainBlock,
deployKeepsStatus,
buildPreviewUrl,
} from '../../services/previewDeploy.js'
@@ -107,18 +108,24 @@ router.post('/gitea', async (req, res) => {
}
}
const previewConfig = previewConfigFile || defaultPreviewConfig(name)
const hasPreview = Boolean(previewConfigFile?.enabled !== false && previewConfigFile)
const wantsPreview = Boolean(previewConfigFile?.enabled !== false && previewConfigFile)
// Kopiertes Repo mit der .webklar-preview.json eines anderen Projekts: dessen Subdomain weder
// beliefern noch uebernehmen - das Projekt bleibt ohne Preview, bis WOMS ihm eine eigene gibt.
const blocked = wantsPreview
? await foreignSubdomainBlock(name, previewConfig.subdomain || name.split('/').pop())
: null
const hasPreview = wantsPreview && !blocked
// Nur Projekte mit aktivierter Preview bekommen eine Subdomain zugewiesen -
// Repo-only-Projekte (enabled:false, keine Subdomain) bleiben ohne Website.
const subdomain = hasPreview ? previewConfig.subdomain || name.split('/').pop() : ''
const previewUrl = subdomain ? buildPreviewUrl(subdomain) : ''
let deployResult = { deployed: false, skipped: true }
let deployResult = blocked || { deployed: false, skipped: true }
const configBranch = previewConfig.branch || 'main'
if (hasPreview && previewConfig.enabled !== false && branch === configBranch) {
try {
deployResult = await runDeploy(name, branch, previewConfig, { sha: commitSha })
deployResult = await runDeploy(name, branch, previewConfig, { sha: commitSha, subdomainChecked: true })
} catch (deployErr) {
console.error('[webhook] deploy failed:', deployErr.message)
deployResult = { deployed: false, error: deployErr.message }
@@ -141,7 +148,8 @@ router.post('/gitea', async (req, res) => {
const payloadNameStillCurrent = payloadName.toLowerCase() === current.toLowerCase()
const projectPayload = {
projectName: previewConfig.displayName || (payloadNameStillCurrent ? repo.name : '') || subdomain,
// der Anzeigename einer fremden Config gehoert ebenfalls dem Original
projectName: (blocked ? '' : previewConfig.displayName) || (payloadNameStillCurrent ? repo.name : '') || subdomain,
templateName: previewConfig.templateName || config.gitea.templateRepo,
giteaRepoUrl: payloadNameStillCurrent
? repo.html_url || `${config.gitea.baseUrl}/${current}`
@@ -161,6 +169,20 @@ router.post('/gitea', async (req, res) => {
} else if (hasPreview) {
projectPayload.status = 'pending'
projectPayload.provisioningStatus = deployResult.error ? 'deploy_failed' : 'pending'
} else if (blocked) {
// eine schon vergebene eigene Preview bleibt, wie sie ist - sonst Projekt ohne Preview;
// ein vorhandener Projektname auch (der Repo-Name waere nur der Ersatz fuer neue Projekte)
const existing = (await listDocuments(config.collections.websiteProjects, [
Query.equal('repoFullName', current),
Query.limit(1),
]))[0]
if (!existing?.subdomain) projectPayload.status = 'synced'
else {
// sonst stuende im WOMS weiter der alte Bau als aktuell da (Fehlertext nur bei 'failed')
projectPayload.previewState = 'failed'
projectPayload.previewError = `Neuer Stand nicht veröffentlicht: In .webklar-preview.json steht die Subdomain „${blocked.subdomain}“, die gehört ${blocked.owner}. Projekt im WOMS bearbeiten und speichern, dann stimmt die Datei wieder.`.slice(0, 500)
}
if (existing?.projectName) delete projectPayload.projectName
} else {
projectPayload.status = 'synced'
}