Skip to content
Clone
e1df092
145 lines 4.9 KB Rust
use models::issue::issue_label;
use models::pull_request::{pull_request_label, pull_requests};
use sea_orm::ActiveValue::Set;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder};
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,
dir_id: Uuid,
reference: &str,
) -> Result<issue_label::Model, PullError> {
let reference = reference.trim();
let select = issue_label::Entity::find()
.filter(issue_label::Column::DirId.eq(dir_id))
.filter(issue_label::Column::IsArchived.eq(false));
let row = if let Ok(id) = Uuid::parse_str(reference) {
select.filter(issue_label::Column::Id.eq(id))
} else {
select.filter(issue_label::Column::Name.eq(reference))
}
.one(self.db_read())
.await?;
row.ok_or(PullError::BadRequest(format!(
"label `{reference}` not found in this repository directory"
)))
}
/// 可附加到本仓库合并请求的标签候选(同目录、未归档),供侧栏搜索下拉使用。
pub async fn list_label_options(
&self,
dir_id: Uuid,
) -> Result<Vec<issues::services::labels::LabelDto>, PullError> {
let rows = issue_label::Entity::find()
.filter(issue_label::Column::DirId.eq(dir_id))
.filter(issue_label::Column::IsArchived.eq(false))
.order_by_asc(issue_label::Column::Name)
.all(self.db_read())
.await?;
Ok(rows
.into_iter()
.map(issues::services::labels::LabelDto::from)
.collect())
}
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();
let rows = issue_label::Entity::find()
.filter(issue_label::Column::Id.is_in(ids))
.all(self.db_read())
.await?;
Ok(rows
.into_iter()
.map(issues::services::labels::LabelDto::from)
.collect())
}
pub async fn attach_label(
&self,
pull: &pull_requests::Model,
dir_id: Uuid,
caller: i64,
cmd: AttachLabelCmd,
) -> Result<Vec<issues::services::labels::LabelDto>, PullError> {
authz::require_unlocked(pull)?;
let label = self.resolve_label(dir_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() {
pull_request_label::ActiveModel {
id: Set(Uuid::now_v7()),
pull_request_id: Set(pull.id),
label_id: Set(label.id),
created_at: Set(OffsetDateTime::now_utc()),
}
.insert(self.db_write())
.await?;
self.record_event(
pull,
Some(caller),
EVENT_LABELED,
None,
Some(label.name.clone()),
)
.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 deleted = pull_request_label::Entity::delete_many()
.filter(pull_request_label::Column::PullRequestId.eq(pull.id))
.filter(pull_request_label::Column::LabelId.eq(label.id))
.exec(self.db_write())
.await?;
if deleted.rows_affected > 0 {
self.record_event(
pull,
Some(caller),
EVENT_UNLABELED,
None,
Some(label.name.clone()),
)
.await?;
}
self.list_labels(pull.id).await
}
}
use time::OffsetDateTime;