use std::sync::Arc; use export::AppRedis; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use uuid::Uuid; use crate::context::facade::GitContext; use crate::service::GitService; use crate::smart::error::{SmartError, SmartResult}; use crate::smart::push::{NoopPushBackend, PushCoordinator, PushOperations, RedisPushBackend}; use crate::sync::queue::SyncQueue; use config::git::SyncSettings; #[derive(Clone)] pub struct SmartService { pub git: GitService, pub kv: Option>, pub queue: Option>, pub sync_settings: SyncSettings, /// When set, pushes also publish a CI event on this Redis list topic. pub ci_event_topic: Option, /// When set, pushes also enqueue AI tasks (commit review / release draft). pub ai_config: Option, } impl SmartService { pub fn new(git: GitService) -> Self { Self { git, kv: None, queue: None, sync_settings: SyncSettings::default(), ci_event_topic: None, ai_config: None, } } pub fn with_ai_config(mut self, config: Option) -> Self { self.ai_config = config; self } pub fn ai_config(&self) -> Option<&config::ai::AiConfig> { self.ai_config.as_ref() } pub fn with_ci_event_topic(mut self, topic: Option) -> Self { self.ci_event_topic = topic; self } pub fn ci_event_topic(&self) -> Option<&str> { self.ci_event_topic.as_deref() } pub fn with_kv(mut self, kv: Option>) -> Self { self.kv = kv; self } pub fn with_queue(mut self, queue: Option>) -> Self { self.queue = queue; self } pub fn with_sync_settings(mut self, settings: SyncSettings) -> Self { self.sync_settings = settings; self } pub fn push_coordinator(&self) -> PushCoordinator { let backend: Arc = match &self.kv { Some(kv) => Arc::new(RedisPushBackend { kv: kv.clone() }), None => Arc::new(NoopPushBackend), }; PushCoordinator::new(backend) } pub fn bare_path(&self, ctx: &GitContext) -> std::path::PathBuf { ctx.repo_dir() } pub async fn resolve(&self, space: &str, repo: &str) -> SmartResult { self.git .context(space.to_string(), repo.to_string()) .await .map_err(|e| match e { crate::share::errors::GitError::NotFound => { SmartError::NotFound(format!("repo {space}/{repo}")) } other => SmartError::from(other), }) } pub async fn owning_space_id(&self, ctx: &GitContext) -> SmartResult { let space_name = self.space_for(ctx).await?; let Some(space) = models::space::spaces::Entity::find() .filter(models::space::spaces::Column::LowerName.eq(space_name.to_lowercase())) .one(self.git.db_read()) .await? else { return Err(SmartError::NotFound(format!("space {space_name}"))); }; Ok(space.id) } async fn space_for(&self, ctx: &GitContext) -> SmartResult { let space = crate::share::access::space_for_dir(self.git.db_read(), ctx.model.dir_id) .await? .ok_or_else(|| SmartError::NotFound("space".into()))?; Ok(space.lower_name) } } pub use crate::smart::error::SmartError as SmartServiceError;