use crate::auth::jwt::Claims; use crate::config::AppState; use crate::models::health_stats::{HealthStatResponse, HealthStatistic}; use crate::models::medication::EncryptedFieldWire; 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 encrypted_data: EncryptedFieldWire, /// Which profile this stat belongs to. pub profile_id: String, pub recorded_at: Option, } #[derive(Debug, Deserialize, Default)] pub struct ListHealthStatsQuery { pub profile_id: Option, } #[derive(Debug, Deserialize)] pub struct UpdateHealthStatRequest { pub encrypted_data: EncryptedFieldWire, } pub async fn create_health_stat( State(state): State, Extension(claims): Extension, Json(req): Json, ) -> impl IntoResponse { let repo = match state.health_stats_repo.as_ref() { Some(r) => r, None => { return ( StatusCode::SERVICE_UNAVAILABLE, Json(serde_json::json!({ "error": "health stats unavailable" })), ) .into_response() } }; let stat = HealthStatistic { id: None, user_id: claims.sub.clone(), profile_id: req.profile_id, encrypted_data: req.encrypted_data, recorded_at: req .recorded_at .unwrap_or_else(|| chrono::Utc::now().to_rfc3339()), }; match repo.create(&stat).await { Ok(Some(created)) => { (StatusCode::CREATED, Json(HealthStatResponse::from(created))).into_response() } Ok(None) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "failed to create" })), ) .into_response(), Err(e) => { eprintln!("Error creating health stat: {:?}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .into_response() } } } pub async fn list_health_stats( State(state): State, Extension(claims): Extension, Query(query): Query, ) -> impl IntoResponse { let repo = match state.health_stats_repo.as_ref() { Some(r) => r, None => { return ( StatusCode::SERVICE_UNAVAILABLE, Json(serde_json::json!({ "error": "health stats unavailable" })), ) .into_response() } }; match repo .find_by_user_filtered(&claims.sub, query.profile_id.as_deref()) .await { Ok(stats) => { let resp: Vec = stats.into_iter().map(Into::into).collect(); (StatusCode::OK, Json(resp)).into_response() } Err(_) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .into_response(), } } pub async fn get_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, ) -> impl IntoResponse { let repo = match state.health_stats_repo.as_ref() { Some(r) => r, None => { return ( StatusCode::SERVICE_UNAVAILABLE, Json(serde_json::json!({ "error": "health stats unavailable" })), ) .into_response() } }; let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => { return ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "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(HealthStatResponse::from(stat))).into_response() } Ok(None) => ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "not found" })), ) .into_response(), Err(_) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .into_response(), } } pub async fn update_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, Json(req): Json, ) -> impl IntoResponse { let repo = match state.health_stats_repo.as_ref() { Some(r) => r, None => { return ( StatusCode::SERVICE_UNAVAILABLE, Json(serde_json::json!({ "error": "health stats unavailable" })), ) .into_response() } }; let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => { return ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "invalid id" })), ) .into_response() } }; let existing = match repo.find_by_id(&object_id).await { Ok(Some(s)) => s, Ok(None) => { return ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "not found" })), ) .into_response() } Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .into_response() } }; if existing.user_id != claims.sub { return (StatusCode::FORBIDDEN, "Access denied").into_response(); } match repo .update_encrypted_data(&object_id, req.encrypted_data) .await { Ok(Some(updated)) => { (StatusCode::OK, Json(HealthStatResponse::from(updated))).into_response() } Ok(None) => ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "not found" })), ) .into_response(), Err(_) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .into_response(), } } pub async fn delete_health_stat( State(state): State, Extension(claims): Extension, Path(id): Path, ) -> impl IntoResponse { let repo = match state.health_stats_repo.as_ref() { Some(r) => r, None => { return ( StatusCode::SERVICE_UNAVAILABLE, Json(serde_json::json!({ "error": "health stats unavailable" })), ) .into_response() } }; let object_id = match ObjectId::parse_str(&id) { Ok(oid) => oid, Err(_) => { return ( StatusCode::BAD_REQUEST, Json(serde_json::json!({ "error": "invalid id" })), ) .into_response() } }; let stat = match repo.find_by_id(&object_id).await { Ok(Some(s)) => s, Ok(None) => { return ( StatusCode::NOT_FOUND, Json(serde_json::json!({ "error": "not found" })), ) .into_response() } Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": "database error" })), ) .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, Json(serde_json::json!({ "error": "database error" })), ) .into_response(), } }