use mongodb::{Client, Database, Collection, bson::doc, options::ClientOptions}; use anyhow::Result; use mongodb::bson::oid::ObjectId; use std::time::Duration; use crate::models::{ user::{User, UserRepository}, share::{Share, ShareRepository}, permission::Permission, }; #[derive(Clone)] pub struct MongoDb { database: Database, pub users: Collection, pub shares: 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"), 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"), 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"), 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) } }