Skip to content

Bedrijfslogica

Huidig: Beam is gratis. Geen billing, geen quota-enforcement op planniveau.

Gepland: Stripe integratie met plan tiers. Zie Backlog P2 #11. Openstaande vraag: per addon, per vertical pack, of flat-rate per tier (OQ-9).

Beam hanteert harde limieten ongeacht plan. Bij toekomstige plannen worden deze per tier configureerbaar.

ResourceLimietEnforcement
Media opslag per site500 MBcheck_media_quota() DB trigger
Bestand upload max10 MB (images/docs)API validatie
Video upload max100 MBAPI validatie
Custom domeinen per site5API validatie
Sites per owner10 (actief)check_site_quota() DB trigger (telt alleen deleted_at IS NULL)
Team uitnodiging expiry7 dagenexpires_at kolom
Pagina slug formaat^[a-z0-9]([a-z0-9-]{1,61}[a-z0-9])?$ (max 63 tekens)Client + API validatie
Query resultaat limiet200-500 rijen.limit() op alle Supabase queries
Cache purge URLsMax 30 per requestAPI validatie

Video’s worden gehost bij Bunny Stream en tellen niet mee in de 500 MB site quota. Alleen images en documenten in R2 tellen mee.

Bij quota bereikt: De check_media_quota() database trigger blokkeert de INSERT. De API retourneert een foutmelding en de dashboard toont een toast: “Opslaglimiet bereikt.” De gebruiker moet bestaande media verwijderen om ruimte vrij te maken.

CategorieLimietScopeResponse
Algemeen100 req/minPer authenticated user429 + Retry-After
Uploads20 req/minPer authenticated user429 + Retry-After
Ongeauthenticeerd30 req/minPer IP429 + Retry-After

Implementatie: In-memory sliding window per Worker isolate. Niet globaal gedeeld — voldoende voor abuse preventie. Response headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.

Beam gebruikt een rolhierarchie per team. Elke owner kan meerdere sites beheren (max 10).

RolPagina’sMediaPatternsMenusTeamSite Settings
OwnerCRUDCRUDCRUDCRUDAllesAlles
AdminCRUDCRUDCRUDCRUDInvite, rol wijzigen, verwijderenAlles
EditorCRUDCRUDCRUDCRUD
ViewerLezenLezenLezenLezen

Regels:

  • Een team heeft precies 1 owner — kan niet verwijderd of gewijzigd worden
  • Admins kunnen alle rollen wijzigen behalve owner (inclusief andere admins promoten/degraderen)
  • Editors kunnen geen team- of site-instellingen wijzigen
  • Viewers hebben alleen leesrechten

Alle data-toegang wordt afgedwongen via Supabase RLS policies die get_user_role(auth.uid(), site_id) checken. Als een user geen team_members rij heeft voor een site, falen alle CRUD operaties.

Beschermingslagen:

  1. Database triggeradd_owner_to_site_team_trigger maakt automatisch een team_members rij aan bij site creatie
  2. Runtime safety netensure_owner_team_member() SECURITY DEFINER functie, aanroepbaar via supabase.rpc(), valideert sites.owner_id = auth.uid() voordat de rij wordt aangemaakt
  3. Stale site detectieuseSites() hook auto-reset naar eerste site als opgeslagen site ID niet meer in de user’s sites lijst zit
  4. Specifieke foutmeldingen — RLS errors (42501, PGRST116) tonen “Je hebt geen toegang tot deze site” i.p.v. generiek “Opslaan mislukt”

API team endpoints gebruiken de admin Supabase client (service role) voor team_members lookups en updates, na validatie van de caller’s rechten via verifyCallerSiteRole().

Aanmaken → draft
→ Bewerken in editor (autosave naar Zustand store)
→ Opslaan → PUT /pages/:id (RLS check: editor+ in team)
→ Publiceren → status: published
→ Cache purge (fire-and-forget)
→ Publiek zichtbaar via Astro SSR

Publicatie status:

  • draft — Alleen zichtbaar voor teamleden in dashboard
  • published — Publiek zichtbaar via de Astro site. RLS laat SELECT toe zonder auth.

Slug wijziging: Bij slug wijziging worden alle interne links op andere pagina’s automatisch bijgewerkt via POST /pages/:id/cascade-hrefs. Dit doorzoekt recursief alle blocks (nested groups, columns, hero buttons).

Uitnodigen → pending (7 dagen geldig)
→ Accepteren → accepted (team_members record)
→ OF Afwijzen → declined
→ OF Verlopen → expired (automatisch)
→ OF Intrekken → revoked (door owner/admin)

Validaties bij uitnodiging:

  • Geen self-invite
  • Geen duplicaat email (al lid of pending)
  • Alleen owner/admin mag uitnodigen
  • Ghost users worden opgeruimd via delete_ghost_user() SQL functie

Upload:

  • Client berekent SHA-256 hash vóór upload
  • Duplicaat check: als hash bestaat → hergebruik bestaand item
  • SSRF check op externe URLs: blokkeert private IP ranges (127.x, 10.x, 172.16-31.x, 192.168.x, ::1)

Ondersteunde formaten:

TypeFormatenMax
AfbeeldingenJPEG, PNG, SVG, WebP, GIF10 MB
DocumentenPDF, DOCX, TXT10 MB
Video’sMP4, WebM, MOV100 MB

Verwerking bij upload:

  • Blur placeholder (LQIP): 20px via CF Image Resizing
  • Thumbnail: 400px WebP
  • Metadata opslaan (dimensions, hash, MIME type)

Cleanup: Dagelijkse cron (03:00 UTC) verwijdert orphaned media (site_id IS NULL), max 100 per run.

Regels:

  • Max 5 domeinen per site
  • Eerste domein wordt automatisch primary
  • Primary is te wijzigen via dashboard
  • Bij verwijderen van primary: volgende domein wordt promoted
  • Status polling: elke 5 minuten via cron
  • Timeout: 48 uur zonder DNS → status: error

Geblokkeerde domeinen: Vooraf gedefinieerde lijst van domeinen die niet gekoppeld kunnen worden (bijv. builtwithbeam.com subdomains).

Aanmaken → actief (deleted_at IS NULL)
→ Gebruiker bewerkt content, media, menus, settings
→ Soft delete → deleted_at = NOW()
→ Site verdwijnt uit dashboard en site switcher
→ 14 dagen grace period
→ hardDeleteExpiredSites() cron → definitief verwijderd
→ R2 bestanden opgeruimd
→ CF custom domains losgekoppeld
→ DB records verwijderd (CASCADE)

Slug auto-generatie: Bij aanmaken wordt een slug automatisch gegenereerd uit de sitenaam (lowercase, spaties → hyphens, speciale tekens verwijderd). De slug is uniek per site.

Pagina slugs: Pagina slugs zijn uniek per site (UNIQUE(site_id, slug)) — dezelfde slug kan bestaan op verschillende sites.

Patterns zijn herbruikbare block templates met optionele sync.

Propagatie regels:

  • “Update all uses” werkt alleen op synced patterns
  • Ownership check via RLS — alleen team members
  • Query limiet: 500 pagina’s per propagatie
  • Alle betroffenen pagina’s worden parallel bijgewerkt
  • Cache purge na propagatie

Usage tracking: GET /patterns/:id/usage retourneert lijst van pagina’s die het pattern gebruiken, inclusief titel en status.