gitdataai/libs/service/project/labels.rs
2026-04-14 19:02:01 +08:00

355 lines
12 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::projects::{MemberRole, project_audit_log, project_label};
use models::system::label;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct CreateLabelParams {
pub name: String,
pub color: String,
pub description: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct UpdateLabelParams {
pub name: Option<String>,
pub color: Option<String>,
pub description: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct LabelResponse {
pub id: i64,
pub project_uid: Uuid,
pub name: String,
pub color: String,
pub description: Option<String>,
pub created_at: chrono::DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct LabelListResponse {
pub labels: Vec<LabelResponse>,
pub total: usize,
}
impl AppService {
pub async fn project_create_label(
&self,
project_name: String,
params: CreateLabelParams,
ctx: &Session,
) -> Result<LabelResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project = self.utils_find_project_by_name(project_name).await?;
self.utils_check_project_permission(
&project.id,
user_uid,
&[MemberRole::Admin, MemberRole::Owner],
)
.await?;
// Create the label in system::label table
let label_model = label::ActiveModel {
project: Set(project.id),
name: Set(params.name.clone()),
color: Set(params.color.clone()),
..Default::default()
};
let created_system_label = label_model.insert(&self.db).await?;
// Create the project-label relation
let project_label = project_label::ActiveModel {
project: Set(project.id),
label: Set(created_system_label.id),
relation_at: Set(Utc::now()),
..Default::default()
};
let created_project_label = project_label.insert(&self.db).await?;
let _ = self
.project_log_activity(
project.id,
None,
user_uid,
super::activity::ActivityLogParams {
event_type: "label_create".to_string(),
title: format!("{} created label '{}'", user_uid, params.name),
repo_id: None,
content: None,
event_id: None,
event_sub_id: Some(created_project_label.id),
metadata: Some(serde_json::json!({
"label_id": created_project_label.id,
"label_name": params.name,
"color": params.color,
})),
is_private: false,
},
)
.await;
let log = project_audit_log::ActiveModel {
project: Set(project.id),
actor: Set(user_uid),
action: Set("label_create".to_string()),
details: Set(Some(serde_json::json!({
"label_id": created_project_label.id,
"label_name": params.name,
"color": params.color,
}))),
created_at: Set(Utc::now()),
..Default::default()
};
log.insert(&self.db).await?;
Ok(LabelResponse {
id: created_project_label.id,
project_uid: project.id,
name: params.name,
color: params.color,
description: params.description,
created_at: created_project_label.relation_at,
})
}
pub async fn project_get_labels(
&self,
project_name: String,
) -> Result<LabelListResponse, AppError> {
let project = self.utils_find_project_by_name(project_name).await?;
// Find all project-label relations
let project_label_relations = project_label::Entity::find()
.filter(project_label::Column::Project.eq(project.id))
.all(&self.db)
.await?;
if project_label_relations.is_empty() {
return Ok(LabelListResponse {
labels: vec![],
total: 0,
});
}
// Get all label IDs
let label_ids: Vec<i64> = project_label_relations.iter().map(|r| r.label).collect();
// Fetch label details
let labels = label::Entity::find()
.filter(label::Column::Id.is_in(label_ids))
.all(&self.db)
.await?;
let total = labels.len();
let labels: Vec<LabelResponse> = project_label_relations
.into_iter()
.filter_map(|relation| {
labels
.iter()
.find(|l| l.id == relation.label)
.map(|l| LabelResponse {
id: relation.id,
project_uid: relation.project,
name: l.name.clone(),
color: l.color.clone(),
description: None, // system::label doesn't have description
created_at: relation.relation_at,
})
})
.collect();
Ok(LabelListResponse { labels, total })
}
pub async fn project_get_label(&self, label_id: i64) -> Result<LabelResponse, AppError> {
let project_label = project_label::Entity::find_by_id(label_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Label not found".to_string()))?;
let system_label = label::Entity::find_by_id(project_label.label)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Label not found".to_string()))?;
Ok(LabelResponse {
id: project_label.id,
project_uid: project_label.project,
name: system_label.name,
color: system_label.color,
description: None,
created_at: project_label.relation_at,
})
}
pub async fn project_update_label(
&self,
label_id: i64,
params: UpdateLabelParams,
ctx: &Session,
) -> Result<LabelResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project_label = project_label::Entity::find_by_id(label_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Label not found".to_string()))?;
self.utils_check_project_permission(
&project_label.project,
user_uid,
&[MemberRole::Admin, MemberRole::Owner],
)
.await?;
let system_label = label::Entity::find_by_id(project_label.label)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Label not found".to_string()))?;
let mut active_label: label::ActiveModel = system_label.into();
let updated_name = params.name.is_some();
let updated_color = params.color.is_some();
let updated_description = params.description.is_some();
if let Some(name) = params.name {
active_label.name = Set(name);
}
if let Some(color) = params.color {
active_label.color = Set(color);
}
let updated_system_label = active_label.update(&self.db).await?;
let _ = self
.project_log_activity(
project_label.project,
None,
user_uid,
super::activity::ActivityLogParams {
event_type: "label_update".to_string(),
title: format!("{} updated label '{}'", user_uid, updated_system_label.name),
repo_id: None,
content: None,
event_id: None,
event_sub_id: Some(label_id),
metadata: Some(serde_json::json!({
"label_id": label_id,
"updated_fields": {
"name": updated_name,
"color": updated_color,
"description": updated_description,
}
})),
is_private: false,
},
)
.await;
let log = project_audit_log::ActiveModel {
project: Set(project_label.project),
actor: Set(user_uid),
action: Set("label_update".to_string()),
details: Set(Some(serde_json::json!({
"label_id": label_id,
"updated_fields": {
"name": updated_name,
"color": updated_color,
"description": updated_description,
}
}))),
created_at: Set(Utc::now()),
..Default::default()
};
log.insert(&self.db).await?;
Ok(LabelResponse {
id: project_label.id,
project_uid: project_label.project,
name: updated_system_label.name,
color: updated_system_label.color,
description: params.description,
created_at: project_label.relation_at,
})
}
pub async fn project_delete_label(&self, label_id: i64, ctx: &Session) -> Result<(), AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project_label = project_label::Entity::find_by_id(label_id)
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Label not found".to_string()))?;
self.utils_check_project_permission(
&project_label.project,
user_uid,
&[MemberRole::Admin, MemberRole::Owner],
)
.await?;
let system_label = label::Entity::find_by_id(project_label.label)
.one(&self.db)
.await?;
let deleted_label_name = system_label
.as_ref()
.map(|l| l.name.clone())
.unwrap_or_default();
let _ = self
.project_log_activity(
project_label.project,
None,
user_uid,
super::activity::ActivityLogParams {
event_type: "label_delete".to_string(),
title: format!("{} deleted label '{}'", user_uid, deleted_label_name),
repo_id: None,
content: None,
event_id: None,
event_sub_id: Some(label_id),
metadata: Some(serde_json::json!({
"label_id": label_id,
"label_name": deleted_label_name,
})),
is_private: false,
},
)
.await;
let log = project_audit_log::ActiveModel {
project: Set(project_label.project),
actor: Set(user_uid),
action: Set("label_delete".to_string()),
details: Set(Some(serde_json::json!({
"label_id": label_id,
"label_name": system_label.as_ref().map(|l| l.name.clone()),
}))),
created_at: Set(Utc::now()),
..Default::default()
};
log.insert(&self.db).await?;
project_label::Entity::delete_by_id(label_id)
.exec(&self.db)
.await?;
// Also delete the system label if it exists
if let Some(sl) = system_label {
label::Entity::delete_by_id(sl.id).exec(&self.db).await?;
}
Ok(())
}
}