use db::conn::sea_orm::*; use db::git::git_commit_status as status_entity; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{RepoError, RepoResult}; use crate::features::RepoService; use super::types::{ CombinedStatusView, CommitStatusPage, CommitStatusView, CreateStatusOption, ListStatusQuery, parse_state_pub, }; fn require_authenticated(actor: Uuid) -> RepoResult<()> { if actor.is_nil() { return Err(RepoError::Unauthorized("authentication required".into())); } Ok(()) } fn aggregate_state(items: &[status_entity::Model]) -> &'static str { let mut has_failure = false; let mut has_error = false; let mut has_pending = false; let mut has_success = false; for m in items { match m.state { status_entity::CommitStatusState::Failure => has_failure = true, status_entity::CommitStatusState::Error => has_error = true, status_entity::CommitStatusState::Pending => has_pending = true, status_entity::CommitStatusState::Success => has_success = true, } } if has_failure || has_error { "failure" } else if has_pending { "pending" } else if has_success { "success" } else { "pending" } } pub async fn list_commit_statuses( svc: &RepoService, actor: Uuid, repo_id: Uuid, sha: &str, q: ListStatusQuery, ) -> RepoResult { let _ = actor; let limit = q.limit.unwrap_or(svc.config.default_pagination); let offset = q.page.unwrap_or(0).saturating_mul(limit); let limit = std::cmp::min(limit, svc.config.max_pagination); let db = svc.db.reader(); let mut query = status_entity::Entity::find() .filter(status_entity::Column::RepoId.eq(repo_id)) .filter(status_entity::Column::Sha.eq(sha)); if let Some(s) = q.state.as_deref() { let parsed = parse_state_pub(s)?; query = query.filter(status_entity::Column::State.eq(parsed)); } let total = query.clone().count(&db).await?; let items = query .order_by_desc(status_entity::Column::CreatedAt) .limit(Some(limit)) .offset(Some(offset)) .all(&db) .await? .into_iter() .map(CommitStatusView::from) .collect(); Ok(CommitStatusPage { items, total: total as i64, }) } pub async fn create_commit_status( svc: &RepoService, actor: Uuid, repo_id: Uuid, sha: &str, input: CreateStatusOption, ) -> RepoResult { require_authenticated(actor)?; let state = parse_state_pub(&input.state)?; let db = svc.db.writer(); let now = OffsetDateTime::now_utc(); let active: status_entity::ActiveModel = status_entity::ActiveModel { id: Set(Uuid::new_v4()), repo_id: Set(repo_id), sha: Set(sha.to_string()), state: Set(state), context: Set(input.context), description: Set(input.description), target_url: Set(input.target_url), creator_id: Set(actor), created_at: Set(now), updated_at: Set(Some(now)), }; let saved = active.insert(&db).await?; Ok(CommitStatusView::from(saved)) } pub async fn list_commit_statuses_by_ref( svc: &RepoService, actor: Uuid, repo_id: Uuid, git_ref: &str, q: ListStatusQuery, ) -> RepoResult { let _ = actor; let sha = resolve_ref(svc, repo_id, git_ref).await?; list_commit_statuses(svc, actor, repo_id, &sha, q).await } pub async fn combined_status_by_ref( svc: &RepoService, actor: Uuid, repo_id: Uuid, git_ref: &str, ) -> RepoResult { let _ = actor; let sha = resolve_ref(svc, repo_id, git_ref).await?; let db = svc.db.reader(); let items = status_entity::Entity::find() .filter(status_entity::Column::RepoId.eq(repo_id)) .filter(status_entity::Column::Sha.eq(&sha)) .all(&db) .await?; let total = items.len() as i64; let state = aggregate_state(&items).to_string(); let statuses: Vec = items.into_iter().map(CommitStatusView::from).collect(); Ok(CombinedStatusView { sha, state, statuses, total_count: total, }) } async fn resolve_ref(_svc: &RepoService, _repo_id: Uuid, git_ref: &str) -> RepoResult { if git_ref.is_empty() { return Err(RepoError::BadRequest("ref is empty".into())); } Ok(git_ref.to_string()) }