use futures::stream::TryStreamExt; use mongodb::Collection; use mongodb::{ bson::{doc, oid::ObjectId}, error::Error as MongoError, }; use serde::{Deserialize, Serialize}; use crate::models::medication::EncryptedFieldWire; /// Zero-knowledge health stat: the value/unit/type/notes are encrypted inside /// the opaque blob; only `recorded_at` stays plaintext (filterable/sortable). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HealthStatistic { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub user_id: String, /// Opaque client-encrypted blob (value, unit, type, notes inside). #[serde(rename = "encryptedData")] pub encrypted_data: EncryptedFieldWire, /// When the reading was taken (plaintext, filterable/sortable). pub recorded_at: String, } /// Wire response (what the API returns). #[derive(Debug, Serialize)] pub struct HealthStatResponse { pub id: String, pub user_id: String, pub encrypted_data: EncryptedFieldWire, pub recorded_at: String, } impl From for HealthStatResponse { fn from(s: HealthStatistic) -> Self { HealthStatResponse { id: s.id.map(|o| o.to_hex()).unwrap_or_default(), user_id: s.user_id, encrypted_data: s.encrypted_data, recorded_at: s.recorded_at, } } } #[derive(Clone)] pub struct HealthStatisticsRepository { collection: Collection, } impl HealthStatisticsRepository { pub fn new(db: &mongodb::Database) -> Self { Self { collection: db.collection("health_statistics"), } } pub async fn create( &self, stat: &HealthStatistic, ) -> Result, MongoError> { let result = self.collection.insert_one(stat, None).await?; let mut created = stat.clone(); created.id = result.inserted_id.as_object_id(); Ok(Some(created)) } pub async fn find_by_user(&self, user_id: &str) -> Result, MongoError> { let filter = doc! { "user_id": user_id }; let cursor = self.collection.find(filter, None).await?; cursor.try_collect().await } pub async fn find_by_id(&self, id: &ObjectId) -> Result, MongoError> { let filter = doc! { "_id": id }; self.collection.find_one(filter, None).await } pub async fn update_encrypted_data( &self, id: &ObjectId, encrypted_data: EncryptedFieldWire, ) -> Result, MongoError> { self.collection .find_one_and_update( doc! { "_id": id }, doc! { "$set": { "encryptedData.data": encrypted_data.data, "encryptedData.iv": encrypted_data.iv, "encryptedData.authTag": encrypted_data.auth_tag, }}, None, ) .await } pub async fn delete(&self, id: &ObjectId) -> Result { let filter = doc! { "_id": id }; let result = self.collection.delete_one(filter, None).await?; Ok(result.deleted_count > 0) } }