mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
PR 3 of the multi-user accounts series. Lands the server-side endpoints the upcoming Members tab UI will call. Built on the helpers from #169 (`requireRole`, `getCurrentAccount`, `toErrorResponse`). API surface - GET /api/account — caller's account + role - PATCH /api/account — rename (admin+) - GET /api/account/members — roster; email visible to admin+ only - PATCH /api/account/members/[userId] — change role (admin+) - DELETE /api/account/members/[userId] — remove (admin+) - POST /api/account/transfer-ownership — atomic owner handoff (owner) Member-role writes can't ride on the regular `profiles_update` RLS policy (which only allows self-edits — admins can't modify a teammate's row that way). The three SECURITY DEFINER RPCs in the new migration are the scoped escape hatches: - `set_member_role(p_user_id, p_new_role)` — admin+ only. Blocks promotion to / demotion from owner (use transfer instead) and self-targeting. - `remove_account_member(p_user_id)` — admin+ only. Creates a fresh personal account for the removed user and reassigns their profile as its 'owner' — mirror of the signup flow, so they keep their login and just "start over". Blocks removing the owner or self. - `transfer_account_ownership(p_new_owner_user_id)` — owner only. Atomically demotes current owner → admin, promotes target → owner, and points accounts.owner_user_id at the new owner. Demote-first ordering means the temporary "zero owners" state is never visible. Error contract - Each RPC raises with SQLSTATE 42501 (forbidden) or 22023 (bad input) and a human-readable RAISE message. The route handlers map these onto 403 / 400 respectively; anything else collapses to a generic 500. - The TS guards (e.g. `requireRole('owner')` on the transfer route, `isAccountRole(role)` on the PATCH route) are belt-and-braces — they fail fast before the Supabase round trip but the RPCs would catch the same violations regardless. Field visibility - `GET /api/account/members` returns `email` only when the caller is admin+. Agents and viewers see name + avatar + role + joined date — matches the planning decision so a viewer can't harvest the team's email list. Verification - `npm run lint` → 0 errors (same 20 pre-existing warnings) - `npm run typecheck` → clean - `npm test` → 366/366 pass - `npm run build` → succeeds; the 4 new routes register as ƒ (dynamic — they all use `next/headers` cookies via the SSR client) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| migrations | ||