import { v4 as uuid } from 'uuid' import type { AdminSubmissionRepository, UsageImpact, UsageImpactSample } from './AdminSubmissionRepository' import { CATALOG_TABLE_BY_ENTITY_TYPE } from './catalogRowMapping' import type { CatalogSubmission } from './SubmissionRepository' import { supabase } from './supabaseClient' import { toSubmission, type SubmissionRow } from './submissionRowMapping' interface ProposedDeviceTemplatePort { name: string direction: string port_type_id: string } /** Backs the app with the `catalog_submissions` table (Admin's-eye view) * plus the catalog tables it approves onto and the usage-impact RPC. RLS's * `is_admin()` clauses are what actually gate every write here — this * class assumes the caller already is one. */ export class SupabaseAdminSubmissionRepository implements AdminSubmissionRepository { async listAll(): Promise { const { data, error } = await supabase .from('catalog_submissions') .select('*') .order('created_at', { ascending: false }) if (error) { console.error('Failed to load submissions from Supabase', error) return [] } return ((data ?? []) as SubmissionRow[]).map(toSubmission) } async approve(submission: CatalogSubmission): Promise { const { data: { user }, } = await supabase.auth.getUser() if (!user) throw new Error('Not signed in.') // proposed_data is already row-shaped (see data/catalogRowMapping.ts) — // approving is just writing it onto the live row, flipping it public. // `ports` (device_template only) isn't a column on device_templates // itself; pull it out and replace device_template_ports separately. const { ports, ...rowPatch } = submission.proposedData as Record & { ports?: ProposedDeviceTemplatePort[] } const table = CATALOG_TABLE_BY_ENTITY_TYPE[submission.entityType] const { error } = await supabase .from(table) .update({ ...rowPatch, is_public: true, owner_id: null }) .eq('id', submission.entityId) if (error) { console.error('Failed to approve submission (entity update) in Supabase', error) throw error } if (submission.entityType === 'device_template') { const { error: deleteError } = await supabase .from('device_template_ports') .delete() .eq('device_template_id', submission.entityId) if (deleteError) console.error('Failed to clear device template ports while approving', deleteError) if (ports && ports.length > 0) { const { error: insertError } = await supabase.from('device_template_ports').insert( ports.map((port, index) => ({ id: uuid(), device_template_id: submission.entityId, name: port.name, direction: port.direction, port_type_id: port.port_type_id, sort_order: index, })), ) if (insertError) console.error('Failed to write device template ports while approving', insertError) } } const { error: statusError } = await supabase .from('catalog_submissions') .update({ status: 'approved', reviewer_id: user.id, review_reason: null }) .eq('id', submission.id) if (statusError) console.error('Failed to mark submission approved in Supabase', statusError) } async reject(id: string, reason: string): Promise { const { data: { user }, } = await supabase.auth.getUser() if (!user) throw new Error('Not signed in.') const { error } = await supabase .from('catalog_submissions') .update({ status: 'rejected', reviewer_id: user.id, review_reason: reason }) .eq('id', id) if (error) console.error('Failed to reject submission in Supabase', error) } async getUsageImpact(entityType: CatalogSubmission['entityType'], entityId: string): Promise { const { data, error } = await supabase.rpc('catalog_entity_usage_impact', { p_entity_type: entityType, p_entity_id: entityId, }) if (error || !data || data.length === 0) { if (error) console.error('Failed to load usage impact from Supabase', error) return { diagramCount: 0, sample: [] } } const row = data[0] as { diagram_count: number; sample: UsageImpactSample[] } return { diagramCount: row.diagram_count, sample: row.sample ?? [] } } async getSubmitterUsernames(submissionIds: string[]): Promise> { if (submissionIds.length === 0) return {} const { data, error } = await supabase.rpc('catalog_submission_submitters', { p_submission_ids: submissionIds }) if (error) { console.error('Failed to load submitter usernames from Supabase', error) return {} } const result: Record = {} for (const row of (data ?? []) as Array<{ submission_id: string; username: string }>) { result[row.submission_id] = row.username } return result } }