use models::repo::repo_branch_protection_policy; use models::repo::repo_branch_protection_whitelists::{self, WhitelistKind}; use models::repo::repo_tag_protection_policy; use models::repo::repo_tag_protection_whitelists; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use utoipa::ToSchema; use uuid::Uuid; use crate::service::GitService; use crate::share::errors::GitError; #[derive(Debug, Clone, Serialize, ToSchema)] pub struct BranchWhitelistDto { pub id: Uuid, pub policy_id: Uuid, pub kind: WhitelistKind, pub user_id: Option, pub user: Option, pub group_id: Option, pub created_at: OffsetDateTime, } impl From for BranchWhitelistDto { fn from(m: repo_branch_protection_whitelists::Model) -> Self { Self { id: m.id, policy_id: m.policy_id, kind: m.kind, user_id: m.user_id, user: None, group_id: m.group_id, created_at: OffsetDateTime::now_utc(), } } } #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct AddBranchWhitelistCmd { pub kind: WhitelistKind, pub user_id: Option, pub group_id: Option, } #[derive(Debug, Clone, Serialize, ToSchema)] pub struct TagWhitelistDto { pub policy_id: Uuid, pub user_id: i64, pub user: Option, } impl From for TagWhitelistDto { fn from(m: repo_tag_protection_whitelists::Model) -> Self { Self { policy_id: m.policy_id, user_id: m.user_id, user: None, } } } #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct AddTagWhitelistCmd { pub user_id: i64, } impl GitService { async fn embed_whitelist_users( &self, mut rows: Vec, ) -> Result, GitError> { let uids: Vec = rows.iter().filter_map(|r| r.user_id).collect(); if !uids.is_empty() { let map = self.user_dto_map(&uids).await?; for row in rows.iter_mut() { row.user = row.user_id.and_then(|u| map.get(&u).cloned()); } } Ok(rows) } pub(crate) async fn user_dto_map( &self, uids: &[i64], ) -> Result, GitError> { use models::users::user; let mut map = std::collections::HashMap::new(); if uids.is_empty() { return Ok(map); } let rows = user::Entity::find() .filter(user::Column::Uid.is_in(uids.to_vec())) .all(self.db_read()) .await?; for u in rows { map.insert(u.uid, space::services::spaces::UserDto::from(u)); } Ok(map) } async fn ensure_branch_policy( &self, policy_id: Uuid, ) -> Result { repo_branch_protection_policy::Entity::find_by_id(policy_id) .one(self.db_read()) .await? .ok_or(GitError::NotFound) } async fn ensure_tag_policy( &self, policy_id: Uuid, ) -> Result { repo_tag_protection_policy::Entity::find_by_id(policy_id) .one(self.db_read()) .await? .ok_or(GitError::NotFound) } pub async fn add_branch_whitelist( &self, repo_id: Uuid, policy_id: Uuid, cmd: AddBranchWhitelistCmd, ) -> Result { let policy = self.ensure_branch_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } if cmd.user_id.is_some() == cmd.group_id.is_some() { return Err(GitError::InvalidArgument( "exactly one of user_id or group_id must be set".to_string(), )); } let existing = repo_branch_protection_whitelists::Entity::find() .filter(repo_branch_protection_whitelists::Column::PolicyId.eq(policy_id)) .filter(repo_branch_protection_whitelists::Column::Kind.eq(cmd.kind)) .filter(repo_branch_protection_whitelists::Column::UserId.eq(cmd.user_id)) .filter(repo_branch_protection_whitelists::Column::GroupId.eq(cmd.group_id)) .one(self.db_read()) .await?; if let Some(row) = existing { return Ok(self .embed_whitelist_users(vec![row.into()]) .await? .into_iter() .next() .unwrap()); } let am = repo_branch_protection_whitelists::ActiveModel { id: Set(Uuid::now_v7()), policy_id: Set(policy_id), kind: Set(cmd.kind), user_id: Set(cmd.user_id), group_id: Set(cmd.group_id), }; let saved = am.insert(self.db_write()).await?; Ok(self .embed_whitelist_users(vec![saved.into()]) .await? .into_iter() .next() .unwrap()) } pub async fn remove_branch_whitelist( &self, repo_id: Uuid, policy_id: Uuid, whitelist_id: Uuid, ) -> Result<(), GitError> { let policy = self.ensure_branch_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } repo_branch_protection_whitelists::Entity::delete_by_id(whitelist_id) .filter(repo_branch_protection_whitelists::Column::PolicyId.eq(policy_id)) .exec(self.db_write()) .await?; Ok(()) } pub async fn list_branch_whitelists( &self, repo_id: Uuid, policy_id: Uuid, ) -> Result, GitError> { let policy = self.ensure_branch_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } let rows = repo_branch_protection_whitelists::Entity::find() .filter(repo_branch_protection_whitelists::Column::PolicyId.eq(policy_id)) .all(self.db_read()) .await?; self.embed_whitelist_users(rows.into_iter().map(Into::into).collect()) .await } pub async fn add_tag_whitelist( &self, repo_id: Uuid, policy_id: Uuid, cmd: AddTagWhitelistCmd, ) -> Result { let policy = self.ensure_tag_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } let existing = repo_tag_protection_whitelists::Entity::find_by_id((policy_id, cmd.user_id)) .one(self.db_read()) .await?; if let Some(row) = existing { let mut dto: TagWhitelistDto = row.into(); dto.user = self .user_dto_map(&[dto.user_id]) .await? .get(&dto.user_id) .cloned(); return Ok(dto); } let am = repo_tag_protection_whitelists::ActiveModel { policy_id: Set(policy_id), user_id: Set(cmd.user_id), }; let saved = TransactionTrait::transaction(self.db_write(), |txn| { Box::pin(async move { am.insert(txn).await.map_err(GitError::from) }) }) .await?; let mut dto: TagWhitelistDto = saved.into(); dto.user = self .user_dto_map(&[dto.user_id]) .await? .get(&dto.user_id) .cloned(); Ok(dto) } pub async fn remove_tag_whitelist( &self, repo_id: Uuid, policy_id: Uuid, user_id: i64, ) -> Result<(), GitError> { let policy = self.ensure_tag_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } repo_tag_protection_whitelists::Entity::delete_by_id((policy_id, user_id)) .exec(self.db_write()) .await?; Ok(()) } pub async fn list_tag_whitelists( &self, repo_id: Uuid, policy_id: Uuid, ) -> Result, GitError> { let policy = self.ensure_tag_policy(policy_id).await?; if policy.repo_id != repo_id { return Err(GitError::NotFound); } let rows = repo_tag_protection_whitelists::Entity::find() .filter(repo_tag_protection_whitelists::Column::PolicyId.eq(policy_id)) .all(self.db_read()) .await?; let mut out: Vec = rows.into_iter().map(Into::into).collect(); let uids: Vec = out.iter().map(|t| t.user_id).collect(); let map = self.user_dto_map(&uids).await?; for t in out.iter_mut() { t.user = map.get(&t.user_id).cloned(); } Ok(out) } }