use crate::AppService; use crate::error::AppError; use models::projects::{MemberRole, ProjectMember, project_audit_log, project_members}; use models::repos::repo; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; use uuid::Uuid; #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct TransferRepoParams { pub target_project_name: String, } #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct TransferRepoResponse { pub repo_id: Uuid, pub old_project_name: String, pub new_project_name: String, pub repo_name: String, } impl AppService { pub async fn transfer_repo( &self, ctx: &Session, source_project_name: String, repo_name: String, params: TransferRepoParams, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let source_project = self .utils_find_project_by_name(source_project_name.clone()) .await?; let target_project = self .utils_find_project_by_name(params.target_project_name.clone()) .await?; let source_member = ProjectMember::find() .filter(project_members::Column::Project.eq(source_project.id)) .filter(project_members::Column::User.eq(user_uid)) .one(&self.db) .await? .ok_or(AppError::PermissionDenied)?; if source_member .scope_role() .map_err(|_| AppError::InternalError)? != MemberRole::Owner && source_member .scope_role() .map_err(|_| AppError::InternalError)? != MemberRole::Admin { return Err(AppError::PermissionDenied); } let target_member = ProjectMember::find() .filter(project_members::Column::Project.eq(target_project.id)) .filter(project_members::Column::User.eq(user_uid)) .one(&self.db) .await? .ok_or(AppError::PermissionDenied)?; if target_member .scope_role() .map_err(|_| AppError::InternalError)? != MemberRole::Owner && target_member .scope_role() .map_err(|_| AppError::InternalError)? != MemberRole::Admin { return Err(AppError::PermissionDenied); } let repository = repo::Entity::find() .filter(repo::Column::RepoName.eq(repo_name.clone())) .one(&self.db) .await? .ok_or(AppError::NotFound("Repository not found".to_string()))?; if repository.project != source_project.id { return Err(AppError::NotFound( "Repository not found in source project".to_string(), )); } let target_repo_exists = repo::Entity::find() .filter(repo::Column::RepoName.eq(repo_name.clone())) .one(&self.db) .await?; if target_repo_exists.is_some() { return Err(AppError::InternalServerError( "Repository with same name already exists in target project".to_string(), )); } let txn = self.db.begin().await?; let old_project_name = source_project.name.clone(); let mut active_repo: repo::ActiveModel = repository.clone().into(); active_repo.project = Set(target_project.id); let updated_repo = active_repo.update(&txn).await?; let source_log = project_audit_log::ActiveModel { project: Set(source_project.id), actor: Set(user_uid), action: Set("repo_transfer_out".to_string()), details: Set(Some(serde_json::json!({ "repo_id": repository.id, "repo_name": repo_name, "target_project": target_project.name, }))), created_at: Set(chrono::Utc::now()), ..Default::default() }; source_log.insert(&txn).await?; let target_log = project_audit_log::ActiveModel { project: Set(target_project.id), actor: Set(user_uid), action: Set("repo_transfer_in".to_string()), details: Set(Some(serde_json::json!({ "repo_id": repository.id, "repo_name": repo_name, "source_project": source_project.name, }))), created_at: Set(chrono::Utc::now()), ..Default::default() }; target_log.insert(&txn).await?; txn.commit().await?; let _ = self .project_log_activity( source_project.id, Some(updated_repo.id), user_uid, super::activity::ActivityLogParams { event_type: "repo_transfer_out".to_string(), title: format!( "{} transferred repository '{}' to project '{}'", user_uid, repo_name, target_project.name ), repo_id: Some(updated_repo.id), content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({ "repo_name": repo_name, "target_project": target_project.name, })), is_private: false, }, ) .await; let _ = self .project_log_activity( target_project.id, Some(updated_repo.id), user_uid, super::activity::ActivityLogParams { event_type: "repo_transfer_in".to_string(), title: format!( "{} transferred repository '{}' from project '{}'", user_uid, repo_name, source_project.name ), repo_id: Some(updated_repo.id), content: None, event_id: None, event_sub_id: None, metadata: Some(serde_json::json!({ "repo_name": repo_name, "source_project": source_project.name, })), is_private: false, }, ) .await; Ok(TransferRepoResponse { repo_id: updated_repo.id, old_project_name, new_project_name: target_project.name, repo_name, }) } }