use async_trait::async_trait; use chrono::{DateTime, Utc}; use common::{MQTT_SEND_INTERVAL, StatusMessage}; use memory::InMemoryRepository; use rusqlite::Row; use serde::{Deserialize, Serialize}; use sqlite::SQLiteRepository; use std::sync::Arc; pub mod memory; pub mod sqlite; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UptimeStorageModel { pub id: String, pub first_seen: DateTime, pub last_seen: DateTime, pub message_count: u64, } #[derive(Debug, Serialize, Deserialize)] pub struct UptimeMessage { pub agent: String, pub uptime: f64, pub last_seen: DateTime, } impl Into for UptimeStorageModel { fn into(self) -> UptimeMessage { let duration = Utc::now().signed_duration_since(self.first_seen); let expected_messages = duration.num_seconds() as f64 / MQTT_SEND_INTERVAL as f64; let uptime_pct = (self.message_count as f64 / expected_messages * 100.0).min(100.0); UptimeMessage { agent: self.id, uptime: uptime_pct, last_seen: self.last_seen, } } } impl TryFrom<&Row<'_>> for UptimeStorageModel { type Error = rusqlite::Error; fn try_from(row: &Row) -> Result { let first_seen: DateTime = row.get::<_, String>(1)?.parse().map_err(|e| { rusqlite::Error::FromSqlConversionFailure(1, rusqlite::types::Type::Text, Box::new(e)) })?; let last_seen: DateTime = row.get::<_, String>(2)?.parse().map_err(|e| { rusqlite::Error::FromSqlConversionFailure(2, rusqlite::types::Type::Text, Box::new(e)) })?; Ok(UptimeStorageModel { id: row.get(0)?, first_seen, last_seen, message_count: row.get(1)?, }) } } #[async_trait] pub trait StorageRepository: Send + Sync { async fn record_message(&self, message: &StatusMessage) -> anyhow::Result<()>; async fn record_uptime(&self, agent: &str) -> anyhow::Result<()>; async fn get_history( &self, agent: &str, duration: Option>, ) -> anyhow::Result>; async fn get_agents(&self) -> anyhow::Result>; } pub enum StorageStrategy { InMemory, SQLite(String), } pub struct StorageRepositoryImpl { inner: Arc, } impl StorageRepositoryImpl { pub fn new(strategy: StorageStrategy) -> Self { let inner: Arc = match strategy { StorageStrategy::InMemory => Arc::new(InMemoryRepository::new()), StorageStrategy::SQLite(path) => Arc::new(SQLiteRepository::new(path)), }; Self { inner } } pub fn inner(&self) -> Arc { self.inner.clone() } }