use anyhow::Result; use mongodb::bson::oid::ObjectId; use mongodb::{bson::doc, options::ClientOptions, Client, Collection, Database}; use std::time::Duration; use crate::models::{ medication::{Medication, MedicationDose, MedicationRepository, UpdateMedicationRequest}, user::{User, UserRepository}, }; #[derive(Clone)] pub struct MongoDb { database: Database, pub users: Collection, pub medications: Collection, pub medication_doses: Collection, } impl MongoDb { pub async fn new(uri: &str, db_name: &str) -> Result { eprintln!("[MongoDB] Starting connection to: {}", uri); // Parse the URI first let mut client_options = match ClientOptions::parse(uri).await { Ok(opts) => { eprintln!("[MongoDB] URI parsed successfully"); opts } Err(e) => { eprintln!("[MongoDB] ERROR: Failed to parse URI: {}", e); let error_msg = e.to_string().to_lowercase(); if error_msg.contains("dns") || error_msg.contains("resolution") || error_msg.contains("lookup") { eprintln!("[MongoDB] DNS RESOLUTION ERROR DETECTED!"); eprintln!("[MongoDB] Cannot resolve hostname in: {}", uri); eprintln!("[MongoDB] Error: {}", e); } eprintln!( "[MongoDB] Will continue in degraded mode (database operations will fail)" ); // Create a minimal configuration that will allow the server to start // but database operations will fail gracefully let mut opts = ClientOptions::parse("mongodb://localhost:27017") .await .map_err(|e| { anyhow::anyhow!("Failed to create fallback client options: {}", e) })?; opts.server_selection_timeout = Some(Duration::from_secs(1)); opts.connect_timeout = Some(Duration::from_secs(1)); let client = Client::with_options(opts) .map_err(|e| anyhow::anyhow!("Failed to create MongoDB client: {}", e))?; let database = client.database(db_name); return Ok(Self { users: database.collection("users"), medications: database.collection("medications"), medication_doses: database.collection("medication_doses"), database, }); } }; // Set connection timeout with retry logic client_options.server_selection_timeout = Some(Duration::from_secs(10)); client_options.connect_timeout = Some(Duration::from_secs(10)); eprintln!("[MongoDB] Connecting to server..."); let client = match Client::with_options(client_options) { Ok(c) => { eprintln!("[MongoDB] Client created successfully"); c } Err(e) => { eprintln!("[MongoDB] ERROR: Failed to create client: {}", e); eprintln!("[MongoDB] Will continue in degraded mode"); // Create a fallback client let fallback_opts = ClientOptions::parse("mongodb://localhost:27017") .await .map_err(|e| { anyhow::anyhow!("Failed to create fallback client options: {}", e) })?; let fallback_client = Client::with_options(fallback_opts) .map_err(|e| anyhow::anyhow!("Failed to create MongoDB client: {}", e))?; let database = fallback_client.database(db_name); return Ok(Self { users: database.collection("users"), medications: database.collection("medications"), medication_doses: database.collection("medication_doses"), database, }); } }; eprintln!("[MongoDB] Client created, selecting database..."); let database = client.database(db_name); eprintln!("[MongoDB] Database selected: {}", db_name); Ok(Self { users: database.collection("users"), medications: database.collection("medications"), medication_doses: database.collection("medication_doses"), database, }) } pub async fn health_check(&self) -> Result { eprintln!("[MongoDB] Health check: pinging database..."); match self.database.run_command(doc! { "ping": 1 }, None).await { Ok(_) => { eprintln!("[MongoDB] Health check: OK"); Ok("OK".to_string()) } Err(e) => { eprintln!("[MongoDB] Health check: FAILED - {}", e); // Return OK anyway to allow the server to continue running // The actual database operations will fail when needed Ok("DEGRADED".to_string()) } } } /// Get a reference to the underlying MongoDB Database /// This is needed for security services in Phase 2.6 pub fn get_database(&self) -> Database { self.database.clone() } // ===== User Methods ===== pub async fn create_user(&self, user: &User) -> Result> { let repo = UserRepository::new(self.users.clone()); Ok(repo.create(user).await?) } pub async fn find_user_by_email(&self, email: &str) -> Result> { let repo = UserRepository::new(self.users.clone()); Ok(repo.find_by_email(email).await?) } pub async fn find_user_by_id(&self, id: &ObjectId) -> Result> { let repo = UserRepository::new(self.users.clone()); Ok(repo.find_by_id(id).await?) } pub async fn update_user(&self, user: &User) -> Result<()> { let repo = UserRepository::new(self.users.clone()); repo.update(user).await?; Ok(()) } pub async fn update_last_active(&self, user_id: &ObjectId) -> Result<()> { let repo = UserRepository::new(self.users.clone()); repo.update_last_active(user_id).await?; Ok(()) } pub async fn delete_user(&self, user_id: &ObjectId) -> Result<()> { let repo = UserRepository::new(self.users.clone()); repo.delete(user_id).await?; Ok(()) } // ===== Medication Methods (Fixed for Phase 2.8) ===== pub async fn create_medication(&self, medication: &Medication) -> Result> { let repo = MedicationRepository::new(self.medications.clone()); let created = repo .create(medication.clone()) .await .map_err(|e| anyhow::anyhow!("Failed to create medication: {}", e))?; Ok(created.id) } pub async fn get_medication(&self, id: &str) -> Result> { let object_id = ObjectId::parse_str(id)?; let repo = MedicationRepository::new(self.medications.clone()); repo.find_by_id(&object_id) .await .map_err(|e| anyhow::anyhow!("Failed to get medication: {}", e)) } pub async fn list_medications( &self, user_id: &str, profile_id: Option<&str>, ) -> Result> { let repo = MedicationRepository::new(self.medications.clone()); if let Some(profile_id) = profile_id { Ok(repo .find_by_user_and_profile(user_id, profile_id) .await .map_err(|e| anyhow::anyhow!("Failed to list medications by profile: {}", e))?) } else { Ok(repo .find_by_user(user_id) .await .map_err(|e| anyhow::anyhow!("Failed to list medications: {}", e))?) } } pub async fn update_medication( &self, id: &str, updates: UpdateMedicationRequest, ) -> Result> { let object_id = ObjectId::parse_str(id)?; let repo = MedicationRepository::new(self.medications.clone()); repo.update(&object_id, updates) .await .map_err(|e| anyhow::anyhow!("Failed to update medication: {}", e)) } pub async fn delete_medication(&self, id: &str) -> Result { let object_id = ObjectId::parse_str(id)?; let repo = MedicationRepository::new(self.medications.clone()); repo.delete(&object_id) .await .map_err(|e| anyhow::anyhow!("Failed to delete medication: {}", e)) } pub async fn log_medication_dose(&self, dose: &MedicationDose) -> Result> { // Insert the dose into the medication_doses collection let result = self .medication_doses .insert_one(dose.clone(), None) .await .map_err(|e| anyhow::anyhow!("Failed to log dose: {}", e))?; Ok(result.inserted_id.as_object_id()) } }