use std::sync::Arc; use config::AppConfig; use export::AppState; use models::repo::repos; use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; use crate::errors::{CiError, CiResult}; #[derive(Clone)] pub struct CiService { pub state: AppState, } impl CiService { #[must_use] pub fn new(state: AppState) -> Self { Self { state } } #[must_use] pub fn config(&self) -> &AppConfig { &self.state.config } #[must_use] pub fn db_read(&self) -> &DatabaseConnection { self.state.database.read() } #[must_use] pub fn db_write(&self) -> &DatabaseConnection { self.state.database.write() } #[must_use] pub fn encryption_key(&self) -> &str { &self.state.config.ci.secret_encryption_key } #[must_use] pub fn enabled(&self) -> bool { self.state.config.ci.enabled } pub fn ensure_enabled(&self) -> CiResult<()> { if self.enabled() { Ok(()) } else { Err(CiError::Forbidden) } } pub async fn resolve_repo( &self, space_name: &str, repo_name: &str, ) -> CiResult<(repos::Model, models::space::spaces::Model)> { use models::space::spaces; let space_lower = space_name.trim().to_lowercase(); if space_lower.is_empty() { return Err(CiError::NotFound); } let space = spaces::Entity::find() .filter(spaces::Column::LowerName.eq(&space_lower)) .filter(spaces::Column::IsActive.eq(true)) .one(self.db_read()) .await? .ok_or(CiError::NotFound)?; let repo_lower = repo_name.trim().to_lowercase(); if repo_lower.is_empty() { return Err(CiError::NotFound); } let candidates = repos::Entity::find() .filter(repos::Column::LowerName.eq(&repo_lower)) .all(self.db_read()) .await?; let mut found = None; for candidate in candidates { if git::share::access::space_for_dir(self.db_read(), candidate.dir_id) .await? .is_some_and(|s| s.id == space.id) { found = Some(candidate); break; } } let repo = found.ok_or(CiError::NotFound)?; Ok((repo, space)) } pub async fn space_of_repo( &self, repo: &repos::Model, ) -> CiResult { let space = git::share::access::space_for_dir(self.db_read(), repo.dir_id) .await? .ok_or(CiError::NotFound)?; Ok(space) } #[must_use] pub fn repo_dir(&self, repo_id: uuid::Uuid) -> std::path::PathBuf { std::path::PathBuf::from(&self.state.config.git.repo_root).join(repo_id.to_string()) } #[must_use] pub fn storage(&self) -> &Arc { &self.state.storage } } impl CiService { /// Builds a `GitContext` for authorization checks without resolving names. pub async fn git_context( &self, repo: &repos::Model, ) -> CiResult { let space = self.space_of_repo(repo).await?; let service = git::service::GitService::new(self.state.clone()); service .context(space.lower_name, repo.name.clone()) .await .map_err(CiError::from) } }