This commit is contained in:
2026-04-09 14:55:37 +02:00
parent 8be455ba98
commit 4a85efc270
30 changed files with 4895 additions and 1 deletions
+113
View File
@@ -0,0 +1,113 @@
use std::{sync::Arc, time::Instant};
use chrono::{DateTime, Utc};
use redis::Client as RedisClient;
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use tokio::sync::RwLock;
use uuid::Uuid;
use crate::{config::AppConfig, error::AppError};
#[derive(Clone)]
pub struct AppState {
pub config: AppConfig,
pub started_at: Instant,
pub database_pool: Option<PgPool>,
pub redis_client: Option<RedisClient>,
current_user_id: Uuid,
session: Arc<RwLock<Option<SessionSnapshot>>>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct SessionStartRequest {
pub locale_code: Option<String>,
pub device_platform: Option<String>,
pub device_model: Option<String>,
pub os_version: Option<String>,
pub app_version: Option<String>,
pub experiment_variant: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SessionSnapshot {
pub session_id: Uuid,
pub user_id: Uuid,
pub started_at: DateTime<Utc>,
pub ended_at: Option<DateTime<Utc>>,
pub experiment_variant: String,
pub app_version: String,
pub device_model: Option<String>,
pub os_version: Option<String>,
pub locale_code: String,
pub device_platform: String,
}
impl AppState {
pub fn new(
config: AppConfig,
database_pool: Option<PgPool>,
redis_client: Option<RedisClient>,
) -> Self {
Self {
config,
started_at: Instant::now(),
database_pool,
redis_client,
current_user_id: Uuid::new_v4(),
session: Arc::new(RwLock::new(None)),
}
}
pub async fn start_session(&self, request: SessionStartRequest) -> SessionSnapshot {
let session = SessionSnapshot {
session_id: Uuid::new_v4(),
user_id: self.current_user_id,
started_at: Utc::now(),
ended_at: None,
experiment_variant: request
.experiment_variant
.unwrap_or_else(|| self.config.default_experiment_variant.clone()),
app_version: request
.app_version
.unwrap_or_else(|| self.config.app_version.clone()),
device_model: request.device_model,
os_version: request.os_version,
locale_code: request
.locale_code
.unwrap_or_else(|| self.config.default_locale.clone()),
device_platform: request
.device_platform
.unwrap_or_else(|| self.config.default_device_platform.clone()),
};
*self.session.write().await = Some(session.clone());
session
}
pub async fn current_session(&self) -> Option<SessionSnapshot> {
self.session.read().await.clone()
}
pub async fn end_session(&self) -> Result<SessionSnapshot, AppError> {
let mut guard = self.session.write().await;
let mut session = guard
.clone()
.ok_or_else(|| AppError::not_found("No active session"))?;
session.ended_at = Some(Utc::now());
*guard = Some(session.clone());
Ok(session)
}
pub fn database_ready(&self) -> bool {
self.database_pool.is_some()
}
pub fn redis_ready(&self) -> bool {
self.redis_client.is_some()
}
pub fn uptime_ms(&self) -> u128 {
self.started_at.elapsed().as_millis()
}
}