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}, permission::Permission, share::{Share, ShareRepository}, user::{User, UserRepository}, }; #[derive(Clone)] pub struct MongoDb { database: Database, pub users: Collection, pub shares: 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"), shares: database.collection("shares"), 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"), shares: database.collection("shares"), 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"), shares: database.collection("shares"), 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(()) } // ===== Share Methods ===== pub async fn create_share(&self, share: &Share) -> Result> { let repo = ShareRepository::new(self.shares.clone()); Ok(repo.create(share).await?) } pub async fn get_share(&self, id: &str) -> Result> { let object_id = ObjectId::parse_str(id)?; let repo = ShareRepository::new(self.shares.clone()); Ok(repo.find_by_id(&object_id).await?) } pub async fn list_shares_for_user(&self, user_id: &str) -> Result> { let object_id = ObjectId::parse_str(user_id)?; let repo = ShareRepository::new(self.shares.clone()); Ok(repo.find_by_target(&object_id).await?) } pub async fn update_share(&self, share: &Share) -> Result<()> { let repo = ShareRepository::new(self.shares.clone()); repo.update(share).await?; Ok(()) } pub async fn delete_share(&self, id: &str) -> Result<()> { let object_id = ObjectId::parse_str(id)?; let repo = ShareRepository::new(self.shares.clone()); repo.delete(&object_id).await?; Ok(()) } // ===== Permission Methods ===== pub async fn check_user_permission( &self, user_id: &str, resource_type: &str, resource_id: &str, permission: &str, ) -> Result { let user_oid = ObjectId::parse_str(user_id)?; let resource_oid = ObjectId::parse_str(resource_id)?; let repo = ShareRepository::new(self.shares.clone()); let shares = repo.find_by_target(&user_oid).await?; for share in shares { if share.resource_type == resource_type && share.resource_id.as_ref() == Some(&resource_oid) && share.active && !share.is_expired() { // Check if share has the required permission let perm = match permission.to_lowercase().as_str() { "read" => Permission::Read, "write" => Permission::Write, "delete" => Permission::Delete, "share" => Permission::Share, "admin" => Permission::Admin, _ => return Ok(false), }; if share.has_permission(&perm) { return Ok(true); } } } Ok(false) } /// Check permission using a simplified interface pub async fn check_permission( &self, user_id: &str, resource_id: &str, permission: &str, ) -> Result { // For now, check all resource types let resource_types = ["profiles", "health_data", "lab_results", "medications"]; for resource_type in resource_types { if self .check_user_permission(user_id, resource_type, resource_id, permission) .await? { return Ok(true); } } Ok(false) } // ===== 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()) } pub async fn get_medication_adherence( &self, medication_id: &str, days: i64, ) -> Result { let repo = MedicationRepository::new(self.medications.clone()); repo.calculate_adherence(medication_id, days) .await .map_err(|e| anyhow::anyhow!("Failed to calculate adherence: {}", e)) } }