use crate::auth::jwt::Claims; use crate::config::AppState; use crate::models::health_stats::HealthStatistic; use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, Extension, Json, }; use mongodb::bson::oid::ObjectId; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct CreateHealthStatRequest { pub stat_type: String, pub value: serde_json::Value, // Support complex values like blood pressure pub unit: String, pub notes: Option, pub recorded_at: Option, } #[derive(Debug, Deserialize)] pub struct UpdateHealthStatRequest { pub value: Option, pub unit: Option, pub notes: Option, } #[derive(Debug, Deserialize)] pub struct HealthTrendsQuery { pub stat_type: String, pub period: Option, // "7d", "30d", etc. } pub async fn create_health_stat( State(state): State, Extension(claims): Extension, Json(req): Json, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); // Convert complex value to f64 or store as string let value_num = match req.value { serde_json::Value::Number(n) => n.as_f64().unwrap_or(0.0), serde_json::Value::Object(_) => { // For complex objects like blood pressure, use a default 0.0 } _ => 0.0, }; let stat = HealthStatistic { id: None, user_id: claims.sub.clone(), stat_type: req.stat_type, value: value_num, unit: req.unit, notes: req.notes, recorded_at: req.recorded_at.unwrap_or_else(|| { use chrono::Utc; Utc::now().to_rfc3339() }), }; match repo.create(&stat).await { Ok(created) => (StatusCode::CREATED, Json(created)).into_response(), Err(e) => { eprintln!("Error creating health stat: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to create health stat", ) .into_response() } } } pub async fn list_health_stats( State(state): State, Extension(claims): Extension, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); match repo.find_by_user(&claims.sub).await { Ok(stats) => (StatusCode::OK, Json(stats)).into_response(), Err(e) => { eprintln!("Error fetching health stats: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch health stats", ) .into_response() } } } pub async fn get_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => return (StatusCode::BAD_REQUEST, "Invalid ID").into_response(), }; match repo.find_by_id(&object_id).await { Ok(Some(stat)) => { if stat.user_id != claims.sub { return (StatusCode::FORBIDDEN, "Access denied").into_response(); } (StatusCode::OK, Json(stat)).into_response() } Ok(None) => (StatusCode::NOT_FOUND, "Health stat not found").into_response(), Err(e) => { eprintln!("Error fetching health stat: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch health stat", ) .into_response() } } } pub async fn update_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, Json(req): Json, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => return (StatusCode::BAD_REQUEST, "Invalid ID").into_response(), }; let mut stat = match repo.find_by_id(&object_id).await { Ok(Some(s)) => s, Ok(None) => return (StatusCode::NOT_FOUND, "Health stat not found").into_response(), Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch health stat", ) .into_response() } }; if stat.user_id != claims.sub { return (StatusCode::FORBIDDEN, "Access denied").into_response(); } if let Some(value) = req.value { let value_num = match value { serde_json::Value::Number(n) => n.as_f64().unwrap_or(0.0), _ => 0.0, }; stat.value = value_num; } if let Some(unit) = req.unit { stat.unit = unit; } if let Some(notes) = req.notes { stat.notes = Some(notes); } match repo.update(&object_id, &stat).await { Ok(Some(updated)) => (StatusCode::OK, Json(updated)).into_response(), Ok(None) => (StatusCode::NOT_FOUND, "Failed to update").into_response(), Err(_) => ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to update health stat", ) .into_response(), } } pub async fn delete_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => return (StatusCode::BAD_REQUEST, "Invalid ID").into_response(), }; let stat = match repo.find_by_id(&object_id).await { Ok(Some(s)) => s, Ok(None) => return (StatusCode::NOT_FOUND, "Health stat not found").into_response(), Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch health stat", ) .into_response() } }; if stat.user_id != claims.sub { return (StatusCode::FORBIDDEN, "Access denied").into_response(); } match repo.delete(&object_id).await { Ok(true) => StatusCode::NO_CONTENT.into_response(), _ => ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to delete health stat", ) .into_response(), } } pub async fn get_health_trends( State(state): State, Extension(claims): Extension, Query(query): Query, ) -> impl IntoResponse { let repo = state.health_stats_repo.as_ref().unwrap(); match repo.find_by_user(&claims.sub).await { Ok(stats) => { // Filter by stat_type let filtered: Vec = stats .into_iter() .filter(|s| s.stat_type == query.stat_type) .collect(); // Calculate basic trend statistics if filtered.is_empty() { return ( StatusCode::OK, Json(serde_json::json!({ "stat_type": query.stat_type, "count": 0, "data": [] })), ) .into_response(); } let values: Vec = filtered.iter().map(|s| s.value).collect(); let avg = values.iter().sum::() / values.len() as f64; let min = values.iter().fold(f64::INFINITY, |a, &b| a.min(b)); let max = values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)); let response = serde_json::json!({ "stat_type": query.stat_type, "count": filtered.len(), "average": avg, "min": min, "max": max, "data": filtered }); (StatusCode::OK, Json(response)).into_response() } Err(e) => { eprintln!("Error fetching health trends: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch health trends", ) .into_response() } } }