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

151 lines
4.6 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::issues::{issue, issue_repo};
use models::projects::project_members;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct IssueLinkRepoRequest {
pub repo: Uuid,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueRepoResponse {
pub issue: Uuid,
pub repo: Uuid,
pub relation_at: chrono::DateTime<Utc>,
}
impl AppService {
/// List repos linked to an issue.
pub async fn issue_repo_list(
&self,
project_name: String,
issue_number: i64,
ctx: &Session,
) -> Result<Vec<IssueRepoResponse>, AppError> {
let project = self.utils_find_project_by_name(project_name).await?;
if let Some(uid) = ctx.user() {
self.check_project_access(project.id, uid).await?;
}
let issue = issue::Entity::find()
.filter(issue::Column::Project.eq(project.id))
.filter(issue::Column::Number.eq(issue_number))
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Issue not found".to_string()))?;
let repos = issue_repo::Entity::find()
.filter(issue_repo::Column::Issue.eq(issue.id))
.all(&self.db)
.await?;
let responses: Vec<IssueRepoResponse> = repos
.into_iter()
.map(|r| IssueRepoResponse {
issue: r.issue,
repo: r.repo,
relation_at: r.relation_at,
})
.collect();
Ok(responses)
}
/// Link a repo to an issue.
pub async fn issue_repo_link(
&self,
project_name: String,
issue_number: i64,
request: IssueLinkRepoRequest,
ctx: &Session,
) -> Result<IssueRepoResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project = self.utils_find_project_by_name(project_name).await?;
let _member = project_members::Entity::find()
.filter(project_members::Column::Project.eq(project.id))
.filter(project_members::Column::User.eq(user_uid))
.one(&self.db)
.await?
.ok_or(AppError::NoPower)?;
let issue = issue::Entity::find()
.filter(issue::Column::Project.eq(project.id))
.filter(issue::Column::Number.eq(issue_number))
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Issue not found".to_string()))?;
let existing = issue_repo::Entity::find()
.filter(issue_repo::Column::Issue.eq(issue.id))
.filter(issue_repo::Column::Repo.eq(request.repo))
.one(&self.db)
.await?;
if existing.is_some() {
return Err(AppError::BadRequest(
"Repo already linked to this issue".to_string(),
));
}
let now = Utc::now();
let active = issue_repo::ActiveModel {
issue: Set(issue.id),
repo: Set(request.repo),
relation_at: Set(now),
..Default::default()
};
let model = active.insert(&self.db).await?;
self.invalidate_issue_cache(project.id, issue_number).await;
Ok(IssueRepoResponse {
issue: model.issue,
repo: model.repo,
relation_at: model.relation_at,
})
}
/// Unlink a repo from an issue.
pub async fn issue_repo_unlink(
&self,
project_name: String,
issue_number: i64,
repo_id: Uuid,
ctx: &Session,
) -> Result<(), AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let project = self.utils_find_project_by_name(project_name).await?;
let _member = project_members::Entity::find()
.filter(project_members::Column::Project.eq(project.id))
.filter(project_members::Column::User.eq(user_uid))
.one(&self.db)
.await?
.ok_or(AppError::NoPower)?;
let issue = issue::Entity::find()
.filter(issue::Column::Project.eq(project.id))
.filter(issue::Column::Number.eq(issue_number))
.one(&self.db)
.await?
.ok_or(AppError::NotFound("Issue not found".to_string()))?;
issue_repo::Entity::delete_many()
.filter(issue_repo::Column::Issue.eq(issue.id))
.filter(issue_repo::Column::Repo.eq(repo_id))
.exec(&self.db)
.await?;
self.invalidate_issue_cache(project.id, issue_number).await;
Ok(())
}
}