eba4fd1
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<models::space::spaces::Model> {
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<export::AppStorage> {
&self.state.storage
}
}
impl CiService {
/// Builds a `GitContext` for authorization checks without resolving names.
pub async fn git_context(
&self,
repo: &repos::Model,
) -> CiResult<git::context::facade::GitContext> {
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)
}
}