import type { CatalogEntityType } from './SubmissionRepository' import type { CableType, DeviceTemplate, PortType } from '../domain/types' /** * Maps domain objects to the shape of their Supabase table row (snake_case * columns, no id/is_public/owner_id). Shared by two call sites that both * need a row-shaped payload: SupabaseCatalogRepository's inserts, and * catalog submissions' `proposed_data` (see SubmissionRepository) — a * submission's proposed_data is deliberately row-shaped so approving it is * just writing that object onto the row at `entity_id`, not a translation * step an Admin's approval action would otherwise have to duplicate. */ /** Which table backs each catalog entity type — shared by * SupabaseAdminSubmissionRepository (approving into the right table) and * SupabaseCatalogRepository (direct Admin unpublish). */ export const CATALOG_TABLE_BY_ENTITY_TYPE: Record = { device_template: 'device_templates', port_type: 'port_types', cable_type: 'cable_types', device_category: 'device_categories', } export function portTypeToRow(portType: Omit): Record { return { name: portType.name, category: portType.category, family: portType.family, compatible_family_ids: portType.compatibleFamilyIds ?? [], max_connections: portType.maxConnections ?? null, } } export function cableTypeToRow(cableType: Omit): Record { return { name: cableType.name, family: cableType.family, family2: cableType.family2 ?? null, unit: cableType.unit, cost_per_unit: cableType.costPerUnit ?? null, } } export function deviceCategoryToRow(name: string): Record { return { name } } export function deviceTemplateToRow(template: Omit): Record { return { name: template.name, category_id: template.category, manufacturer: template.manufacturer ?? null, model: template.model ?? null, cost: template.cost ?? null, ports: template.ports.map((port, index) => ({ name: port.name, direction: port.direction, port_type_id: port.portTypeId, sort_order: index, })), } } /** * The reverse direction: turns a `catalog_submissions.proposed_data` row * back into domain-shaped fields, so a rejected/pending submission can be * reopened in the same form component that created it (see MySubmissionsModal) * instead of needing a bespoke "edit a raw JSON blob" UI. Safe because * proposed_data is always a *complete* row projection (every field of the * type, per the `*ToRow` functions above), never a partial patch. */ export function rowToPortTypeFields(row: Record): Omit { const compatibleFamilyIds = row.compatible_family_ids as string[] | undefined return { name: row.name as string, category: row.category as PortType['category'], family: row.family as string, compatibleFamilyIds: compatibleFamilyIds && compatibleFamilyIds.length > 0 ? compatibleFamilyIds : undefined, maxConnections: (row.max_connections as number | null) ?? undefined, } } export function rowToCableTypeFields(row: Record): Omit { return { name: row.name as string, family: row.family as string, family2: (row.family2 as string | null) ?? undefined, unit: row.unit as CableType['unit'], costPerUnit: (row.cost_per_unit as number | null) ?? undefined, } } export function rowToDeviceTemplateFields(row: Record): Omit { const ports = (row.ports as Array<{ name: string; direction: DeviceTemplate['ports'][number]['direction']; port_type_id: string }>) ?? [] return { name: row.name as string, category: row.category_id as string, manufacturer: (row.manufacturer as string | null) ?? undefined, model: (row.model as string | null) ?? undefined, cost: (row.cost as number | null) ?? undefined, ports: ports.map((port, index) => ({ id: `draft-port-${index}`, name: port.name, direction: port.direction, portTypeId: port.port_type_id, })), } } export function rowToDeviceCategoryName(row: Record): string { return row.name as string }