use futures::stream::TryStreamExt; use mongodb::{ bson::{doc, oid::ObjectId}, options::FindOptions, Database, }; use crate::common::errors::AppError; use crate::developers::models::Developer; #[async_trait::async_trait] pub trait DeveloperRepository { async fn find_all_by_user(&self, user_id: &ObjectId) -> Result, AppError>; async fn find_by_id(&self, dev_id: &ObjectId, user_id: &ObjectId) -> Result, AppError>; async fn search_by_name(&self, user_id: &ObjectId, query: &str) -> Result, AppError>; async fn create(&self, user_id: &ObjectId, name: &str, email: &str, skills: Vec) -> Result; async fn update(&self, dev_id: &ObjectId, user_id: &ObjectId, name: &str, email: &str, skills: Vec) -> Result<(), AppError>; async fn delete(&self, dev_id: &ObjectId, user_id: &ObjectId) -> Result<(), AppError>; async fn ensure_seeded(&self, user_id: &ObjectId) -> Result<(), AppError>; } #[derive(Clone)] pub struct MongoDeveloperRepository { db: Database, } impl MongoDeveloperRepository { pub fn new(db: Database) -> Self { Self { db } } } #[async_trait::async_trait] impl DeveloperRepository for MongoDeveloperRepository { async fn find_all_by_user(&self, user_id: &ObjectId) -> Result, AppError> { let collection = self.db.collection::("developers"); let filter = doc! { "user_id": user_id }; let find_options = FindOptions::builder().sort(doc! { "created_at": -1 }).build(); let mut cursor = collection.find(filter).with_options(find_options).await?; let mut developers = Vec::new(); while let Some(dev) = cursor.try_next().await? { developers.push(dev); } Ok(developers) } async fn find_by_id(&self, dev_id: &ObjectId, user_id: &ObjectId) -> Result, AppError> { let collection = self.db.collection::("developers"); let filter = doc! { "_id": dev_id, "user_id": user_id }; let dev = collection.find_one(filter).await?; Ok(dev) } async fn search_by_name(&self, user_id: &ObjectId, query: &str) -> Result, AppError> { let collection = self.db.collection::("developers"); // Regex search case-insensitive on developer name let filter = doc! { "user_id": user_id, "name": { "$regex": query, "$options": "i" } }; let find_options = FindOptions::builder().limit(10).build(); let mut cursor = collection.find(filter).with_options(find_options).await?; let mut developers = Vec::new(); while let Some(dev) = cursor.try_next().await? { developers.push(dev); } Ok(developers) } async fn create(&self, user_id: &ObjectId, name: &str, email: &str, skills: Vec) -> Result { let collection = self.db.collection::("developers"); let new_dev = Developer { id: None, user_id: *user_id, name: name.to_string(), email: email.to_string(), skills, created_at: chrono::Utc::now(), }; let insert_result = collection.insert_one(new_dev.clone()).await?; let mut dev = new_dev; dev.id = Some(insert_result.inserted_id.as_object_id().expect("Inserted ID is ObjectId")); Ok(dev) } async fn update(&self, dev_id: &ObjectId, user_id: &ObjectId, name: &str, email: &str, skills: Vec) -> Result<(), AppError> { let collection = self.db.collection::("developers"); let filter = doc! { "_id": dev_id, "user_id": user_id }; let update = doc! { "$set": { "name": name, "email": email, "skills": skills } }; let result = collection.update_one(filter, update).await?; if result.matched_count == 0 { return Err(AppError::Unauthorized("Developer not found or not owned by user".to_string())); } Ok(()) } async fn delete(&self, dev_id: &ObjectId, user_id: &ObjectId) -> Result<(), AppError> { let collection = self.db.collection::("developers"); let filter = doc! { "_id": dev_id, "user_id": user_id }; let result = collection.delete_one(filter).await?; if result.deleted_count == 0 { return Err(AppError::Unauthorized("Developer not found or not owned by user".to_string())); } Ok(()) } async fn ensure_seeded(&self, user_id: &ObjectId) -> Result<(), AppError> { let devs = self.find_all_by_user(user_id).await?; if devs.is_empty() { let _ = self.create(user_id, "Alice Vance", "alice@example.com", vec!["Rust".to_string(), "Axum".to_string()]).await; let _ = self.create(user_id, "Bob Carter", "bob@example.com", vec!["Tailwind".to_string(), "JavaScript".to_string()]).await; let _ = self.create(user_id, "Charlie Smith", "charlie@example.com", vec!["HTML".to_string(), "CSS".to_string()]).await; } Ok(()) } }