103 lines
6.7 KiB
PL/PgSQL
103 lines
6.7 KiB
PL/PgSQL
-- Credentials and cached aggregates are server-only. No raw token is retained.
|
|
create table public.prometheus_settings (
|
|
id boolean primary key default true check (id),
|
|
token_hash text check (token_hash ~ '^[a-f0-9]{64}$'),
|
|
updated_by uuid references public.profiles(id) on delete set null,
|
|
updated_at timestamptz,
|
|
last_scraped_at timestamptz,
|
|
snapshot jsonb,
|
|
snapshot_at timestamptz
|
|
);
|
|
insert into public.prometheus_settings(id) values (true);
|
|
alter table public.prometheus_settings enable row level security;
|
|
revoke all on public.prometheus_settings from public, anon, authenticated;
|
|
grant select, update on public.prometheus_settings to service_role;
|
|
create policy "Server manages metrics integration" on public.prometheus_settings
|
|
for all to service_role using (true) with check (true);
|
|
|
|
create function public.prometheus_status(p_reviewer uuid) returns jsonb
|
|
language plpgsql security invoker set search_path = '' as $$
|
|
begin
|
|
if not exists (select 1 from public.profiles where id=p_reviewer and role='admin') then
|
|
raise exception using errcode='42501', message='Admin permission required';
|
|
end if;
|
|
return (select jsonb_build_object(
|
|
'enabled', s.token_hash is not null and exists(select 1 from public.profiles p where p.id=s.updated_by and p.role='admin'),
|
|
'updatedAt', s.updated_at, 'lastScrapedAt', s.last_scraped_at
|
|
) from public.prometheus_settings s where s.id);
|
|
end; $$;
|
|
|
|
create function public.configure_prometheus(p_reviewer uuid, p_token_hash text) returns jsonb
|
|
language plpgsql security invoker set search_path = '' as $$
|
|
begin
|
|
if not exists (select 1 from public.profiles where id=p_reviewer and role='admin') then
|
|
raise exception using errcode='42501', message='Admin permission required';
|
|
end if;
|
|
if p_token_hash is not null and p_token_hash !~ '^[a-f0-9]{64}$' then raise exception 'Invalid metrics token hash'; end if;
|
|
update public.prometheus_settings set token_hash=p_token_hash, updated_by=p_reviewer,
|
|
updated_at=clock_timestamp(), last_scraped_at=null, snapshot=null, snapshot_at=null where id;
|
|
return public.prometheus_status(p_reviewer);
|
|
end; $$;
|
|
|
|
create index if not exists game_sessions_metrics_created on public.game_sessions(created_at);
|
|
create index if not exists multiplayer_rooms_metrics_created on public.multiplayer_rooms(created_at);
|
|
|
|
create function public.read_prometheus_metrics(p_token_hash text) returns jsonb
|
|
language plpgsql security invoker set search_path = '' set statement_timeout = '15s' as $$
|
|
declare settings public.prometheus_settings; collected_at timestamptz := clock_timestamp(); result jsonb;
|
|
begin
|
|
-- The token grants only access to aggregate metrics. Demoting/deleting its
|
|
-- issuing Admin also invalidates it. Lock prevents concurrent scrape stampedes.
|
|
select s.* into settings from public.prometheus_settings s
|
|
where s.id and s.token_hash=p_token_hash
|
|
and exists(select 1 from public.profiles p where p.id=s.updated_by and p.role='admin')
|
|
for update;
|
|
if not found then return null; end if;
|
|
if settings.snapshot_at > collected_at - interval '30 seconds' then
|
|
update public.prometheus_settings set last_scraped_at=collected_at where id;
|
|
return settings.snapshot;
|
|
end if;
|
|
with samples(metric, labels, value) as (
|
|
select 'minegeo_accounts', '{}'::jsonb, count(*)::double precision from public.profiles
|
|
union all select 'minegeo_published_panoramas', '{}'::jsonb, coalesce(sum(panorama_count),0)::double precision from public.datasets where state='published'
|
|
union all select 'minegeo_games_started_24h', '{}'::jsonb, count(*)::double precision from public.game_sessions where created_at >= collected_at - interval '24 hours'
|
|
union all select 'minegeo_rooms_created_24h', '{}'::jsonb, count(*)::double precision from public.multiplayer_rooms where created_at >= collected_at - interval '24 hours'
|
|
union all select 'minegeo_snapshot_timestamp_seconds', '{}'::jsonb, extract(epoch from collected_at)::double precision
|
|
union all
|
|
select 'minegeo_maps', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('uploading'),('processing'),('draft'),('review'),('published'),('rejected'),('unpublished'),('failed')) v(state)
|
|
left join (select state,count(*) n from public.datasets group by state) c using(state)
|
|
union all
|
|
select 'minegeo_games', jsonb_build_object('difficulty',d.difficulty,'status',v.status), coalesce(c.n,0)::double precision
|
|
from (values('easy'),('normal'),('hard')) d(difficulty)
|
|
cross join (values('incomplete'),('complete')) v(status)
|
|
left join (select difficulty,case when current_round=5 then 'complete' else 'incomplete' end status,count(*) n
|
|
from public.game_sessions group by 1,2) c using(difficulty,status)
|
|
union all
|
|
select 'minegeo_rooms', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('lobby'),('loading'),('playing'),('results'),('finished'),('closed')) v(state)
|
|
left join (select status state,count(*) n from public.multiplayer_rooms group by status) c using(state)
|
|
union all
|
|
select 'minegeo_upload_jobs', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('queued'),('running'),('complete'),('failed'),('cancelled')) v(state)
|
|
left join (select state,count(*) n from public.upload_jobs group by state) c using(state)
|
|
union all
|
|
select 'minegeo_bug_reports', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('draft'),('pending'),('open'),('done')) v(state)
|
|
left join (select state,count(*) n from public.bug_reports group by state) c using(state)
|
|
union all
|
|
select 'minegeo_content_reports', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('open'),('resolved'),('confirmed'),('dismissed')) v(state)
|
|
left join (select state,count(*) n from public.reports group by state) c using(state)
|
|
union all
|
|
select 'minegeo_discord_announcements', jsonb_build_object('state',v.state), coalesce(c.n,0)::double precision
|
|
from (values('pending'),('sending'),('sent'),('skipped'),('failed')) v(state)
|
|
left join (select state,count(*) n from public.discord_announcements group by state) c using(state)
|
|
) select jsonb_agg(jsonb_build_object('metric',metric,'labels',labels,'value',value) order by metric,labels::text) into result from samples;
|
|
update public.prometheus_settings set last_scraped_at=collected_at, snapshot_at=collected_at, snapshot=result where id;
|
|
return result;
|
|
end; $$;
|
|
|
|
revoke all on function public.prometheus_status(uuid), public.configure_prometheus(uuid,text), public.read_prometheus_metrics(text) from public,anon,authenticated;
|
|
grant execute on function public.prometheus_status(uuid), public.configure_prometheus(uuid,text), public.read_prometheus_metrics(text) to service_role;
|