Ga naar inhoud

Hoe het samenhangt

Deze pagina zet de onderdelen uit Begrippen in beweging: wie praat met wie, en in welke volgorde. De details staan in Architectuur; dit is het overzicht dat je nodig hebt om die te kunnen lezen.

┌─────────────────────────────────┐
│ Master dashboard (Nuxt) │
│ klanten · provisioning · │
│ domeinen · activiteit │
└──────┬──────────────┬────────────┘
Coolify API │ │ Cloudflare API
(app aanmaken, env, │ │ (klantdomeinen + TLS)
starten) ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ VPS bij Hetzner, beheerd met Coolify, Traefik ervoor │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ site: acme │ │ site: rb │ │ site: … │ Astro SSR, │
│ │ TENANT_ID │ │ TENANT_ID │ │ │ één │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ container │
│ │ │ │ per klant │
│ ┌─────▼───────────────▼───────────────▼──────┐ │
│ │ Postgres: één database per klant + backups │ │
│ └────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ media (afbeeldingen)
Cloudflare R2, replica op Backblaze B2

Drie bewegende delen:

  1. De klantsite (apps/site-template): één Astro-app die publieke site én CMS is. Per klant draait er een eigen exemplaar, met eigen omgevingsvariabelen (TENANT_ID, DATABASE_URL, SITE_DOMAIN en geheimen).
  2. Het master dashboard (apps/master-dashboard): één Nuxt-app voor de platformbeheerder, met een eigen master-database.
  3. De gedeelde packages (packages/*): contract (types en schema’s), db (databaseschema en migraties), blocks (de blokkenbibliotheek) en ops (beheerscripts). Beide apps bouwen hierop.

De pijlen lopen bewust één kant op: het dashboard stuurt Coolify en Cloudflare aan, en de sites melden zich bij het dashboard met webhooks. Het dashboard kijkt nooit rechtstreeks in een klantdatabase.

  1. Registreren. De platformbeheerder maakt in het dashboard een klant aan: naam en slug. Er ontstaat een rij in de master-database met status provisioning.
  2. Provisioneren. Eén knop start zes stappen, in vaste volgorde: database en databasegebruiker aanmaken, migraties uitvoeren, geheimen genereren en versleuteld opslaan, Coolify-app aanmaken, omgevingsvariabelen zetten, starten. Elke stap slaat over wat al bestaat, dus na een fout kun je gewoon opnieuw klikken.
  3. Preview-adres. De site is bereikbaar op <slug>.preview.okhema.studio, het CMS op admin.<slug>.preview.okhema.studio. Het wildcard-certificaat daarvoor is eenmalig ingericht.
  4. Content en gebruiker. Met de beheerscripts uit packages/ops krijgt de site zijn eerste pagina’s en navigatie (uit een JSON-bestand in tenants/) en een CMS-gebruiker met een eenmalig wachtwoord. Dit is nog handwerk; automatisering staat op de roadmap.
  5. De klant beheert. De klant logt in op het CMS en past teksten, blokken, media en instellingen aan. Elke wijziging wordt gevalideerd, opgeslagen, in het auditlog gezet en is direct zichtbaar op de publieke site.
  6. Het dashboard luistert. De site stuurt bij elke relevante gebeurtenis een ondertekende webhook naar het dashboard. Blijft een site 24 uur stil, dan toont het dashboard dat als aandachtspunt.
  7. Eigen domein. Wil de klant zijn eigen domein, dan registreert de beheerder de hostname in het dashboard, zet de klant een CNAME-record, en regelt Cloudflare het certificaat. Het dashboard toont de status tot die Actief is.
  8. Updates. Een verbetering aan het template is één git-push; Coolify bouwt en herstart de sites van alle klanten. Klantverschillen zitten in gegevens, nooit in code, dus dit blijft veilig.
  1. De bezoeker vraagt https://klant.nl/diensten op. Traefik kijkt naar de hostname en stuurt het verzoek naar de container van die klant.
  2. Astro’s route-cache kijkt of deze pagina recent is opgebouwd. Bij een treffer gaat de opgeslagen HTML meteen terug; de database wordt niet geraakt.
  3. Anders draait de middleware: is dit de publieke host of de CMS-host? Op de publieke host hoeft niemand ingelogd te zijn; het verzoek gaat door.
  4. De pagina laadt de site-rij (naam, huisstijl, navigatie) en de gevraagde content uit de tenant-database.
  5. De huisstijl wordt omgezet naar CSS-variabelen op het <html>-element. Alle componenten verwijzen naar die variabelen, dus de juiste kleuren en lettertypes volgen vanzelf.
  6. De blokken van de pagina worden één voor één gevalideerd en door de bijbehorende renderer omgezet naar HTML. Een onbekend of kapot blok wordt overgeslagen (en in ontwikkelmodus zichtbaar gemarkeerd).
  7. De HTML gaat terug naar de bezoeker en blijft vijf minuten in de cache, met een uur “verouderd mag nog even” als vangnet.

Wat er gebeurt als een redacteur publiceert

Section titled “Wat er gebeurt als een redacteur publiceert”
  1. De redacteur klikt op opslaan in het CMS (admin.klant.nl). Het formulier roept een server-actie aan.
  2. De actie controleert de sessie en de rol, en valideert de invoer met het Zod-schema uit het contract. Ongeldige blokken komen de database niet in.
  3. De wijziging wordt opgeslagen, samen met een regel in het auditlog (wie, wat, wanneer).
  4. De actie purge’t de cache van precies de pagina’s die deze wijziging raken. De volgende bezoeker ziet de nieuwe versie.
  5. De actie zet een gebeurtenis (content.published) klaar en probeert die als webhook naar het dashboard te sturen. Mislukt dat, dan blijft de gebeurtenis in de outbox en volgt een nieuwe poging met oplopende wachttijd, tot een etmaal lang.

De redacteur wacht nooit op het dashboard, en een bezoeker merkt van dit alles niets.

  • Isolatie boven gedeelde tabellen. Een eigen database en een eigen container per klant maakt een datalek tussen klanten structureel onmogelijk en houdt de kosten voorspelbaar.
  • Gegevens boven code voor alles wat per klant verschilt. Daardoor blijft “één update voor iedereen” mogelijk.
  • Duwen boven trekken. Sites melden zich bij het dashboard, zodat het dashboard geen toegang tot klantdatabases nodig heeft.
  • Eén afspraak over types in @platform/contract, zodat dashboard en sites nooit een andere betekenis aan hetzelfde veld geven.