-- Migration date: 2026-06-13 -- Projects overview read model. -- Returns the project list, owner display name, and per-project counts in one -- database call for the /projects table. create or replace function public.get_projects_overview( p_user_id text, p_user_email text default null ) returns table ( id uuid, user_id text, name text, cm_number text, shared_with jsonb, created_at timestamptz, updated_at timestamptz, is_owner boolean, owner_display_name text, owner_email text, document_count integer, chat_count integer, review_count integer ) language sql stable as $$ with visible_projects as ( select p.* from public.projects p where p.user_id = p_user_id or ( coalesce(p_user_email, '') <> '' and p.user_id <> p_user_id and p.shared_with @> jsonb_build_array(p_user_email) ) ), document_counts as ( select d.project_id, count(*)::integer as document_count from public.documents d where d.project_id in (select vp.id from visible_projects vp) group by d.project_id ), chat_counts as ( select c.project_id, count(*)::integer as chat_count from public.chats c where c.project_id in (select vp.id from visible_projects vp) group by c.project_id ), review_counts as ( select tr.project_id, count(*)::integer as review_count from public.tabular_reviews tr where tr.project_id in (select vp.id from visible_projects vp) group by tr.project_id ) select vp.id, vp.user_id, vp.name, vp.cm_number, vp.shared_with, vp.created_at, vp.updated_at, vp.user_id = p_user_id as is_owner, nullif(trim(up.display_name), '') as owner_display_name, null::text as owner_email, coalesce(dc.document_count, 0) as document_count, coalesce(cc.chat_count, 0) as chat_count, coalesce(rc.review_count, 0) as review_count from visible_projects vp left join public.user_profiles up on up.user_id::text = vp.user_id left join document_counts dc on dc.project_id = vp.id left join chat_counts cc on cc.project_id = vp.id left join review_counts rc on rc.project_id = vp.id order by vp.created_at desc; $$;