Key: webseiten-builder · App: apps/websitebuilder + separater Builder-Service · Plan: Basis (gratis) · Seit: Juli 2026
KI-Website-Builder für die öffentliche Vereinswebseite (CluWeb): Onboarding-Wizard mit KI-Erstentwurf, Direct-Manipulation-Editor mit KI-Edit pro Sektion, Publish-Schalter — veröffentlicht ersetzt die Webseite das Login auf der Root der Club-Domain (https://<club>.cluhu.ch/).
Der Builder ist bewusst KEINE Django-App im cluhu-Monolith, sondern ein eigenständiger Service:
| cluhu | Builder-Service | |
|---|---|---|
| Code | apps/websitebuilder (dünne Schicht) |
/srv/cluhu/builder/code (eigenes Git-Repo) |
| Stack | django_cluhu (Port 7200) |
cluhu_builder (/srv/cluhu/builder/compose, Port 17556) |
| DB | cluhu-Postgres | eigene Postgres (cluhu_builder) |
| Öffentlich | https://<club>.cluhu.ch |
https://builder.cluhu.ch |
Kommunikation ausschliesslich über interne HTTP-APIs (Bearer-Key) und SSO-Tokens (HMAC-signiert, 60 s gültig). Secrets liegen paarweise identisch in beiden .env (/srv/cluhu/compose/.env ↔ /srv/cluhu/builder/compose/.env).
Das alte apps/clubweb-Modellgerüst (8 leere Models) ist durch den Builder-Service abgelöst und kann aufgeräumt werden.
redirect_url direkt in den Initial-Wizard/_internal/probe-url/ vorgeprüft — JS-lastige oder textarme Seiten lösen eine Warnung + Voll-Fragen aus) → Vereinsfragen → Logo & Farben (Logo landet im Datei-Browser-Ordner „Web-Dateien") → asynchrone KI-Generierung mit Fortschrittsseite. Der URL-Pfad crawlt bis 8 Unterseiten (inkl. sitemap.xml, Duplikat-Filter), übernimmt Bilder der alten Website und alle KI-Stock-Fotos in „Web-Dateien" (öffentlich, SHA-256-Dedupe, via /_internal/builder-files/upload/), extrahiert echte Kontaktdaten und generiert zweistufig (Outline → ein LLM-Call pro Seite). Nachgelagert: Link-Integritäts-Check (erfundene interne Links → existierende Seiten oder weg), Bild-Duplikat-Ersatz, Verwendungs-Meldung an den Datei-Browser./clucore/ bleibt immer der Login-Einstieg; auf der Webseite ist der Login fest im Menü verankert (ab 2 Seiten) bzw. als Login-Icon oben rechts (Onepager) — nicht löschbar.Ein Verein kann bis zu 3 Webseiten parallel halten (Slot-Leiste über dem Editor) — zum Ausprobieren oder für einen kompletten Neuanfang, ohne die bestehende Webseite zu überschreiben. Genau eine ist live (DB-Constraint: partial unique auf club where is_published).
/_internal/tenants/<slug>/clone/). Media-Dateien werden geteilt, nicht dupliziert; Tenant-Slug-Präfixe in Slot-URLs werden umgeschrieben./_internal/tenants/<slug>/delete/, nur für Tenants mit external_ref).MAX_SLOTS in websitebuilder/views.py — Kandidat für Plan-Differenzierung).Tenant.external_ref ist seit den Slots nicht mehr unique — Quelle der Wahrheit für die Club-Zuordnung sind die ClubWebsite-Zeilen.addon_deactivate (core) ruft websitebuilder.views.handle_addon_deactivated: Der Live-Slot wird offline genommen (Club-Domain zeigt wieder das Login, builder.cluhu.ch liefert 404), alle Slots und Daten bleiben erhalten. Reaktivierung stellt nichts automatisch live — der Verein schaltet bewusst neu.
| URL | Funktion |
|---|---|
/clucore/website/ |
Editor-Einbettung (oder Redirect in den Wizard) |
/clucore/website/veroeffentlichen/ |
Publish-Schalter (POST) |
/clucore/website/editor/ |
Vollbild-Editor (mintet frischen SSO-Token) |
/clucore/website/start/… |
Wizard: /, fragen/, logo/, generierung/, status/, neu/ |
/clucore/website/slot/<uuid>/… |
Slots: waehlen/, umbenennen/, kopieren/, loeschen/ |
/ + /<slug>/ (Club-Domain) |
Public-Proxy der veröffentlichten Webseite (Catch-all nach allen cluhu-Routen) |
WEBSITE_BUILDER_INTERNAL_API_KEY)| Endpoint | Funktion |
|---|---|
POST /_internal/onboarding/start/ |
Generierungs-Job starten (Modi url/qa/upload, Club-Kontext, Farben, Logo) |
GET /_internal/onboarding/status/<job_id>/ |
Job-Polling für den Wizard |
POST /_internal/extract-logo/ |
Logo speichern + dominante Farben extrahieren (liefert Dicts mit hex) |
POST /_internal/tenants/<slug>/publish/ |
Publish-Status → Tenant.is_public |
POST /_internal/tenants/<slug>/clone/ |
Slot kopieren (Deep-Copy inkl. Historie) |
POST /_internal/tenants/<slug>/delete/ |
Slot löschen (nur cluhu-Tenants) |
POST /_internal/provision/ |
Express-Provisionierung (Hero-only; vom Wizard abgelöst) |
GET /_sso/?token=… |
SSO-Einstieg in den Editor (Session-Scope pro Tenant) |
ClubWebsite — Mapping club ↔ builder_tenant_slug, is_published, published_at. Mehr nicht.Tenant (mit external_ref = Club-UUID, is_public), Site, SiteVersion (Snapshot pro Änderung, nie destruktiv), VorlageInstance (typisierter Vorlagen-Katalog, 20+ Vorlagen), GenerationJob (async KI-Läufe), Media, ImportSource.core.root_router fragt websitebuilder.views.public_website_response: Addon aktiv + ClubWebsite.is_published → Seite wird serverseitig vom Builder geholt und unter der Club-Domain ausgeliefert (Tenant-Präfix aus Links entfernt, /static/+/media/ absolut auf builder.cluhu.ch umgeschrieben, eigene CSP). Fallback bei jedem Problem: bisheriges Login-Verhalten. Reservierte Slugs: Webseiten-Unterseiten namens clucore, admin, api, onboarding, addons, kalender … werden von cluhu-Routen überdeckt.
builder.cluhu.ch liegt NICHT in der NPM-UI: Der Proxy-Host ist eine Custom-Config in /data/nginx/custom/http.conf im nginx-proxy-manager auf 192.168.178.141 (nutzt das *.cluhu.ch-Wildcard-Zertifikat; exakter server_name schlägt das Wildcard)..env-Änderungen brauchen docker compose up -d --force-recreate web — ein blosses restart liest die Umgebung NICHT neu ein.builder.cluhu.ch/<slug>/ liefert 404, solange is_public=False — ausser man hat eine SSO-Session für genau diesen Tenant (Editor-Preview) oder ist Django-Staff./clucore/website/editor/, das serverseitig einen frischen Token mintet (ein in die Seite gerenderter Token wäre beim Klick oft abgelaufen).DEBUG=False hinter dem Proxy: Whitenoise mit USE_FINDERS (kein collectstatic), Media wird DEBUG-unabhängig über eine eigene URL-Route serviert.wb_wizard), der Generierungs-Job läuft aber im Builder weiter, selbst wenn der Browser zugeht — beim nächsten Besuch des Tabs geht es auf der Fortschrittsseite weiter.