355 lines
12 KiB
Rust
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(())
|
|
}
|
|
}
|