feat: revamp analytics dashboards and harden database migrations

Add dashboard and overview analytics, health monitoring, provider expense tracking, and announcement updates across the gateway and frontend.

Keep schema migrations free of historical backfills while preserving automatic backfill execution. Bound migration deadlines, run schema preparation before Compose replacement, and anonymize deleted dashboard users.

Include the current documentation cleanup and regression coverage.
This commit is contained in:
elky
2026-10-01 11:48:17 +08:00
parent 60b89cc840
commit 066ea87d72
327 changed files with 31728 additions and 20645 deletions
@@ -104,6 +104,7 @@ ALTER TABLE ONLY public.payment_orders ADD CONSTRAINT uq_payment_orders_order_no
CREATE INDEX IF NOT EXISTS idx_payment_orders_wallet_created ON public.payment_orders USING btree (wallet_id, created_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_user_created ON public.payment_orders USING btree (user_id, created_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON public.payment_orders USING btree (status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_status_credited_user ON public.payment_orders USING btree (status, credited_at, user_id);
CREATE INDEX IF NOT EXISTS idx_payment_orders_gateway_order_id ON public.payment_orders USING btree (gateway_order_id);
CREATE UNIQUE INDEX IF NOT EXISTS uq_payment_orders_payment_method_gateway_order_id ON public.payment_orders USING btree (payment_method, gateway_order_id);
CREATE INDEX IF NOT EXISTS idx_payment_orders_kind_status ON public.payment_orders USING btree (order_kind, status);
@@ -99,7 +99,11 @@ CREATE TABLE IF NOT EXISTS public.usage (
wallet_gift_balance_after double precision,
finalized_at bigint,
created_at_unix_ms bigint DEFAULT 0 NOT NULL,
updated_at_unix_secs bigint DEFAULT 0 NOT NULL
updated_at_unix_secs bigint DEFAULT 0 NOT NULL,
failure_origin text,
failure_stage text,
failure_reason text,
failure_schema_version integer
);
ALTER TABLE ONLY public.usage ADD CONSTRAINT usage_pkey PRIMARY KEY (request_id);
@@ -204,6 +208,14 @@ CREATE INDEX IF NOT EXISTS ix_usage_counter_deltas_request_kind ON public.usage_
CREATE TABLE IF NOT EXISTS public.usage_settlement_snapshots (
request_id character varying(128) NOT NULL,
billing_status character varying(64) NOT NULL,
quota_covered_amount_usd numeric(20,8),
wallet_consumed_amount_usd numeric(20,8),
wallet_debit_amount_usd numeric(20,8),
wallet_recharge_debit_usd numeric(20,8),
wallet_gift_debit_usd numeric(20,8),
wallet_overdraft_usd numeric(20,8),
allocation_schema_version integer,
allocation_status text,
wallet_id character varying(64),
wallet_balance_before double precision,
wallet_balance_after double precision,
@@ -0,0 +1,63 @@
-- Generated by aether-data-schema from crates/aether-data/runtime/schema/logical/*.toml.
-- Do not edit generated files directly; edit logical schema or explicit overrides instead.
CREATE TABLE IF NOT EXISTS public.stats_bucket_state (
projection_version text NOT NULL,
granularity text NOT NULL,
bucket_start timestamp with time zone NOT NULL,
source_revision bigint DEFAULT 0 NOT NULL,
built_revision bigint DEFAULT -1 NOT NULL,
coverage_status text DEFAULT 'unbuilt' NOT NULL,
built_at timestamp with time zone,
last_error text,
last_failed_at timestamp with time zone
);
ALTER TABLE ONLY public.stats_bucket_state ADD CONSTRAINT stats_bucket_state_pkey PRIMARY KEY (projection_version, granularity, bucket_start);
CREATE TABLE IF NOT EXISTS public.stats_overview_daily (
projection_version text NOT NULL,
bucket_start timestamp with time zone NOT NULL,
dimensions jsonb NOT NULL,
metrics jsonb NOT NULL
);
ALTER TABLE ONLY public.stats_overview_daily ADD CONSTRAINT stats_overview_daily_pkey PRIMARY KEY (projection_version, bucket_start, dimensions);
CREATE TABLE IF NOT EXISTS public.stats_overview_dirty_events (
transaction_id bigint NOT NULL,
projection_version text NOT NULL,
granularity text NOT NULL,
bucket_start timestamp with time zone NOT NULL,
unrecoverable boolean DEFAULT false NOT NULL
);
ALTER TABLE ONLY public.stats_overview_dirty_events ADD CONSTRAINT stats_overview_dirty_events_pkey PRIMARY KEY (transaction_id, projection_version, granularity, bucket_start);
CREATE INDEX IF NOT EXISTS ix_stats_overview_dirty_events_bucket ON public.stats_overview_dirty_events USING btree (projection_version, granularity, bucket_start);
CREATE TABLE IF NOT EXISTS public.stats_overview_hourly (
projection_version text NOT NULL,
bucket_start timestamp with time zone NOT NULL,
dimensions jsonb NOT NULL,
metrics jsonb NOT NULL
);
ALTER TABLE ONLY public.stats_overview_hourly ADD CONSTRAINT stats_overview_hourly_pkey PRIMARY KEY (projection_version, bucket_start, dimensions);
CREATE TABLE IF NOT EXISTS public.usage_attribution_snapshots (
request_id text NOT NULL,
actor_user_id text,
credential_owner_id text,
attribution_kind text DEFAULT 'unknown' NOT NULL,
attribution_source text DEFAULT 'unknown' NOT NULL,
record_kind text DEFAULT 'request' NOT NULL,
parent_request_id text,
schema_version integer DEFAULT 1 NOT NULL,
attribution_revision bigint DEFAULT 1 NOT NULL,
recorded_at timestamp with time zone DEFAULT NOW() NOT NULL
);
ALTER TABLE ONLY public.usage_attribution_snapshots ADD CONSTRAINT usage_attribution_snapshots_pkey PRIMARY KEY (request_id);
CREATE INDEX IF NOT EXISTS ix_usage_attribution_actor_request ON public.usage_attribution_snapshots USING btree (actor_user_id, request_id);
CREATE INDEX IF NOT EXISTS ix_usage_attribution_owner_request ON public.usage_attribution_snapshots USING btree (credential_owner_id, request_id);
@@ -0,0 +1,84 @@
-- Generated by aether-data-schema from crates/aether-data/runtime/schema/logical/*.toml.
-- Do not edit generated files directly; edit logical schema or explicit overrides instead.
CREATE TABLE IF NOT EXISTS public.dashboard_activity_hour (
bucket_start timestamp with time zone NOT NULL,
shard smallint NOT NULL,
request_count bigint NOT NULL
);
ALTER TABLE ONLY public.dashboard_activity_hour ADD CONSTRAINT dashboard_activity_hour_pkey PRIMARY KEY (bucket_start, shard);
CREATE TABLE IF NOT EXISTS public.dashboard_activity_minute (
bucket_start timestamp with time zone NOT NULL,
shard smallint NOT NULL,
request_count bigint NOT NULL
);
ALTER TABLE ONLY public.dashboard_activity_minute ADD CONSTRAINT dashboard_activity_minute_pkey PRIMARY KEY (bucket_start, shard);
CREATE TABLE IF NOT EXISTS public.dashboard_actor_minute (
bucket_start timestamp with time zone NOT NULL,
shard smallint NOT NULL,
actor_user_id character varying(255) NOT NULL,
request_count bigint NOT NULL
);
ALTER TABLE ONLY public.dashboard_actor_minute ADD CONSTRAINT dashboard_actor_minute_pkey PRIMARY KEY (bucket_start, shard, actor_user_id);
CREATE TABLE IF NOT EXISTS public.dashboard_request_contributions (
request_id character varying(100) NOT NULL,
created_at timestamp with time zone NOT NULL,
actor_user_id character varying(255),
metrics jsonb NOT NULL
);
ALTER TABLE ONLY public.dashboard_request_contributions ADD CONSTRAINT dashboard_request_contributions_pkey PRIMARY KEY (request_id);
CREATE TABLE IF NOT EXISTS public.dashboard_stats_minute (
bucket_start timestamp with time zone NOT NULL,
shard smallint NOT NULL,
metrics jsonb DEFAULT '{}'::jsonb NOT NULL
);
ALTER TABLE ONLY public.dashboard_stats_minute ADD CONSTRAINT dashboard_stats_minute_pkey PRIMARY KEY (bucket_start, shard);
CREATE TABLE IF NOT EXISTS public.dashboard_stats_pending (
transaction_id bigint NOT NULL,
request_id character varying(100) NOT NULL,
deleted_fact jsonb
);
ALTER TABLE ONLY public.dashboard_stats_pending ADD CONSTRAINT dashboard_stats_pending_pkey PRIMARY KEY (transaction_id, request_id);
CREATE TABLE IF NOT EXISTS public.dashboard_stats_state (
singleton boolean DEFAULT true NOT NULL,
stats_since timestamp with time zone NOT NULL,
contributions_cleanup_cursor character varying(100)
);
ALTER TABLE ONLY public.dashboard_stats_state ADD CONSTRAINT dashboard_stats_state_pkey PRIMARY KEY (singleton);
CREATE TABLE IF NOT EXISTS public.dashboard_stats_total (
shard smallint NOT NULL,
metrics jsonb DEFAULT '{}'::jsonb NOT NULL
);
ALTER TABLE ONLY public.dashboard_stats_total ADD CONSTRAINT dashboard_stats_total_pkey PRIMARY KEY (shard);
CREATE TABLE IF NOT EXISTS public.dashboard_user_anonymization_pending (
transaction_id bigint NOT NULL,
user_id character varying(255) NOT NULL
);
ALTER TABLE ONLY public.dashboard_user_anonymization_pending ADD CONSTRAINT dashboard_user_anonymization_pending_pkey PRIMARY KEY (transaction_id, user_id);
CREATE TABLE IF NOT EXISTS public.dashboard_user_events_minute (
bucket_start timestamp with time zone NOT NULL,
shard smallint NOT NULL,
created_count bigint DEFAULT 0 NOT NULL,
deleted_count bigint DEFAULT 0 NOT NULL
);
ALTER TABLE ONLY public.dashboard_user_events_minute ADD CONSTRAINT dashboard_user_events_minute_pkey PRIMARY KEY (bucket_start, shard);
@@ -0,0 +1,27 @@
-- Generated by aether-data-schema from crates/aether-data/runtime/schema/logical/*.toml.
-- Do not edit generated files directly; edit logical schema or explicit overrides instead.
CREATE TABLE IF NOT EXISTS public.provider_expenses (
id text NOT NULL,
client_request_id text NOT NULL,
provider_id text NOT NULL,
provider_name text NOT NULL,
kind text NOT NULL,
amount numeric(20,8) NOT NULL,
currency text NOT NULL,
paid_at timestamp with time zone NOT NULL,
period_start timestamp with time zone,
period_end timestamp with time zone,
note text,
external_reference text,
created_by text,
created_at timestamp with time zone DEFAULT NOW() NOT NULL,
voided_at timestamp with time zone,
voided_by text
);
ALTER TABLE ONLY public.provider_expenses ADD CONSTRAINT provider_expenses_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.provider_expenses ADD CONSTRAINT provider_expenses_client_request_id_key UNIQUE (client_request_id);
CREATE INDEX IF NOT EXISTS ix_provider_expenses_paid_at ON public.provider_expenses USING btree (paid_at, id);
CREATE INDEX IF NOT EXISTS ix_provider_expenses_provider_paid_at ON public.provider_expenses USING btree (provider_id, paid_at);
@@ -9,3 +9,6 @@
006_usage.sql
007_stats.sql
008_background_tasks.sql
009_overview.sql
010_dashboard.sql
010_provider_expenses.sql