feat: Phase 2 data aggregation pipeline (Strike 4A)

Backend:
- Stats ingestion consumer subscribing to corrosion.*.stats NATS subject
- Hourly aggregation scheduler (runs :05 past every hour)
- Daily cleanup job (03:00 UTC) with 7-day raw / 90-day hourly retention
- Analytics API (summary, timeseries, CSV export)
- Complete stats DB queries with aggregation and cleanup

Frontend:
- Analytics dashboard with ECharts integration
- Player count and server performance charts
- Time range selector (24h/7d/30d)
- CSV export functionality
- Real-time data loading

Infrastructure:
- Exposed NatsBridge.jetstream for consumer access
- Background service initialization in main.rs

Data flow: Plugin → NATS → Consumer → DB → Aggregation → API → Charts

Unblocks Strike 4B (dashboards) and 4C (alerting).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 12:53:25 -05:00
parent 81eeb3b451
commit 75d08aeee4
11 changed files with 1130 additions and 73 deletions

View File

@@ -65,6 +65,43 @@ async fn main() -> anyhow::Result<()> {
// Bootstrap: create admin user + license on first run
bootstrap_admin(&db).await;
// Initialize background services if NATS is available
if let Some(ref nats_client) = nats {
let nats_bridge = Arc::new(services::nats_bridge::NatsBridge::new(nats_client.clone()));
// Start stats consumer
let stats_consumer = services::stats_consumer::StatsConsumerService::new(
db.clone(),
nats_bridge.clone(),
);
if let Err(e) = stats_consumer.start().await {
tracing::error!("Failed to start stats consumer: {}", e);
}
// Start scheduler service
let scheduler = services::scheduler::SchedulerService::new(
db.clone(),
nats_bridge.clone(),
)
.await?;
// Register stats jobs
if let Err(e) = scheduler.register_stats_aggregation().await {
tracing::error!("Failed to register stats aggregation job: {}", e);
}
if let Err(e) = scheduler.register_stats_cleanup().await {
tracing::error!("Failed to register stats cleanup job: {}", e);
}
if let Err(e) = scheduler.start().await {
tracing::error!("Failed to start scheduler: {}", e);
} else {
tracing::info!("Scheduler service started");
}
} else {
tracing::warn!("Skipping background services (NATS not available)");
}
let state = Arc::new(AppState { db, nats, config });
// CORS — permissive in dev, locked down in production
@@ -91,6 +128,7 @@ async fn main() -> anyhow::Result<()> {
.nest("/api/early-access", api::early_access::router())
.nest("/api/admin", api::admin::router())
.nest("/api/ws", api::ws::router())
.nest("/api/analytics", api::analytics::router())
.layer(cors)
.layer(TraceLayer::new_for_http())
.with_state(state);