7fbe53f025cab0804d17b03b623113c9d3d6eccd
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
4b757e89f5 |
Prevent sharing a diagram with yourself
RLS is the real guard (diagram_collaborators_insert/update now reject user_id = the diagram's owner, regardless of who's performing the write — covers a Super Admin acting on someone else's diagram too, not just the normal owner path); the client-side check in SupabaseDiagramCollaboratorRepository.add is just there to surface a friendly message instead of the raw 42501. Verified: tsc -b and oxlint clean; supabase db reset + 53/53 pgTAP tests pass (1 new test). |
||
|
|
dea26f7ee8 |
Add diagram sharing/collaborators, version history, and view-only lockdown
Per organized-ideas.md §8. Backend tables/RLS (diagrams, diagram_collaborators, diagram_snapshots) already existed from an earlier phase — this is the frontend for them, plus two small backend additions. Backend (supabase/migrations/20260913000000_diagram_sharing.sql): - find_user_id_by_username(text): lets any authenticated user resolve a username to an id for "share with @username" — unlike general profile browsing (blocked by profiles_select_self_or_super_admin), a username is meant to be a shareable handle, so this is deliberately not gated. - diagram_collaborator_usernames / diagram_snapshot_saved_by_usernames: same pattern as the admin-review-queue phase's submitter-username lookup — batched per diagram, gated to "can you see this diagram at all" (reusing diagrams_select's own helper functions). - prune_diagram_snapshots trigger: keeps the 50 most recent snapshots per diagram, enforced at write time rather than a scheduled job (diagram_ snapshots has no update/delete policy for regular users at all). - 14 new pgTAP tests (52/52 total). Frontend: - DiagramCollaboratorRepository/store + DiagramSharingModal: add/remove collaborators by username, per-person view/edit permission, owner-only controls. - DiagramSnapshotRepository/store + VersionHistoryModal (its own top-bar button, not nested under Share — moved there after review): periodic checkpoints (one per 5 min of active editing) written as a side effect of normal saves, list + restore. - Restore's duplicate-snapshot problem: repeatedly jumping between old versions without editing in between was writing a near-duplicate safety snapshot on every jump. Fixed by having projectStore track which snapshot the diagram was last restored from and its updatedAt at that moment (touch() always advances updatedAt on a genuine edit) — a restore skips the safety snapshot when nothing has changed since the last one, and the tracking clears on any real edit so in-progress work stays protected. - DiagramRepository gains getAccess() (owner id + your own permission for the open diagram) — surfaced in projectStore as `access`. - View-only enforcement: FlowCanvas disables drag/connect/drop (nodesDraggable/nodesConnectable + guarded handlers), DeviceInspector/ ConnectionInspector wrap their controls in a disabled <fieldset>, DevicePalette disables adding devices to the canvas, TopBar disables the rename field, and a ViewOnlyBanner makes the restriction visible instead of leaving a collaborator to discover it as controls that just don't work. Autosave itself also refuses to write for a view-only user, as a backstop behind the UI-level lockdown. Verified: tsc -b and oxlint clean; supabase db reset + 52/52 pgTAP tests pass; confirmed find_user_id_by_username works through the real REST API via a live curl call (signup, confirm, resolve). Manually tested two- account sharing (view vs. edit), restoring history, and the duplicate- snapshot fix. |
||
|
|
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. |
||
|
|
1f8d49345e |
Add the Admin review queue
Lets an Admin/Super-Admin review pending catalog submissions and approve (in place, same id) or reject (with a required reason) them, per organized-ideas.md §3/§9. Backend (supabase/migrations/20260910010000_admin_review_queue.sql): - Per-user pending-submission cap (10), enforced in catalog_submissions' insert policy rather than trusted to the client. - catalog_entity_usage_impact(entity_type, entity_id): a SECURITY DEFINER, admin-gated aggregate function answering "how many diagrams reference this, and a short sample" by scanning diagrams.data JSONB — never raw diagram content, and available to regular Admins even though they don't otherwise have diagram visibility (only Super Admins do, per §6). - catalog_submission_submitters(ids[]): same admin-gated pattern, batched, so the queue can show who submitted something without opening general profile browsing to regular Admins. - Follow-up migration: a rejected submission had no way out (the delete policy only allowed withdrawing 'pending') — extended to allow 'rejected' too, so a submitter can dismiss one they don't intend to revise. - 12 new pgTAP tests (38/38 total) covering the cap, both privileged functions (including the non-admin-gets-rejected case), and withdrawing pending vs. rejected submissions. Frontend: - authStore: minimal role awareness, replacing TopBar's local username fetch, used to gate the Review Queue UI. - AdminSubmissionRepository/SupabaseAdminSubmissionRepository + adminReviewStore: list all submissions, approve/reject, usage impact, submitter usernames. - AdminReviewModal: per-submission diff view (current vs. proposed, both row-shaped via the existing catalog<->row mappers), a duplicate-detection nudge (Levenshtein distance against existing public device names) for new device submissions, and an inline impact-check for edits to already-public entries before approving. - TopBar: role-gated "Review Queue" button with a pending-count badge; "My Submissions" gets an unseen-outcome badge (localStorage-tracked, like the existing hidden-template preference) so a submitter notices a decision without having to keep reopening the modal. - Deliberately deferred: the site-wide announcement banner (its own follow-up, per discussion) and the Admin/Super-Admin role-assignment UI (§9's later phase — becoming an Admin locally still means setting profiles.role via SQL/Studio). Verified: tsc -b and oxlint clean; supabase db reset + 38/38 pgTAP tests pass; confirmed the two new RPC functions are actually reachable through PostgREST (not just raw SQL) via a live curl call; manually tested submit -> review -> approve/reject -> (for rejected) dismiss end to end. |
||
|
|
8cea3f8b92 |
Add local Supabase backend foundation: schema, RLS, and RLS tests
- supabase/config.toml: local dev stack config, pinned to the app's fixed dev server port, email confirmation required (hard verification gate per organized-ideas.md). - Initial schema migration: profiles/roles, the public/private catalog tables (manufacturers, device categories, port types, cable types, device templates + ports) with the shared is_public/owner_id RLS pattern, a generalized catalog_submissions review-queue table, and diagrams as JSONB documents (+ collaborators, snapshots) rather than fully normalized -- see the migration's header comment for why. - pgTAP RLS test suite (23 assertions) covering catalog visibility and promotion-in-place, diagram owner/collaborator/admin/super-admin visibility and edit permissions, submission visibility, and role escalation. Caught and fixed a real infinite-recursion bug between the diagrams and diagram_collaborators policies before this ever touched real data. - vite.config.ts: pinned dev server port so Supabase Auth's redirect allow-list doesn't silently break if Vite floats to another port. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x |