From 26215fc3d82089937fcc61df1aca4d8da5f82f55 Mon Sep 17 00:00:00 2001 From: ndii-dev Date: Fri, 21 Aug 2026 19:35:01 +0100 Subject: [PATCH] Add dedicated cache TTL config for /api/v1/stats/summary (issue #4) CacheConfig gains stats_summary_ttl (env CACHE_STATS_SUMMARY_TTL), defaulting to dashboard_stats_ttl so existing CACHE_DASHBOARD_STATS_TTL customizations carry over unless explicitly overridden. Adds keys::stats_summary() cache key builder and tests covering both the fallback and override paths. --- .env.example | 6 ++++++ src/cache.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index a53aa8a..0c447f9 100644 --- a/.env.example +++ b/.env.example @@ -183,6 +183,12 @@ CACHE_CORRIDOR_METRICS_TTL=300 CACHE_ANCHOR_DATA_TTL=600 CACHE_DASHBOARD_STATS_TTL=60 +# CACHE_STATS_SUMMARY_TTL +# TTL in seconds for /api/v1/stats/summary (issue #4). Defaults to +# CACHE_DASHBOARD_STATS_TTL if unset -- only set this if the homepage +# stat tiles need a different freshness window than /analytics/dashboard. +# CACHE_STATS_SUMMARY_TTL=60 + BACKUP_S3_BUCKET=your-backup-bucket-name BACKUP_RETENTION_DAYS=30 NOTIFICATION_EMAIL=admin@example.com diff --git a/src/cache.rs b/src/cache.rs index 093a29b..14d7f60 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -34,6 +34,12 @@ pub struct CacheConfig { pub corridor_metrics_ttl: usize, // 5 minutes pub anchor_data_ttl: usize, // 10 minutes pub dashboard_stats_ttl: usize, // 1 minute + /// TTL for `/api/v1/stats/summary` (issue #4). Defaults to + /// `dashboard_stats_ttl` -- the homepage stat tiles don't need a + /// different freshness window than the existing dashboard by default, + /// but `CACHE_STATS_SUMMARY_TTL` lets it be tuned independently without + /// affecting `/analytics/dashboard`'s TTL. + pub stats_summary_ttl: usize, } impl CacheConfig { @@ -43,6 +49,7 @@ impl CacheConfig { "corridor" => self.corridor_metrics_ttl, "anchor" => self.anchor_data_ttl, "dashboard" => self.dashboard_stats_ttl, + "stats_summary" => self.stats_summary_ttl, _ => 300, } } @@ -54,9 +61,14 @@ impl CacheConfig { /// | `CACHE_CORRIDOR_METRICS_TTL` | `corridor_metrics_ttl` | 300 s | /// | `CACHE_ANCHOR_DATA_TTL` | `anchor_data_ttl` | 600 s | /// | `CACHE_DASHBOARD_STATS_TTL` | `dashboard_stats_ttl` | 60 s | + /// | `CACHE_STATS_SUMMARY_TTL` | `stats_summary_ttl` | same as `dashboard_stats_ttl` unless set | #[must_use] pub fn from_env() -> Self { let default = Self::default(); + let dashboard_stats_ttl = std::env::var("CACHE_DASHBOARD_STATS_TTL") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(default.dashboard_stats_ttl); Self { corridor_metrics_ttl: std::env::var("CACHE_CORRIDOR_METRICS_TTL") .ok() @@ -66,10 +78,15 @@ impl CacheConfig { .ok() .and_then(|v| v.parse().ok()) .unwrap_or(default.anchor_data_ttl), - dashboard_stats_ttl: std::env::var("CACHE_DASHBOARD_STATS_TTL") + dashboard_stats_ttl, + // Falls back to dashboard_stats_ttl (not the hardcoded default) + // so anyone who has already customized CACHE_DASHBOARD_STATS_TTL + // gets consistent behavior for stats/summary too, unless they + // explicitly opt into a different value. + stats_summary_ttl: std::env::var("CACHE_STATS_SUMMARY_TTL") .ok() .and_then(|v| v.parse().ok()) - .unwrap_or(default.dashboard_stats_ttl), + .unwrap_or(dashboard_stats_ttl), } } } @@ -80,6 +97,7 @@ impl Default for CacheConfig { corridor_metrics_ttl: 300, // 5 minutes anchor_data_ttl: 600, // 10 minutes dashboard_stats_ttl: 60, // 1 minute + stats_summary_ttl: 60, // 1 minute, matches dashboard_stats_ttl } } } @@ -475,6 +493,15 @@ pub mod keys { "analytics:dashboard".to_string() } + /// Cache key for `/api/v1/stats/summary` (issue #1/#4). No request + /// parameter varies this endpoint's response today, so a single static + /// key is correct -- if a per-network or per-user dimension is ever + /// added, this key must be extended to include it. + #[must_use] + pub fn stats_summary() -> String { + "stats:summary".to_string() + } + /// Pattern for invalidating all anchor-related caches #[must_use] pub fn anchor_pattern() -> String { @@ -530,5 +557,32 @@ mod tests { assert_eq!(keys::corridor_pattern(), "corridor:*"); assert_eq!(keys::dashboard_stats(), "dashboard:stats"); assert_eq!(keys::anchor_pattern(), "anchor:*"); + assert_eq!(keys::stats_summary(), "stats:summary"); + } + + #[test] + fn test_stats_summary_ttl_defaults_to_dashboard_ttl() { + std::env::remove_var("CACHE_STATS_SUMMARY_TTL"); + std::env::set_var("CACHE_DASHBOARD_STATS_TTL", "45"); + + let config = CacheConfig::from_env(); + assert_eq!(config.dashboard_stats_ttl, 45); + assert_eq!(config.stats_summary_ttl, 45); + assert_eq!(config.get_ttl("stats_summary"), 45); + + std::env::remove_var("CACHE_DASHBOARD_STATS_TTL"); + } + + #[test] + fn test_stats_summary_ttl_can_be_overridden_independently() { + std::env::set_var("CACHE_DASHBOARD_STATS_TTL", "60"); + std::env::set_var("CACHE_STATS_SUMMARY_TTL", "15"); + + let config = CacheConfig::from_env(); + assert_eq!(config.dashboard_stats_ttl, 60); + assert_eq!(config.stats_summary_ttl, 15); + + std::env::remove_var("CACHE_DASHBOARD_STATS_TTL"); + std::env::remove_var("CACHE_STATS_SUMMARY_TTL"); } }