Files
projax/db/migrations/0001_init.sql
mAi b8d3418876 feat(db): projax schema, path trigger, seed areas
- 0001_init.sql: projax.items + projax.item_links tables with indices,
  partial-unique root slug, updated_at trigger, schema grants to the
  application role.
- 0002_path_trigger.sql: BEFORE-write trigger maintains items.path via
  recursive parent walk; rejects cycles and structural-rule violations
  (areas at root, projects not at root). AFTER trigger rewrites
  descendant paths on slug rename or re-parent.
- 0003_seed_areas.sql: dev, sports, home, work, health, finances, social.
- db/migrate.go: embed.FS-backed sequential runner.
- db/migrate_test.go: integration suite covering idempotency, nest,
  rename propagation, re-parent propagation, cycle rejection, and
  structural rules. Skips when no DB env var is set.

Also ignores .m/events.log and .m/locks (per-worker scratch).
2026-05-15 13:16:24 +02:00

82 lines
3.4 KiB
PL/PgSQL

-- 0001_init.sql
-- projax schema: items + item_links
-- See docs/design.md §3
create schema if not exists projax;
create table if not exists projax.items (
id uuid primary key default gen_random_uuid(),
kind text[] not null default '{}',
title text not null,
slug text not null,
path text not null default '',
parent_id uuid references projax.items(id) on delete restrict,
content_md text not null default '',
aliases text[] not null default '{}',
metadata jsonb not null default '{}'::jsonb,
status text not null default 'active',
pinned boolean not null default false,
archived boolean not null default false,
start_time timestamptz,
end_time timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
deleted_at timestamptz,
constraint items_slug_no_dots check (slug !~ '\.'),
constraint items_status_valid check (status in ('active', 'done', 'archived')),
unique (parent_id, slug)
);
create index if not exists items_path_idx on projax.items (path);
create index if not exists items_kind_idx on projax.items using gin (kind);
create index if not exists items_parent_idx on projax.items (parent_id);
create index if not exists items_status_idx on projax.items (status) where deleted_at is null;
create index if not exists items_aliases_idx on projax.items using gin (aliases);
-- Partial uniqueness for root-level slugs (parent_id is null) — PG treats null != null in unique
create unique index if not exists items_root_slug_uniq
on projax.items (slug) where parent_id is null;
create table if not exists projax.item_links (
id uuid primary key default gen_random_uuid(),
item_id uuid not null references projax.items(id) on delete cascade,
ref_type text not null,
ref_id text not null,
rel text not null default 'contains',
note text,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now(),
unique (item_id, ref_type, ref_id, rel)
);
create index if not exists item_links_item_idx on projax.item_links (item_id);
create index if not exists item_links_ref_idx on projax.item_links (ref_type, ref_id);
-- updated_at auto-touch
create or replace function projax.set_updated_at() returns trigger
language plpgsql as $$
begin
new.updated_at := now();
return new;
end;
$$;
drop trigger if exists items_set_updated_at on projax.items;
create trigger items_set_updated_at
before update on projax.items
for each row execute function projax.set_updated_at();
-- Grants: the projax service connects as the `postgres` role on msupabase.
-- Tailscale-only, single-user, so we grant freely on this schema.
do $$ begin
if exists (select 1 from pg_roles where rolname = 'postgres') then
execute 'grant usage on schema projax to postgres';
execute 'grant all on all tables in schema projax to postgres';
execute 'grant all on all sequences in schema projax to postgres';
execute 'grant all on all functions in schema projax to postgres';
execute 'alter default privileges in schema projax grant all on tables to postgres';
execute 'alter default privileges in schema projax grant all on sequences to postgres';
execute 'alter default privileges in schema projax grant all on functions to postgres';
end if;
end $$;