use config::AppConfig; use db::cache::AppCache; use db::database::AppDatabase; use deadpool_redis::cluster::Pool as RedisPool; use tokio_util::sync::CancellationToken; pub mod pool; pub mod sync; pub mod webhook_dispatch; pub use pool::{HookWorker, PoolConfig, RedisConsumer}; pub use pool::types::{HookTask, TaskType}; /// Helper to initialize an optional EmbedService from config (graceful degradation). async fn init_embed_service(config: &AppConfig, db: &AppDatabase) -> Option { match agent::new_embed_client(config).await { Ok(client) => { let model_name = config .get_embed_model_name() .unwrap_or_else(|_| "text-embedding-3-small".into()); let dimensions = config .get_embed_model_dimensions() .unwrap_or(1536); let svc = agent::embed::EmbedService::new( client, db.writer().clone(), model_name, dimensions, ); // Ensure the repo_tag collection exists let _ = svc.ensure_collections().await; tracing::info!("hook worker: EmbedService initialized for tag embedding"); Some(svc) } Err(e) => { tracing::warn!(error = %e, "hook worker: EmbedService not available — tag embedding disabled"); None } } } /// Hook service that manages the Redis-backed task queue worker. /// Multiple gitserver pods can run concurrently — the worker acquires a /// per-repo Redis lock before processing each task. #[derive(Clone)] pub struct HookService { pub(crate) db: AppDatabase, pub(crate) cache: AppCache, pub(crate) redis_pool: RedisPool, pub(crate) config: AppConfig, pub(crate) embed_service: Option, } impl HookService { pub fn new( db: AppDatabase, cache: AppCache, redis_pool: RedisPool, config: AppConfig, ) -> Self { Self { db, cache, redis_pool, config, embed_service: None, } } /// Set an externally-initialized EmbedService (e.g. from the web app). pub fn with_embed_service(mut self, svc: agent::embed::EmbedService) -> Self { self.embed_service = Some(svc); self } /// Start the background worker and return a cancellation token. pub async fn start_worker(&self) -> CancellationToken { // Auto-init embed_service if not set and config allows (standalone binaries) let embed = match self.embed_service.clone() { Some(svc) => Some(svc), None => init_embed_service(&self.config, &self.db).await, }; let pool_config = PoolConfig::from_env(&self.config); pool::start_worker( self.db.clone(), self.cache.clone(), self.redis_pool.clone(), pool_config, embed, ) } }