projax.items_unified joins projax.items (deleted_at IS NULL) with mai.projects so a single query feeds the tree UI. mai.projects.id is a text key, so a deterministic placeholder UUID is derived from md5(p.id); projax-native rows keep their gen_random_uuid(). When a projax item is created with an item_links row pointing back to a mai.projects id (ref_type='mai-project'), the corresponding mai.projects row drops out of the view — that's how the "Promote to projax" flow makes the duplicate disappear without ever touching mai.projects. Test coverage: - both sources appear in the view - promotion link hides the mai source row and surfaces the projax row
72 lines
2.7 KiB
SQL
72 lines
2.7 KiB
SQL
-- 0004_items_unified.sql
|
|
-- Read-only adapter view: union projax-native items with mai.projects rows
|
|
-- so a single query feeds the tree UI. See docs/design.md §3.2.
|
|
--
|
|
-- mai.projects.id is a text key (e.g. 'dotfiles', 'racetrack'). We derive
|
|
-- a deterministic placeholder UUID by hashing it with md5 and slotting
|
|
-- the digest into UUID layout. The literal '00000000-0000-0000-0000-' prefix
|
|
-- distinguishes mai.projects-sourced rows at a glance; projax-native UUIDs
|
|
-- never start with that prefix because they come from gen_random_uuid().
|
|
|
|
create or replace view projax.items_unified as
|
|
select
|
|
i.id,
|
|
i.kind,
|
|
i.title,
|
|
i.slug,
|
|
i.path,
|
|
i.parent_id,
|
|
i.content_md,
|
|
i.aliases,
|
|
i.metadata,
|
|
i.status,
|
|
i.pinned,
|
|
i.archived,
|
|
i.start_time,
|
|
i.end_time,
|
|
'projax'::text as source,
|
|
null::text as source_ref_id,
|
|
i.created_at,
|
|
i.updated_at
|
|
from projax.items i
|
|
where i.deleted_at is null
|
|
|
|
union all
|
|
|
|
select
|
|
('00000000-0000-0000-0000-' || substr(md5(p.id), 1, 12))::uuid as id,
|
|
array['project']::text[] as kind,
|
|
p.name as title,
|
|
p.id as slug,
|
|
('mai.' || p.id) as path,
|
|
null::uuid as parent_id,
|
|
coalesce(p.goal, '') as content_md,
|
|
'{}'::text[] as aliases,
|
|
coalesce(p.metadata, '{}'::jsonb) as metadata,
|
|
case p.status
|
|
when 'active' then 'active'
|
|
when 'sleeping' then 'archived'
|
|
when 'archived' then 'archived'
|
|
when 'done' then 'done'
|
|
else 'active'
|
|
end as status,
|
|
false as pinned,
|
|
(p.status = 'archived') as archived,
|
|
null::timestamptz as start_time,
|
|
null::timestamptz as end_time,
|
|
'mai.projects'::text as source,
|
|
p.id as source_ref_id,
|
|
p.created_at,
|
|
p.updated_at
|
|
from mai.projects p
|
|
where not exists (
|
|
-- Hide mai.projects rows that have been promoted into projax (a projax
|
|
-- item carries an item_links row pointing back to the mai.projects id).
|
|
select 1
|
|
from projax.item_links l
|
|
where l.ref_type = 'mai-project'
|
|
and l.ref_id = p.id
|
|
);
|
|
|
|
grant select on projax.items_unified to postgres;
|