Go to file
Boban Blaskovic 555101c993 Step 1/5: Workspace multi-tenancy foundation
User accepted 5-step plan: Workspace → Sub-Sidebar → Switcher → Plan/
Quota → Billing.

This step adds the workspace layer underneath everything:
- Workspace model with HasUuids + relations (sites/servers/clusters/
  backups/members/invitations/activities) + plan-based quotas + feature
  flags + isOverQuota() + hasFeature() helpers.
- Plan presets: free/starter/pro/agency/enterprise with sites,
  servers, team, storage_gb, features map (audit/monitoring/
  email_relay/multi_workspace/white_label/reports/sso).
- BelongsToWorkspace trait: global scope filters by app('current
  Workspace'), creating-hook auto-fills workspace_id from container.
  Falls back to no-scope when no workspace bound (tests, console).
- Migrations: workspaces table + workspace_id foreignUuid added to
  all 10 domain tables (clusters/servers/sites/backups/team_members/
  invitations/security_events/certificates/activities/crons).
- users gets current_workspace_id nullable FK.
- team_members unique changed to composite (workspace_id, user_id) so
  one user can belong to multiple workspaces.
- ResolveCurrentWorkspace middleware: resolves active workspace from
  ?workspace=slug → session → user.current_workspace_id → first owned.
  Wired into web middleware group via bootstrap/app.php.
- WorkspaceFactory + workspace_id added to all 10 domain factories
  so tests/seed work without explicit workspace context.
- DatabaseSeeder rewritten: creates Marie Weber as owner of 'Acme
  Agency' (Pro plan) workspace + 'Marie · Personal' (Free) workspace
  for switcher demo. Binds app('currentWorkspace') so seeded sites/
  servers/backups/etc. land in the right workspace.
- Sidebar component reads auth user's current workspace for the ws-
  switch panel — no more hard-coded 'Acme Cluster' string.

Full Pest regression: 47/47 green. 2 workspaces seeded:
- Acme Agency (pro): 87 sites, 6 servers, 128 sec events, 102 backups
- Marie · Personal (free): 0 sites
2026-06-01 01:12:00 +02:00
app Step 1/5: Workspace multi-tenancy foundation 2026-06-01 01:12:00 +02:00
docker Skip composer scripts during build; defer package:discover to entrypoint 2026-05-31 18:05:05 +02:00
templates Foundation + Login Glass page from template 2026-05-31 22:18:51 +02:00
.dockerignore Bake app into multi-stage image; drop bind mounts for Portainer remote endpoint 2026-05-31 17:44:35 +02:00
.gitignore nginx: extra_hosts host-gateway for vite HMR upstream; gitignore root .env 2026-05-31 21:01:28 +02:00
Dockerfile Skip composer scripts during build; defer package:discover to entrypoint 2026-05-31 18:05:05 +02:00
docker-compose.override.yml Move dev bind-mounts to docker-compose.override.yml (keep Portainer stack clean) 2026-05-31 23:37:04 +02:00
docker-compose.portainer.yml Add image-only compose for Portainer Repository deploys (no buildkit) 2026-05-31 21:40:46 +02:00
docker-compose.yml Move dev bind-mounts to docker-compose.override.yml (keep Portainer stack clean) 2026-05-31 23:37:04 +02:00
stack.env.example Version-pin images: APP_VERSION for own builds, MARIADB/REDIS for services 2026-05-31 18:17:09 +02:00