Commit Graph
2 Commits
Author SHA1 Message Date
aarbitandClaude Sonnet 5 6cdfde822d Add site-wide announcements, account migration, and profile self-service
Announcements: a Super Admin (or an Admin individually flagged via
profiles.can_post_announcements) can post/retire a site-wide banner.
Account migration: a Super Admin can move a locked-out user's diagrams,
private catalog entries, and submissions to another account, with a
migration-history log; ProfileModal adds the self-service half (link a new
Google identity via Supabase manual linking, then unlink the old one,
while signed in as the account being migrated). Also reworks the top bar's
flat button row into grouped dropdown menus (Diagram / Admin / Account) now
that there are enough entries to need it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
2026-09-28 10:05:11 -05:00
aarbit 4c45b5afa7 Add Roles & Admin/Super-Admin interface
Per organized-ideas.md §6: role assignment, account ban/unban/delete, and
direct Admin/Super-Admin CRUD of public catalog entries outside the
submission workflow.

Backend:
- list_users_for_admin(): Super-Admin-gated SECURITY DEFINER function
  joining profiles + auth.users (username, email, role, banned_until) —
  auth.users isn't exposed through PostgREST, so this is the only way to
  list accounts at all.
- New Edge Function admin-user-action (ban/unban/delete), using
  @supabase/server's `auth: 'user'` mode to verify the caller's JWT, then
  Supabase Auth's Admin API for the actual mutation. This is deliberately
  an Edge Function rather than a Postgres function like everything else in
  this codebase: touching auth.users needs the Admin API, the stable
  documented interface, not a direct write to a schema Supabase manages
  internally. Self-action guard; verify_jwt = true at the gateway on top of
  the function's own JWT verification.
- 5 new pgTAP tests (43/43 total) for list_users_for_admin (Super-Admin-only,
  even regular Admins get 42501).
- CatalogRepository gains admin* methods (direct edit of a public port/cable/
  device entry, plus adminUnpublish which flips is_public rather than
  deleting) — the update methods were already ownership-agnostic (RLS's
  is_admin() clause is what actually permits it), so these are thin aliases,
  not duplicated logic.

Frontend:
- authStore/AdminUserRepository: minimal role plumbing, shared UserRole type.
- adminUserStore + AdminUsersModal: list/role-dropdown/ban/unban/delete,
  gated to Super Admin only via a new "Manage Users" TopBar button.
- PortTypeManager/CableTypeManager/DevicePalette: built-in entries now show
  direct "Edit"/"Unpublish" for Admins (regular Admin included, per §6's
  capability table — not Super-Admin-exclusive) instead of "Suggest edit";
  unpublish reuses the review-queue's impact-check RPC before confirming.
- DeviceTemplateEditor gains an `adminMode` save path alongside its existing
  submissionMode/resubmitId ones.

Verified: tsc -b and oxlint clean; supabase db reset + 43/43 pgTAP tests
pass; confirmed both new privileged endpoints (the SQL function and the
Edge Function) actually work through the real REST API via live curl
calls — signup, email confirm, role promotion, ban/unban/delete round
trips, self-action guard, non-super-admin rejection, and verify_jwt=true
compatibility all exercised directly, not just asserted.
2026-09-08 16:01:01 -05:00