Files
projax/db/migrations/0004_items_unified.sql
mAi c0466ade36 feat(db): items_unified adapter view + promotion hiding
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
2026-05-15 13:17:51 +02:00

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;