Files
status-monitor/server/src/storage/mod.rs
T
2025-07-15 01:34:27 +02:00

99 lines
2.8 KiB
Rust

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<Utc>,
pub last_seen: DateTime<Utc>,
pub message_count: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UptimeMessage {
pub agent: String,
pub uptime: f64,
pub last_seen: DateTime<Utc>,
}
impl Into<UptimeMessage> 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<Self, Self::Error> {
let first_seen: DateTime<Utc> = row.get::<_, String>(1)?.parse().map_err(|e| {
rusqlite::Error::FromSqlConversionFailure(1, rusqlite::types::Type::Text, Box::new(e))
})?;
let last_seen: DateTime<Utc> = 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<DateTime<Utc>>,
) -> anyhow::Result<Vec<StatusMessage>>;
async fn get_agents(&self) -> anyhow::Result<Vec<UptimeMessage>>;
}
pub enum StorageStrategy {
InMemory,
SQLite(String),
}
pub struct StorageRepositoryImpl {
inner: Arc<dyn StorageRepository>,
}
impl StorageRepositoryImpl {
pub fn new(strategy: StorageStrategy) -> Self {
let inner: Arc<dyn StorageRepository> = match strategy {
StorageStrategy::InMemory => Arc::new(InMemoryRepository::new()),
StorageStrategy::SQLite(path) => Arc::new(SQLiteRepository::new(path)),
};
Self { inner }
}
pub fn inner(&self) -> Arc<dyn StorageRepository> {
self.inner.clone()
}
}