-- All access goes through authenticated server routes using the service client. create table public.map_reactions ( dataset_id uuid not null references public.datasets(id) on delete cascade, user_id uuid not null references public.profiles(id) on delete cascade, value smallint not null check (value in (-1, 1)), created_at timestamptz not null default now(), primary key (dataset_id, user_id) ); create index map_reactions_user on public.map_reactions(user_id); create index map_reactions_totals on public.map_reactions(dataset_id, value); create table public.map_comments ( id uuid primary key default gen_random_uuid(), dataset_id uuid not null references public.datasets(id) on delete cascade, author_id uuid references public.profiles(id) on delete set null, body text not null check (body = btrim(body) and char_length(body) between 1 and 2000), created_at timestamptz not null default now(), removed_at timestamptz, removed_by uuid references public.profiles(id) on delete set null ); create index map_comments_dataset_created on public.map_comments(dataset_id, created_at desc, id desc); create index map_comments_author on public.map_comments(author_id); create index map_comments_removed_by on public.map_comments(removed_by); create table public.community_reports ( id uuid primary key default gen_random_uuid(), reporter_id uuid references public.profiles(id) on delete set null, target text not null check (target in ('comment', 'account')), comment_id uuid references public.map_comments(id) on delete cascade, account_id uuid references public.profiles(id) on delete cascade, category text not null check (category in ('spam', 'advertising', 'inappropriate', 'harassment', 'other')), reason text not null check (reason = btrim(reason) and char_length(reason) between 10 and 2000), state text not null default 'open' check (state in ('open', 'resolved', 'dismissed')), reviewer_id uuid references public.profiles(id) on delete set null, reviewed_at timestamptz, review_reason text check (char_length(review_reason) <= 2000), created_at timestamptz not null default now(), constraint community_reports_target_link check ( (target = 'comment' and comment_id is not null and account_id is null) or (target = 'account' and account_id is not null and comment_id is null) ) ); create unique index community_reports_open_comment on public.community_reports(reporter_id, comment_id) where state = 'open' and target = 'comment'; create unique index community_reports_open_account on public.community_reports(reporter_id, account_id) where state = 'open' and target = 'account'; create index community_reports_queue on public.community_reports(state, created_at desc); create index community_reports_reporter on public.community_reports(reporter_id); create index community_reports_comment on public.community_reports(comment_id); create index community_reports_account on public.community_reports(account_id); create index community_reports_reviewer on public.community_reports(reviewer_id); alter table public.map_reactions enable row level security; alter table public.map_comments enable row level security; alter table public.community_reports enable row level security; revoke all on public.map_reactions, public.map_comments, public.community_reports from public, anon, authenticated; grant all on public.map_reactions, public.map_comments, public.community_reports to service_role; create function public.map_reaction_summary(p_dataset uuid) returns table (likes bigint, dislikes bigint) language sql stable security invoker set search_path = '' as $$ select count(*) filter (where value = 1), count(*) filter (where value = -1) from public.map_reactions where dataset_id = p_dataset; $$; revoke all on function public.map_reaction_summary(uuid) from public, anon, authenticated; grant execute on function public.map_reaction_summary(uuid) to service_role; -- Make new tables and the RPC visible to PostgREST immediately after commit. notify pgrst, 'reload schema';