mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 01:47:47 +08:00
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:
@@ -426,6 +426,10 @@ columns = ["user_id", "created_at"]
|
||||
name = "idx_payment_orders_status"
|
||||
columns = ["status"]
|
||||
|
||||
[[table.payment_orders.indexes]]
|
||||
name = "idx_payment_orders_status_credited_user"
|
||||
columns = ["status", "credited_at", "user_id"]
|
||||
|
||||
[[table.payment_orders.indexes]]
|
||||
name = "idx_payment_orders_gateway_order_id"
|
||||
columns = ["gateway_order_id"]
|
||||
|
||||
@@ -928,6 +928,26 @@ columns = ["processed_at", "created_at", "id"]
|
||||
name = "ix_usage_counter_deltas_request_kind"
|
||||
columns = ["request_id", "kind", "target_id"]
|
||||
|
||||
[[table.usage.columns]]
|
||||
name = "failure_origin"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage.columns]]
|
||||
name = "failure_stage"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage.columns]]
|
||||
name = "failure_reason"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage.columns]]
|
||||
name = "failure_schema_version"
|
||||
type = "int32"
|
||||
nullable = true
|
||||
|
||||
[table.usage_settlement_snapshots]
|
||||
domain = "usage"
|
||||
order = 20
|
||||
@@ -943,6 +963,52 @@ name = "billing_status"
|
||||
type = "text"
|
||||
length = 64
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "quota_covered_amount_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_consumed_amount_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_debit_amount_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_recharge_debit_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_gift_debit_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_overdraft_usd"
|
||||
type = "decimal_money"
|
||||
nullable = true
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "allocation_schema_version"
|
||||
type = "int32"
|
||||
nullable = true
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "allocation_status"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage_settlement_snapshots.columns]]
|
||||
name = "wallet_id"
|
||||
type = "text_id"
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
[table.usage_attribution_snapshots]
|
||||
domain = "usage"
|
||||
primary_key = ["request_id"]
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "request_id"
|
||||
type = "text"
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "actor_user_id"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "credential_owner_id"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "attribution_kind"
|
||||
type = "text"
|
||||
default = "unknown"
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "attribution_source"
|
||||
type = "text"
|
||||
default = "unknown"
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "record_kind"
|
||||
type = "text"
|
||||
default = "request"
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "parent_request_id"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "schema_version"
|
||||
type = "int32"
|
||||
default = 1
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "attribution_revision"
|
||||
type = "int64"
|
||||
default = 1
|
||||
|
||||
[[table.usage_attribution_snapshots.columns]]
|
||||
name = "recorded_at"
|
||||
type = "timestamp"
|
||||
default = { raw = "NOW()" }
|
||||
|
||||
[[table.usage_attribution_snapshots.indexes]]
|
||||
name = "ix_usage_attribution_actor_request"
|
||||
columns = ["actor_user_id", "request_id"]
|
||||
|
||||
[[table.usage_attribution_snapshots.indexes]]
|
||||
name = "ix_usage_attribution_owner_request"
|
||||
columns = ["credential_owner_id", "request_id"]
|
||||
|
||||
[table.stats_bucket_state]
|
||||
domain = "stats"
|
||||
primary_key = ["projection_version","granularity","bucket_start"]
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "projection_version"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "granularity"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "source_revision"
|
||||
type = "int64"
|
||||
default = 0
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "built_revision"
|
||||
type = "int64"
|
||||
default = -1
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "coverage_status"
|
||||
type = "text"
|
||||
default = "unbuilt"
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "built_at"
|
||||
type = "timestamp"
|
||||
nullable = true
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "last_error"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.stats_bucket_state.columns]]
|
||||
name = "last_failed_at"
|
||||
type = "timestamp"
|
||||
nullable = true
|
||||
|
||||
[table.stats_overview_hourly]
|
||||
domain = "stats"
|
||||
primary_key = ["projection_version","bucket_start","dimensions"]
|
||||
|
||||
[[table.stats_overview_hourly.columns]]
|
||||
name = "projection_version"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_overview_hourly.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.stats_overview_hourly.columns]]
|
||||
name = "dimensions"
|
||||
type = "json"
|
||||
|
||||
[[table.stats_overview_hourly.columns]]
|
||||
name = "metrics"
|
||||
type = "json"
|
||||
|
||||
[table.stats_overview_daily]
|
||||
domain = "stats"
|
||||
primary_key = ["projection_version","bucket_start","dimensions"]
|
||||
|
||||
[[table.stats_overview_daily.columns]]
|
||||
name = "projection_version"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_overview_daily.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.stats_overview_daily.columns]]
|
||||
name = "dimensions"
|
||||
type = "json"
|
||||
|
||||
[[table.stats_overview_daily.columns]]
|
||||
name = "metrics"
|
||||
type = "json"
|
||||
|
||||
[table.stats_overview_dirty_events]
|
||||
domain = "stats"
|
||||
primary_key = ["transaction_id", "projection_version", "granularity", "bucket_start"]
|
||||
|
||||
[[table.stats_overview_dirty_events.columns]]
|
||||
name = "transaction_id"
|
||||
type = "int64"
|
||||
|
||||
[[table.stats_overview_dirty_events.columns]]
|
||||
name = "projection_version"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_overview_dirty_events.columns]]
|
||||
name = "granularity"
|
||||
type = "text"
|
||||
|
||||
[[table.stats_overview_dirty_events.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.stats_overview_dirty_events.columns]]
|
||||
name = "unrecoverable"
|
||||
type = "bool"
|
||||
default = false
|
||||
|
||||
[[table.stats_overview_dirty_events.indexes]]
|
||||
name = "ix_stats_overview_dirty_events_bucket"
|
||||
columns = ["projection_version", "granularity", "bucket_start"]
|
||||
@@ -0,0 +1,187 @@
|
||||
# Future-only dashboard aggregates. Trigger functions and activation data live in
|
||||
# migration 20260919000000_add_future_dashboard_summary.sql; no history is backfilled.
|
||||
|
||||
[table.dashboard_activity_hour]
|
||||
domain = "stats"
|
||||
primary_key = ["bucket_start", "shard"]
|
||||
|
||||
[[table.dashboard_activity_hour.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_activity_hour.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_activity_hour.columns]]
|
||||
name = "request_count"
|
||||
type = "int64"
|
||||
|
||||
[table.dashboard_stats_state]
|
||||
domain = "stats"
|
||||
primary_key = ["singleton"]
|
||||
|
||||
[[table.dashboard_stats_state.columns]]
|
||||
name = "singleton"
|
||||
type = "bool"
|
||||
default = true
|
||||
|
||||
[[table.dashboard_stats_state.columns]]
|
||||
name = "stats_since"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_stats_state.columns]]
|
||||
name = "contributions_cleanup_cursor"
|
||||
type = "text"
|
||||
length = 100
|
||||
nullable = true
|
||||
|
||||
[table.dashboard_activity_minute]
|
||||
domain = "stats"
|
||||
primary_key = ["bucket_start", "shard"]
|
||||
|
||||
[[table.dashboard_activity_minute.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_activity_minute.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_activity_minute.columns]]
|
||||
name = "request_count"
|
||||
type = "int64"
|
||||
|
||||
[table.dashboard_request_contributions]
|
||||
domain = "stats"
|
||||
primary_key = ["request_id"]
|
||||
|
||||
[[table.dashboard_request_contributions.columns]]
|
||||
name = "request_id"
|
||||
type = "text"
|
||||
length = 100
|
||||
|
||||
[[table.dashboard_request_contributions.columns]]
|
||||
name = "created_at"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_request_contributions.columns]]
|
||||
name = "actor_user_id"
|
||||
type = "text"
|
||||
nullable = true
|
||||
length = 255
|
||||
|
||||
[[table.dashboard_request_contributions.columns]]
|
||||
name = "metrics"
|
||||
type = "json"
|
||||
|
||||
[table.dashboard_stats_total]
|
||||
domain = "stats"
|
||||
primary_key = ["shard"]
|
||||
|
||||
[[table.dashboard_stats_total.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_stats_total.columns]]
|
||||
name = "metrics"
|
||||
type = "json"
|
||||
default = { raw = "'{}'::jsonb" }
|
||||
|
||||
[table.dashboard_stats_minute]
|
||||
domain = "stats"
|
||||
primary_key = ["bucket_start","shard"]
|
||||
|
||||
[[table.dashboard_stats_minute.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_stats_minute.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_stats_minute.columns]]
|
||||
name = "metrics"
|
||||
type = "json"
|
||||
default = { raw = "'{}'::jsonb" }
|
||||
|
||||
[table.dashboard_actor_minute]
|
||||
domain = "stats"
|
||||
primary_key = ["bucket_start","shard","actor_user_id"]
|
||||
|
||||
[[table.dashboard_actor_minute.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_actor_minute.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_actor_minute.columns]]
|
||||
name = "actor_user_id"
|
||||
type = "text"
|
||||
length = 255
|
||||
|
||||
[[table.dashboard_actor_minute.columns]]
|
||||
name = "request_count"
|
||||
type = "int64"
|
||||
|
||||
[table.dashboard_stats_pending]
|
||||
domain = "stats"
|
||||
primary_key = ["transaction_id","request_id"]
|
||||
|
||||
[[table.dashboard_stats_pending.columns]]
|
||||
name = "transaction_id"
|
||||
type = "int64"
|
||||
|
||||
[[table.dashboard_stats_pending.columns]]
|
||||
name = "request_id"
|
||||
type = "text"
|
||||
length = 100
|
||||
|
||||
[[table.dashboard_stats_pending.columns]]
|
||||
name = "deleted_fact"
|
||||
type = "json"
|
||||
nullable = true
|
||||
|
||||
[table.dashboard_user_events_minute]
|
||||
domain = "stats"
|
||||
primary_key = ["bucket_start","shard"]
|
||||
|
||||
[[table.dashboard_user_events_minute.columns]]
|
||||
name = "bucket_start"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.dashboard_user_events_minute.columns]]
|
||||
name = "shard"
|
||||
type = "int32"
|
||||
driver.postgres.type = "smallint"
|
||||
|
||||
[[table.dashboard_user_events_minute.columns]]
|
||||
name = "created_count"
|
||||
type = "int64"
|
||||
default = 0
|
||||
|
||||
[[table.dashboard_user_events_minute.columns]]
|
||||
name = "deleted_count"
|
||||
type = "int64"
|
||||
default = 0
|
||||
|
||||
# Transaction-local events only; no committed rows are exported or restored.
|
||||
[table.dashboard_user_anonymization_pending]
|
||||
domain = "stats"
|
||||
primary_key = ["transaction_id", "user_id"]
|
||||
|
||||
[[table.dashboard_user_anonymization_pending.columns]]
|
||||
name = "transaction_id"
|
||||
type = "int64"
|
||||
|
||||
[[table.dashboard_user_anonymization_pending.columns]]
|
||||
name = "user_id"
|
||||
type = "text"
|
||||
length = 255
|
||||
@@ -0,0 +1,88 @@
|
||||
[table.provider_expenses]
|
||||
domain = "wallet_billing"
|
||||
primary_key = ["id"]
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "id"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "client_request_id"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "provider_id"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "provider_name"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "kind"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "amount"
|
||||
type = "decimal_money"
|
||||
driver.postgres.type = "numeric(20,8)"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "currency"
|
||||
type = "text"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "paid_at"
|
||||
type = "timestamp"
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "period_start"
|
||||
type = "timestamp"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "period_end"
|
||||
type = "timestamp"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "note"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "external_reference"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "created_by"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "created_at"
|
||||
type = "timestamp"
|
||||
default = { raw = "NOW()" }
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "voided_at"
|
||||
type = "timestamp"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.columns]]
|
||||
name = "voided_by"
|
||||
type = "text"
|
||||
nullable = true
|
||||
|
||||
[[table.provider_expenses.uniques]]
|
||||
name = "provider_expenses_client_request_id_key"
|
||||
columns = ["client_request_id"]
|
||||
|
||||
[[table.provider_expenses.indexes]]
|
||||
name = "ix_provider_expenses_paid_at"
|
||||
columns = ["paid_at", "id"]
|
||||
|
||||
[[table.provider_expenses.indexes]]
|
||||
name = "ix_provider_expenses_provider_paid_at"
|
||||
columns = ["provider_id", "paid_at"]
|
||||
Reference in New Issue
Block a user