feat: Website-Projekte bearbeiten (Name/Subdomain/Typ) + Archivierung
- updateWebsiteProject: synct Name/Subdomain/Typ nach Gitea (.webklar-preview.json, Repo-Beschreibung), Preview-Deploy (Redeploy+Teardown+TLS) und Appwrite - Projekt-Typen: preview (Login), website (oeffentlich ohne Login), project (kein Hosting) - syncPreviewTls erzeugt fuer isPublic-Projekte Router ohne preview-auth - archiveRepo: Gitea archived + Appwrite-Flag + Preview-Teardown; Reaktivieren redeployt - syncReposToAppwrite uebernimmt repo.archived - Routen: PATCH /:id, POST /:id/archive, POST /:id/unarchive Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,29 @@ import { promisify } from 'node:util'
|
||||
import fs from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { config } from '../config.js'
|
||||
import { listDocuments, Query } from './appwriteAdmin.js'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
/**
|
||||
* Subdomains, die als "oeffentliche Website" (ohne Login) laufen sollen.
|
||||
* Diese bekommen in syncPreviewTls einen Router OHNE preview-auth-ForwardAuth.
|
||||
* Faellt bei Appwrite-Fehlern bewusst auf ein leeres Set zurueck -> alle Seiten
|
||||
* bleiben dann login-geschuetzt (sicherer Default).
|
||||
*/
|
||||
async function getPublicSubdomains() {
|
||||
try {
|
||||
const docs = await listDocuments(config.collections.websiteProjects, [
|
||||
Query.equal('isPublic', true),
|
||||
Query.limit(500),
|
||||
])
|
||||
return new Set(docs.map((d) => d.subdomain).filter(Boolean))
|
||||
} catch (err) {
|
||||
console.error('[previewDeploy] getPublicSubdomains fehlgeschlagen:', err.message)
|
||||
return new Set()
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPreviewConfig(repoFullName, branch) {
|
||||
const [owner, repo] = repoFullName.split('/')
|
||||
if (!owner || !repo || !config.gitea.apiToken) {
|
||||
@@ -93,17 +113,24 @@ export async function syncPreviewTls() {
|
||||
.map((entry) => entry.name)
|
||||
.sort()
|
||||
|
||||
const publicSet = await getPublicSubdomains()
|
||||
|
||||
const blocks = slugs.map((name) => {
|
||||
const host = `${name}.${baseHost}`
|
||||
const id = `preview-${name}`
|
||||
const isPublic = publicSet.has(name)
|
||||
// Oeffentliche Website: HTTPS-Router OHNE preview-auth -> frei zugaenglich.
|
||||
// Preview (Standard): mit preview-chain (ForwardAuth) -> login-geschuetzt.
|
||||
const httpsMiddlewareLines = isPublic
|
||||
? []
|
||||
: [' middlewares:', ' - preview-chain@docker']
|
||||
return [
|
||||
` ${id}-https:`,
|
||||
` rule: "Host(\`${host}\`)"`,
|
||||
' entryPoints:',
|
||||
' - websecure',
|
||||
' service: preview-nginx@docker',
|
||||
' middlewares:',
|
||||
' - preview-chain@docker',
|
||||
...httpsMiddlewareLines,
|
||||
' priority: 100',
|
||||
' tls:',
|
||||
' certResolver: letsencrypt',
|
||||
@@ -160,3 +187,23 @@ export async function runDeploy(repoFullName, branch, previewConfig) {
|
||||
|
||||
return { deployed: true, subdomain, targetDir, tls }
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt das Deploy-Verzeichnis einer Subdomain und synchronisiert die
|
||||
* Traefik-TLS-Router neu (z.B. beim Aendern der Subdomain oder Archivieren).
|
||||
*/
|
||||
export async function removeDeploy(subdomain) {
|
||||
if (!subdomain || !config.preview.deployRoot) return { removed: false }
|
||||
if (!/^[a-z0-9-]+$/.test(subdomain)) return { removed: false, reason: 'ungueltige Subdomain' }
|
||||
const targetDir = path.join(config.preview.deployRoot, subdomain)
|
||||
await fs.rm(targetDir, { recursive: true, force: true })
|
||||
|
||||
let tls = { synced: false }
|
||||
try {
|
||||
tls = await syncPreviewTls()
|
||||
} catch (err) {
|
||||
console.error('[previewDeploy] TLS-Sync nach removeDeploy fehlgeschlagen:', err.message)
|
||||
tls = { synced: false, error: err.message }
|
||||
}
|
||||
return { removed: true, subdomain, tls }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user