use models::pull_request::{pull_request_label, pull_requests}; use sea_orm::ActiveValue::Set; use sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, TransactionTrait, }; use serde::Deserialize; use utoipa::ToSchema; use uuid::Uuid; use crate::errors::PullError; use crate::services::events::{EVENT_LABELED, EVENT_UNLABELED}; use crate::services::service::PullService; use crate::share::authz; #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct AttachLabelCmd { /// 标签 UUID 或名称(同一仓库目录内的标签)。 pub label: String, } impl PullService { async fn resolve_label( &self, space_id: Uuid, reference: &str, ) -> Result { Ok(self.issues.resolve_space_label(space_id, reference).await?) } pub async fn list_label_options( &self, space_id: Uuid, ) -> Result, PullError> { Ok(self.issues.space_labels(space_id).await?) } pub async fn list_labels( &self, pull_id: Uuid, ) -> Result, PullError> { let links = pull_request_label::Entity::find() .filter(pull_request_label::Column::PullRequestId.eq(pull_id)) .order_by_asc(pull_request_label::Column::CreatedAt) .all(self.db_read()) .await?; if links.is_empty() { return Ok(Vec::new()); } let ids: Vec = links.iter().map(|link| link.label_id).collect(); Ok(self.issues.labels_by_ids(ids).await?) } pub async fn attach_label( &self, pull: &pull_requests::Model, space_id: Uuid, caller: i64, cmd: AttachLabelCmd, ) -> Result, PullError> { authz::require_unlocked(pull)?; let label = self.resolve_label(space_id, &cmd.label).await?; let existing = pull_request_label::Entity::find() .filter(pull_request_label::Column::PullRequestId.eq(pull.id)) .filter(pull_request_label::Column::LabelId.eq(label.id)) .one(self.db_read()) .await?; if existing.is_none() { let event_pull = pull.clone(); let this = self.clone(); self.db_write() .transaction::<_, (), PullError>(move |tx| { Box::pin(async move { pull_request_label::ActiveModel { id: Set(Uuid::now_v7()), pull_request_id: Set(event_pull.id), label_id: Set(label.id), created_at: Set(OffsetDateTime::now_utc()), } .insert(tx) .await?; this.record_event_with( tx, &event_pull, Some(caller), EVENT_LABELED, None, Some(label.name), ) .await?; Ok(()) }) }) .await?; } self.list_labels(pull.id).await } pub async fn detach_label( &self, pull: &pull_requests::Model, dir_id: Uuid, caller: i64, label_ref: &str, ) -> Result, PullError> { authz::require_unlocked(pull)?; let label = self.resolve_label(dir_id, label_ref).await?; let event_pull = pull.clone(); let this = self.clone(); self.db_write() .transaction::<_, (), PullError>(move |tx| { Box::pin(async move { let deleted = pull_request_label::Entity::delete_many() .filter(pull_request_label::Column::PullRequestId.eq(event_pull.id)) .filter(pull_request_label::Column::LabelId.eq(label.id)) .exec(tx) .await?; if deleted.rows_affected > 0 { this.record_event_with( tx, &event_pull, Some(caller), EVENT_UNLABELED, None, Some(label.name), ) .await?; } Ok(()) }) }) .await?; self.list_labels(pull.id).await } } use time::OffsetDateTime;