use chrono::Utc; use db::sqlx; use model::repos::RepoCommitStatusModel; use session::Session; use uuid::Uuid; use crate::AppService; use crate::error::AppError; #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct CommitStatusResponse { pub id: Uuid, pub commit_sha: String, pub state: String, pub target_url: Option, pub description: Option, pub context: String, pub creator: Uuid, pub created_at: chrono::DateTime, } #[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)] pub struct CombinedCommitStatus { pub sha: String, pub state: String, pub total_count: i64, pub statuses: Vec, } #[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)] pub struct CreateCommitStatus { pub state: String, pub target_url: Option, pub description: Option, pub context: Option, } impl AppService { pub async fn git_commit_status_list( &self, repo_id: Uuid, commit_sha: &str, ) -> Result, AppError> { let rows = sqlx::query_as::<_, RepoCommitStatusModel>( "SELECT id, repo, commit_sha, state, target_url, description, \ context, creator, created_at, updated_at \ FROM repo_commit_status \ WHERE repo = $1 AND commit_sha = $2 \ ORDER BY created_at DESC", ) .bind(repo_id) .bind(commit_sha) .fetch_all(self.db.reader()) .await .map_err(|e| AppError::DatabaseError(e.to_string()))?; Ok(rows.into_iter().map(status_to_response).collect()) } pub async fn git_commit_status_combined( &self, repo_id: Uuid, commit_sha: &str, ) -> Result { let statuses = self.git_commit_status_list(repo_id, commit_sha).await?; let state = combined_state(&statuses); Ok(CombinedCommitStatus { sha: commit_sha.to_string(), state, total_count: statuses.len() as i64, statuses, }) } pub async fn git_commit_status_create( &self, repo_id: Uuid, user_id: Uuid, commit_sha: &str, params: CreateCommitStatus, ) -> Result { if !["pending", "success", "failure", "error"] .contains(¶ms.state.as_str()) { return Err(AppError::BadRequest( "state must be one of: pending, success, failure, error" .to_string(), )); } let id = Uuid::now_v7(); let now = Utc::now(); let context = params.context.unwrap_or_else(|| "default".to_string()); let row = sqlx::query_as::<_, RepoCommitStatusModel>( "INSERT INTO repo_commit_status (id, repo, commit_sha, state, target_url, \ description, context, creator, created_at, updated_at) \ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$9) RETURNING *", ) .bind(id) .bind(repo_id) .bind(commit_sha) .bind(¶ms.state) .bind(¶ms.target_url) .bind(¶ms.description) .bind(&context) .bind(user_id) .bind(now) .fetch_one(self.db.writer()) .await .map_err(|e| AppError::DatabaseError(e.to_string()))?; Ok(status_to_response(row)) } } impl AppService { pub async fn git_commit_status_list_by_name( &self, ctx: &Session, wk: &str, repo: &str, sha: &str, ) -> Result, AppError> { let repo = self.git_require_member(ctx, wk, repo).await?; self.git_commit_status_list(repo.id, sha).await } pub async fn git_commit_status_combined_by_name( &self, ctx: &Session, wk: &str, repo: &str, sha: &str, ) -> Result { let repo = self.git_require_member(ctx, wk, repo).await?; self.git_commit_status_combined(repo.id, sha).await } pub async fn git_commit_status_create_by_name( &self, ctx: &Session, user_id: Uuid, wk: &str, repo: &str, sha: &str, params: CreateCommitStatus, ) -> Result { let repo = self.git_require_member(ctx, wk, repo).await?; self.git_commit_status_create(repo.id, user_id, sha, params) .await } } fn status_to_response(s: RepoCommitStatusModel) -> CommitStatusResponse { CommitStatusResponse { id: s.id, commit_sha: s.commit_sha, state: s.state, target_url: s.target_url, description: s.description, context: s.context, creator: s.creator, created_at: s.created_at, } } fn combined_state(statuses: &[CommitStatusResponse]) -> String { if statuses.is_empty() { return "pending".to_string(); } let has = |s: &str| statuses.iter().any(|st| st.state == s); (if has("error") { "error" } else if has("failure") { "failure" } else if has("pending") { "pending" } else { "success" }) .to_string() }