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.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import type { CatalogEntityType, CatalogSubmission } from './SubmissionRepository'
|
||||
|
||||
/** Shared by SupabaseSubmissionRepository (submitter's-eye view) and
|
||||
* SupabaseAdminSubmissionRepository (Admin's-eye view) — both read/write
|
||||
* the same `catalog_submissions` table shape. */
|
||||
export interface SubmissionRow {
|
||||
id: string
|
||||
entity_type: CatalogEntityType
|
||||
entity_id: string
|
||||
proposed_data: Record<string, unknown>
|
||||
submitter_id: string
|
||||
status: CatalogSubmission['status']
|
||||
reviewer_id: string | null
|
||||
review_reason: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export function toSubmission(row: SubmissionRow): CatalogSubmission {
|
||||
return {
|
||||
id: row.id,
|
||||
entityType: row.entity_type,
|
||||
entityId: row.entity_id,
|
||||
proposedData: row.proposed_data,
|
||||
submitterId: row.submitter_id,
|
||||
status: row.status,
|
||||
reviewerId: row.reviewer_id ?? undefined,
|
||||
reviewReason: row.review_reason ?? undefined,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user