create table if not exists public.page_refs ( id uuid primary key default gen_random_uuid(), workspace_id uuid not null references public.workspaces(id) on delete cascade, source_page_id uuid not null references public.documents(id) on delete cascade, source_block_id text, target_page_id uuid not null references public.documents(id) on delete cascade, alias text, display_mode text not null default 'inline' check (display_mode in ('inline','embed')), is_previewable boolean not null default true, created_by uuid not null references public.profiles(id) on delete cascade, created_at timestamptz not null default timezone('utc', now()), updated_at timestamptz not null default timezone('utc', now()) ); create index if not exists page_refs_target_idx on public.page_refs (workspace_id, target_page_id, display_mode); create index if not exists page_refs_source_idx on public.page_refs (workspace_id, source_page_id); create index if not exists page_refs_recent_idx on public.page_refs (workspace_id, updated_at desc); create unique index if not exists page_refs_unique_block_ref on public.page_refs (workspace_id, source_page_id, coalesce(source_block_id, ''), target_page_id, display_mode); create or replace function public.set_page_refs_updated_at() returns trigger language plpgsql security invoker as $$ begin new.updated_at := timezone('utc', now()); return new; end; $$; drop trigger if exists set_page_refs_updated_at on public.page_refs; create trigger set_page_refs_updated_at before update on public.page_refs for each row execute procedure public.set_page_refs_updated_at(); alter table public.page_refs enable row level security; drop policy if exists "Select refs within workspace" on public.page_refs; create policy "Select refs within workspace" on public.page_refs for select using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = page_refs.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Insert refs within workspace" on public.page_refs; create policy "Insert refs within workspace" on public.page_refs for insert with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = page_refs.workspace_id and wm.user_id = auth.uid() ) ); drop policy if exists "Update refs ownership" on public.page_refs; create policy "Update refs ownership" on public.page_refs for update using ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = page_refs.workspace_id and wm.user_id = auth.uid() ) ) with check ( exists ( select 1 from public.workspace_members wm where wm.workspace_id = page_refs.workspace_id and wm.user_id = auth.uid() ) ); create or replace function public.record_page_ref( p_workspace_id uuid, p_source_page_id uuid, p_source_block_id text, p_target_page_id uuid, p_alias text, p_display_mode text default 'inline', p_is_previewable boolean default true, p_created_by uuid default auth.uid() ) returns public.page_refs language plpgsql security definer set search_path = public as $$ declare v_existing public.page_refs%rowtype; v_mode text := coalesce(nullif(p_display_mode, ''), 'inline'); begin select * into v_existing from public.page_refs where workspace_id = p_workspace_id and source_page_id = p_source_page_id and target_page_id = p_target_page_id and coalesce(source_block_id, '') = coalesce(p_source_block_id, '') and display_mode = v_mode limit 1; if found then update public.page_refs set alias = coalesce(p_alias, public.page_refs.alias), is_previewable = coalesce(p_is_previewable, public.page_refs.is_previewable), updated_at = timezone('utc', now()) where id = v_existing.id returning * into v_existing; return v_existing; end if; insert into public.page_refs ( workspace_id, source_page_id, source_block_id, target_page_id, alias, display_mode, is_previewable, created_by ) values ( p_workspace_id, p_source_page_id, p_source_block_id, p_target_page_id, p_alias, v_mode, coalesce(p_is_previewable, true), coalesce(p_created_by, auth.uid()) ) returning * into v_existing; return v_existing; end; $$; create or replace function public.list_backlinks( p_workspace_id uuid, p_target_page_id uuid, p_limit integer default 50, p_offset integer default 0 ) returns table ( id uuid, source_page_id uuid, source_block_id text, alias text, display_mode text, is_previewable boolean, created_at timestamptz, updated_at timestamptz, source_title text, workspace_id uuid ) language sql security definer set search_path = public as $$ select r.id, r.source_page_id, r.source_block_id, r.alias, r.display_mode, r.is_previewable, r.created_at, r.updated_at, d.title as source_title, r.workspace_id from public.page_refs r join public.documents d on d.id = r.source_page_id where r.workspace_id = p_workspace_id and r.target_page_id = p_target_page_id and d.workspace_id = p_workspace_id order by r.updated_at desc limit greatest(p_limit, 1) offset greatest(p_offset, 0); $$;