main
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<issues::services::labels::LabelDto, PullError> {
Ok(self.issues.resolve_space_label(space_id, reference).await?)
}
pub async fn list_label_options(
&self,
space_id: Uuid,
) -> Result<Vec<issues::services::labels::LabelDto>, PullError> {
Ok(self.issues.space_labels(space_id).await?)
}
pub async fn list_labels(
&self,
pull_id: Uuid,
) -> Result<Vec<issues::services::labels::LabelDto>, 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<Uuid> = 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<Vec<issues::services::labels::LabelDto>, 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<Vec<issues::services::labels::LabelDto>, 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;