Add manufacturer catalog, category/manufacturer library management, and built-in cable flag
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
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
-- Wires up the normalized `manufacturers` catalog (organized-ideas.md §3:
|
||||
-- "manufacturer becomes a normalized, submittable catalog — the same
|
||||
-- public/user-defined pattern already used for device categories").
|
||||
--
|
||||
-- The table itself, its RLS policies, and device_templates.manufacturer_id
|
||||
-- have all existed unused since the initial schema / the manufacturer_text
|
||||
-- migration — this is the pass that actually populates and reads them.
|
||||
-- Scope matches what device_category has today: create-your-own-private-one
|
||||
-- only, no submission/admin-edit UI yet (that's a separate, already-tracked
|
||||
-- follow-up for both entities together).
|
||||
--
|
||||
-- Any existing free-text manufacturer values (there's realistically at most
|
||||
-- a handful of dev-only rows at this point, nothing shipped) get backfilled
|
||||
-- into a manufacturers row each, grouped by (name, owner, is_public) so two
|
||||
-- different users' private "Fake" manufacturers don't collide into one row.
|
||||
|
||||
insert into public.manufacturers (name, is_public, owner_id)
|
||||
select distinct dt.manufacturer, dt.is_public, dt.owner_id
|
||||
from public.device_templates dt
|
||||
where dt.manufacturer is not null;
|
||||
|
||||
update public.device_templates dt
|
||||
set manufacturer_id = m.id
|
||||
from public.manufacturers m
|
||||
where dt.manufacturer is not null
|
||||
and m.name = dt.manufacturer
|
||||
and m.is_public = dt.is_public
|
||||
and m.owner_id is not distinct from dt.owner_id;
|
||||
|
||||
alter table public.device_templates drop column manufacturer;
|
||||
@@ -0,0 +1,61 @@
|
||||
-- Rounds device_category and manufacturer out to full parity with the
|
||||
-- other catalog entity types: submission/review workflow (already halfway
|
||||
-- there — catalog_submissions.entity_type has allowed both since the
|
||||
-- initial schema, and the app-side plumbing for device_category already
|
||||
-- exists in the review queue and My Submissions), plus direct rename of
|
||||
-- your own private entry / an already-public one by an Admin.
|
||||
--
|
||||
-- No new tables or RLS policies — device_categories/manufacturers already
|
||||
-- have full is_public/owner_id CRUD policies from the initial schema
|
||||
-- (device_categories is explicitly the "representative case" the RLS test
|
||||
-- suite already covers for this shape), just never exercised by app code
|
||||
-- for anything beyond insert. This migration only extends the one function
|
||||
-- that didn't already know about manufacturers.
|
||||
|
||||
create or replace function public.catalog_entity_usage_impact(p_entity_type text, p_entity_id text)
|
||||
returns table(diagram_count integer, sample jsonb)
|
||||
language plpgsql
|
||||
stable
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
begin
|
||||
if not public.is_admin() then
|
||||
raise exception 'insufficient_privilege' using errcode = '42501';
|
||||
end if;
|
||||
|
||||
return query
|
||||
select count(*)::int, coalesce(jsonb_agg(jsonb_build_object('id', s.id, 'name', s.name, 'ownerUsername', s.owner_username) order by s.rn) filter (where s.rn <= 5), '[]'::jsonb)
|
||||
from (
|
||||
select d.id, d.name, p.username as owner_username,
|
||||
row_number() over (order by d.updated_at desc) as rn
|
||||
from public.diagrams d
|
||||
join public.profiles p on p.id = d.owner_id
|
||||
where case p_entity_type
|
||||
when 'device_template' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'templateId' = p_entity_id
|
||||
)
|
||||
when 'device_category' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'category' = p_entity_id
|
||||
)
|
||||
when 'manufacturer' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'manufacturerId' = p_entity_id
|
||||
)
|
||||
when 'port_type' then exists (
|
||||
select 1
|
||||
from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev,
|
||||
jsonb_array_elements(coalesce(dev -> 'ports', '[]'::jsonb)) port
|
||||
where port ->> 'portTypeId' = p_entity_id
|
||||
)
|
||||
when 'cable_type' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'connections', '[]'::jsonb)) conn
|
||||
where conn ->> 'cableTypeId' = p_entity_id
|
||||
)
|
||||
else false
|
||||
end
|
||||
) s;
|
||||
end;
|
||||
$$;
|
||||
@@ -0,0 +1,76 @@
|
||||
-- Lets a regular user check the usage-impact of their OWN still-private
|
||||
-- device category or manufacturer, not just Admins — needed so the app can
|
||||
-- warn "used in N diagrams, can't delete" before a destructive delete of
|
||||
-- your own catalog entry, the same way it already warns an Admin before an
|
||||
-- unpublish. Scoped to just these two entity types and only entities you
|
||||
-- privately own (is_public = false, owner_id = you): a still-private
|
||||
-- category/manufacturer could only ever have been referenced by things you
|
||||
-- yourself created (nobody else's picker would ever have offered it), so
|
||||
-- this can't be used to snoop on another user's diagrams the way opening
|
||||
-- this up generally would.
|
||||
create or replace function public.catalog_entity_usage_impact(p_entity_type text, p_entity_id text)
|
||||
returns table(diagram_count integer, sample jsonb)
|
||||
language plpgsql
|
||||
stable
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
begin
|
||||
if not (
|
||||
public.is_admin()
|
||||
or (
|
||||
p_entity_type = 'device_category'
|
||||
and exists (
|
||||
select 1 from public.device_categories
|
||||
where id = p_entity_id and owner_id = auth.uid() and not is_public
|
||||
)
|
||||
)
|
||||
or (
|
||||
p_entity_type = 'manufacturer'
|
||||
and exists (
|
||||
-- manufacturers.id is still uuid (never migrated to text like the
|
||||
-- other catalog tables — see 20260908033553_catalog_text_ids.sql's
|
||||
-- own comment on why), hence the cast.
|
||||
select 1 from public.manufacturers
|
||||
where id::text = p_entity_id and owner_id = auth.uid() and not is_public
|
||||
)
|
||||
)
|
||||
) then
|
||||
raise exception 'insufficient_privilege' using errcode = '42501';
|
||||
end if;
|
||||
|
||||
return query
|
||||
select count(*)::int, coalesce(jsonb_agg(jsonb_build_object('id', s.id, 'name', s.name, 'ownerUsername', s.owner_username) order by s.rn) filter (where s.rn <= 5), '[]'::jsonb)
|
||||
from (
|
||||
select d.id, d.name, p.username as owner_username,
|
||||
row_number() over (order by d.updated_at desc) as rn
|
||||
from public.diagrams d
|
||||
join public.profiles p on p.id = d.owner_id
|
||||
where case p_entity_type
|
||||
when 'device_template' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'templateId' = p_entity_id
|
||||
)
|
||||
when 'device_category' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'category' = p_entity_id
|
||||
)
|
||||
when 'manufacturer' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev
|
||||
where dev ->> 'manufacturerId' = p_entity_id
|
||||
)
|
||||
when 'port_type' then exists (
|
||||
select 1
|
||||
from jsonb_array_elements(coalesce(d.data -> 'devices', '[]'::jsonb)) dev,
|
||||
jsonb_array_elements(coalesce(dev -> 'ports', '[]'::jsonb)) port
|
||||
where port ->> 'portTypeId' = p_entity_id
|
||||
)
|
||||
when 'cable_type' then exists (
|
||||
select 1 from jsonb_array_elements(coalesce(d.data -> 'connections', '[]'::jsonb)) conn
|
||||
where conn ->> 'cableTypeId' = p_entity_id
|
||||
)
|
||||
else false
|
||||
end
|
||||
) s;
|
||||
end;
|
||||
$$;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Devices with a captive/permanently-attached cable (a keyboard's USB
|
||||
-- cable, a budget AV receiver's power cord, some passive speakers' fixed
|
||||
-- speaker wire, ...) — a property of the physical port, not of any one
|
||||
-- wiring choice, so it lives on device_template_ports rather than on
|
||||
-- connections (which aren't even a relational table — see diagrams' JSONB
|
||||
-- `data` for those; nothing to migrate there, the domain type change alone
|
||||
-- covers newly-placed devices, and this column is what the palette/library
|
||||
-- side needs).
|
||||
alter table public.device_template_ports add column built_in_cable boolean not null default false;
|
||||
Reference in New Issue
Block a user