Normalizes manufacturer as a shared catalog entity (like device categories) instead of free text on each device, giving the admin duplicate-detection nudge a reliable signal. Adds full CRUD (including Admin direct-publish, bypassing the submission queue) for categories, manufacturers, port types, and cable types, plus a Categories & Manufacturers library modal and a browse-by-manufacturer/search view in the device palette. Adds Port.builtInCable so a captive/permanently-attached cable (a keyboard's USB lead, a budget AVR's power cord) can be flagged and excluded from the BOM. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DUU6CnxECCDeqDNYJgr5x
97 lines
5.7 KiB
TypeScript
97 lines
5.7 KiB
TypeScript
import type { CatalogEntityType } from './SubmissionRepository'
|
|
import type { Catalog, CableType, DeviceCategoryDef, DeviceTemplate, Manufacturer, PortType } from '../domain/types'
|
|
|
|
/**
|
|
* Storage abstraction for the global port/cable/category/device-template
|
|
* catalog — mirrors DiagramRepository's role, but for catalog data instead
|
|
* of diagrams. Fetching returns everything the current user can see (public
|
|
* entries plus their own private ones); RLS does that filtering server-side,
|
|
* so implementations don't need to filter client-side.
|
|
*
|
|
* The `add*`/`update*` methods always create or edit an `is_public = false`
|
|
* row you own — that's the regular-user "own private catalog" surface.
|
|
* Getting an entry into the public catalog otherwise goes through the
|
|
* submission/review workflow (organized-ideas.md §3), except for the
|
|
* `admin*` methods below: per §6's capability table, an Admin/Super-Admin
|
|
* can also CRUD public entries directly, without a submission — those
|
|
* bypass ownership entirely (RLS's `is_admin()` clause is what actually
|
|
* allows it) and only ever touch already-public rows.
|
|
*/
|
|
export interface CatalogRepository {
|
|
/** Everything visible to the current user: public entries plus their own private ones. */
|
|
load(): Promise<Catalog>
|
|
|
|
addDeviceCategory(name: string): Promise<DeviceCategoryDef>
|
|
updateDeviceCategory(id: string, name: string): Promise<void>
|
|
/** Throws (rather than swallowing, like most methods here) if the delete
|
|
* is rejected — most commonly a foreign-key violation because one of
|
|
* your device templates still references this category, which the
|
|
* caller needs to know about to tell you why nothing happened. */
|
|
removeDeviceCategory(id: string): Promise<void>
|
|
|
|
addManufacturer(name: string): Promise<Manufacturer>
|
|
updateManufacturer(id: string, name: string): Promise<void>
|
|
/** Same throw-on-failure contract as removeDeviceCategory. */
|
|
removeManufacturer(id: string): Promise<void>
|
|
|
|
addPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType>
|
|
updatePortType(id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>): Promise<void>
|
|
/** Same throw-on-failure contract as removeDeviceCategory — most commonly
|
|
* a foreign-key violation because some device template's port still
|
|
* references it. */
|
|
removePortType(id: string): Promise<void>
|
|
|
|
addCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType>
|
|
updateCableType(id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>): Promise<void>
|
|
removeCableType(id: string): Promise<void>
|
|
|
|
addDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate>
|
|
updateDeviceTemplate(id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>): Promise<void>
|
|
removeDeviceTemplate(id: string): Promise<void>
|
|
|
|
/** Admin/Super-Admin direct-to-public creation — skips the submission/
|
|
* review workflow entirely, for authoring the shared library yourself
|
|
* (e.g. building out the initial device catalog) rather than reviewing
|
|
* someone else's proposal. Same RLS branch that already allows an Admin
|
|
* to directly edit a public entry (`is_admin() and is_public`) also
|
|
* covers inserting one — unlike adminUpdate* below, this is a genuinely
|
|
* different write, not just an alias (is_public: true, owner_id: null
|
|
* from the start). */
|
|
adminAddDeviceCategory(name: string): Promise<DeviceCategoryDef>
|
|
adminAddManufacturer(name: string): Promise<Manufacturer>
|
|
adminAddPortType(portType: Omit<PortType, 'id' | 'custom'>): Promise<PortType>
|
|
adminAddCableType(cableType: Omit<CableType, 'id' | 'custom'>): Promise<CableType>
|
|
adminAddDeviceTemplate(template: Omit<DeviceTemplate, 'id' | 'custom'>): Promise<DeviceTemplate>
|
|
|
|
/** Admin/Super-Admin direct edit of an already-public port/cable/device
|
|
* entry — same underlying write as approving a submission, minus the
|
|
* submission. */
|
|
adminUpdatePortType(id: string, patch: Partial<Omit<PortType, 'id' | 'custom'>>): Promise<void>
|
|
adminUpdateCableType(id: string, patch: Partial<Omit<CableType, 'id' | 'custom'>>): Promise<void>
|
|
adminUpdateDeviceTemplate(id: string, patch: Partial<Omit<DeviceTemplate, 'id' | 'custom'>>): Promise<void>
|
|
adminUpdateDeviceCategory(id: string, name: string): Promise<void>
|
|
adminUpdateManufacturer(id: string, name: string): Promise<void>
|
|
/** Unpublishes a public entry (is_public -> false) rather than deleting
|
|
* it — organized-ideas.md §3's "public catalog entries are never hard-
|
|
* deleted, only hidden/unpublished". Existing diagrams that reference it
|
|
* by id are unaffected; it just stops being offered for new use. */
|
|
adminUnpublish(entityType: CatalogEntityType, id: string): Promise<void>
|
|
|
|
/** Admin/Super-Admin permanent delete of a built-in (public) entry —
|
|
* narrower than adminUnpublish: the caller is expected to have already
|
|
* verified zero usage (no diagram references it, nothing else in the
|
|
* catalog does either) before calling this, same gate already applied to
|
|
* a regular user deleting their own private entry. Doesn't relax
|
|
* organized-ideas.md §3's "never break an existing diagram" principle —
|
|
* it only ever removes something genuinely unused (e.g. a mistake made
|
|
* while authoring the library), which is why this exists alongside
|
|
* adminUnpublish rather than instead of it. Same RLS branch as the other
|
|
* admin* methods (`is_admin()`), so these are aliases of the plain
|
|
* remove* methods above, not separate logic. Throws on failure. */
|
|
adminRemoveDeviceCategory(id: string): Promise<void>
|
|
adminRemoveManufacturer(id: string): Promise<void>
|
|
adminRemovePortType(id: string): Promise<void>
|
|
adminRemoveCableType(id: string): Promise<void>
|
|
adminRemoveDeviceTemplate(id: string): Promise<void>
|
|
}
|