ruvector_scipix/api/
state.rs1use moka::future::Cache;
2use sha2::{Digest, Sha256};
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::time::Duration;
6
7use super::{
8 jobs::JobQueue,
9 middleware::{create_rate_limiter, AppRateLimiter},
10};
11
12#[derive(Clone)]
14pub struct AppState {
15 pub job_queue: Arc<JobQueue>,
17
18 pub cache: Cache<String, String>,
20
21 pub rate_limiter: AppRateLimiter,
23
24 pub auth_enabled: bool,
26
27 pub api_keys: Arc<HashMap<String, String>>,
30}
31
32impl AppState {
33 pub fn new() -> Self {
35 Self {
36 job_queue: Arc::new(JobQueue::new()),
37 cache: create_cache(),
38 rate_limiter: create_rate_limiter(),
39 auth_enabled: false,
40 api_keys: Arc::new(HashMap::new()),
41 }
42 }
43
44 pub fn with_config(max_jobs: usize, cache_size: u64) -> Self {
46 Self {
47 job_queue: Arc::new(JobQueue::with_capacity(max_jobs)),
48 cache: Cache::builder()
49 .max_capacity(cache_size)
50 .time_to_live(Duration::from_secs(3600))
51 .time_to_idle(Duration::from_secs(600))
52 .build(),
53 rate_limiter: create_rate_limiter(),
54 auth_enabled: false,
55 api_keys: Arc::new(HashMap::new()),
56 }
57 }
58
59 pub fn with_auth(api_keys: HashMap<String, String>) -> Self {
61 let hashed_keys: HashMap<String, String> = api_keys
63 .into_iter()
64 .map(|(app_id, key)| (app_id, hash_api_key(&key)))
65 .collect();
66
67 Self {
68 job_queue: Arc::new(JobQueue::new()),
69 cache: create_cache(),
70 rate_limiter: create_rate_limiter(),
71 auth_enabled: true,
72 api_keys: Arc::new(hashed_keys),
73 }
74 }
75
76 pub fn add_api_key(&mut self, app_id: String, api_key: &str) {
78 let hashed = hash_api_key(api_key);
79 Arc::make_mut(&mut self.api_keys).insert(app_id, hashed);
80 self.auth_enabled = true;
81 }
82
83 pub fn set_auth_enabled(&mut self, enabled: bool) {
85 self.auth_enabled = enabled;
86 }
87}
88
89impl Default for AppState {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95fn hash_api_key(key: &str) -> String {
97 let mut hasher = Sha256::new();
98 hasher.update(key.as_bytes());
99 format!("{:x}", hasher.finalize())
100}
101
102fn create_cache() -> Cache<String, String> {
104 Cache::builder()
105 .max_capacity(10_000)
107 .time_to_live(Duration::from_secs(3600))
109 .time_to_idle(Duration::from_secs(600))
111 .build()
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[tokio::test]
119 async fn test_state_creation() {
120 let state = AppState::new();
121 assert!(Arc::strong_count(&state.job_queue) >= 1);
122 }
123
124 #[tokio::test]
125 async fn test_state_with_config() {
126 let state = AppState::with_config(100, 5000);
127 assert!(Arc::strong_count(&state.job_queue) >= 1);
128 }
129
130 #[tokio::test]
131 async fn test_cache_operations() {
132 let state = AppState::new();
133
134 state
136 .cache
137 .insert("key1".to_string(), "value1".to_string())
138 .await;
139
140 let value = state.cache.get(&"key1".to_string()).await;
142 assert_eq!(value, Some("value1".to_string()));
143
144 let missing = state.cache.get(&"missing".to_string()).await;
146 assert_eq!(missing, None);
147 }
148}