feat: multi-workspace support — one login, several separate businesses #4041
Cross-repo impact
See what breaks downstream if this PR merges.
⮌ Merged
This pull request was merged into main.
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts
Originally written by @ccantynz-alt on GitHub.
Imported from https://github.com/ccantynz-alt/AlecRae.com/pull/90
Description
Fixes the reported bug: "in the admin section, I can only create one workspace account." Root cause was two-layered:
res.data[0]fromGET /v1/organizationsand permanently hid the "create organization" form once one existed.users.accountIdwas a hard 1:1 (one login = exactly one tenant/account), so even fixing the UI wouldn't let one login manage more than one separate business.Craig confirmed he wanted the real fix — genuine multi-tenant workspaces (an agency managing several client businesses under one login) — which is a data-model change, so this was scoped, planned, and built as one PR. See
CLAUDE.mdKnown Issue #63 and the Product Decisions Log for the full write-up.What changed
workspace_memberstable (packages/db/src/schema/workspace-members.ts, migration0004_legal_lady_bullseye.sqlwith a backfill for existing users) — decouples identity (users) from per-workspace role, so one identity can belong to several workspaces (accounts) with a different role in each.refresh_tokensgains anaccountIdcolumn so token rotation stays scoped to the active workspace instead of silently reverting to the identity's home account.GET/POST /v1/workspaces(list / create) andPOST /v1/auth/switch-workspace(mints a fresh token pair after verifying membership — role is never trusted from the request).organizations.tsmember/invite endpoints now operate onworkspace_membersinstead ofusers.accountId; invitation-accept no longer re-parents an existing identity into a new account (which would have destroyed their access to other workspaces) — it grants membership instead.workspace_members, with a self-healing fallback to the legacyusers.rolefor rows that predate this table.GET /v1/auth/meand theDELETE /v1/accountowner-check now reflect the active workspace, not the identity's home account.WorkspaceSwitcherin the dashboard sidebar (list / switch / create workspaces), and the Team tab bug is fixed.ai-triage/page.tsx(exactOptionalPropertyTypesviolation) that was blockingbun run buildentirely — bundled in so CI can go green.⚠️ Deployment note
Run
bun run db:migrateon the box before/during deploy — this ships a new table and column.Type of change
Testing
bun run test— 147/147 inapps/api)bunx tsc --noEmitclean inapps/api/apps/web/packages/db;bun run lintclean except pre-existing, unrelated errors indns-providers/*andagent.ts)bun run buildpasses forapps/web(69 static pages) andpackages/dbscripts/check-schema-drift.shpasses (migration fully captures the schema change)Checklist
bun run test)CLAUDE.mdKnown Issue #63 + Product Decisions Log)Breaking changes
Not breaking for existing single-workspace users — behavior is identical until someone creates or joins a second workspace. The migration backfills a
workspace_membersrow for every existing user so nothing regresses.Screenshots
N/A (backend + a new sidebar dropdown; not screenshot-tested against a live environment in this session)
Generated by Claude Code