MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-28

1.2 Workspaces (multi-tenant)

Done
Description

Each user belongs to one or more workspaces. All Motir data — projects, work items, revisions — is scoped to a workspace via foreign key. Multi-tenancy is enforced at the schema layer (Postgres Row-Level Security) and in the application layer (request-scoped middleware), so a leak requires bypassing both gates. Users invite teammates by email through a token-based acceptance flow that reuses Story 1.1's auth surface. Workspace settings include a workspace-level subtask_pr_merge_mode column consumed by Story 1.4 (work-item-model) and Epic 4 (planner agent).

Prerequisites: Story 1.1 (Auth) must be complete — workspaces FK against User, the workspace-context middleware reads the Better-Auth session, the invite flow reuses Better-Auth's Verification token table from 1.1.3 and the lib/email.ts abstraction from 1.1.6, and the invite-acceptance UI reuses Better-Auth's sign-in / sign-up flows from 1.1.5. Story 1.0.5 (Design system) must be complete before Subtask 1.2.1 (mockup) and Subtask 1.2.6 (switcher + settings + invite UI) — those surfaces compose canonical Button, Input, Card, Dialog primitives.

Verification

  • Open a terminal and run:

    git checkout story/PROD-1.2-workspaces ./scripts/db-up.sh pnpm dev

  • Open http://localhost:3000 in your browser. You should be redirected to /sign-in (no session). Sign up with a NEW email.

  • AUTO-WORKSPACE-ON-SIGNUP CHECK: After sign-up, you should land on /dashboard. Look at the top-nav: there should be a workspace switcher showing a name like "Alice's Workspace" (matching your sign-up name). Click the switcher; the dropdown should show one workspace (your auto-created one) with a check mark.

  • RENAME WORKSPACE: Click "Settings" or visit /settings/workspace. In the Name card, change the workspace name and save. The top-nav switcher should update without a hard reload.

  • INVITE FLOW: In the Members card, click "Invite teammates" — enter a second throwaway email and Send. You should see a success toast. In your terminal where pnpm dev is running, look for an [EMAIL] line containing the invite link. Open it in a private/incognito browser window. The invite link should land you on /sign-up (you're not signed in in the incognito session). Sign up with that second email; you should be redirected to /invite/accept showing "{Inviter} invited you to join {Workspace}". Click Accept; you should land on /dashboard with the invited workspace active in the switcher.

  • SWITCHER + ISOLATION SPOT-CHECK: Back in the second browser session, the switcher should show only ONE workspace (the one you accepted). You should NOT see the inviter's auto-created workspace. In the first browser session, the switcher should show TWO workspaces (your renamed original + nothing else — the second user is a member of YOUR original, not the other way around).

  • LEAVE WORKSPACE: In the second browser session, go to /settings/workspace, click Leave in the Danger zone. You should be redirected to a "create your first workspace" empty state (the auto-created workspace from sign-up was the one you just left? — no, the auto-created one was distinct; you should land in your auto-created one). The first browser session's Members card should now show only yourself.

  • DELETE WORKSPACE WITH DOUBLE-CONFIRMATION: Create a third throwaway workspace via the switcher's "Create workspace" entry (name: "Test Delete"). In Settings → Danger zone, click Delete workspace. A dialog appears asking you to type "Test Delete" to confirm. Try typing it wrong first — the destructive button stays disabled. Type it correctly; the button enables; click it. You should be redirected to your remaining workspace; the deleted one is gone from the switcher.

  • ISOLATION ATTACK SPOT-CHECK (manual): From the first browser session's URL bar, try visiting /api/workspaces/{some-other-workspace-id-you-know}/invites (find a workspace ID you're not a member of — e.g. from a fresh incognito sign-up). You should get a 404, not a 403, not a 200.

  • Confirm CI is green on the Story PR (all 4 gates plus the existing Vitest suite from 1.1 plus the new isolation tests from 1.2.7).

  • If all flows work, approve and merge the PR. If anything breaks, add a comment describing what failed and Motir will produce a follow-up Subtask under this Story to fix it.

Status
Done
Type
Story