use std::sync::Arc; use auth::features::AuthService; use auth::pow::PowService; use cache::redis::RedisCache; use config::AppConfig; use config::pow::PowSettings; use db::conn::Db; use email::{EmailService, EmailServiceBuilder}; use issues::service::IssueService; use jwt::JwtState; use jwt::keys::SigningKeyCache; use mcp::McpService; use post::service::PostService; use project::service::ProjectService; use queue::{Queue, QueueBuilder}; use repo::service::RepoService; use space::features::SpaceService; use storage::StorageRepository; use storage::repo::from_config as storage_from_config; use thiserror::Error; use tokio::sync::RwLock; use user::features::UserService; #[derive(Debug, Error)] pub enum WebapiError { #[error("database: {0}")] Db(#[from] db::conn::DbError), #[error("cache: {0}")] Cache(#[from] cache::redis::RedisError), #[error("email: {0}")] Email(#[from] email::EmailError), #[error("queue: {0}")] Queue(#[from] queue::QueueError), #[error("storage: {0}")] Storage(#[from] storage::StorageError), #[error("mcp: {0}")] Mcp(#[from] mcp::McpError), #[error("email service is required (auth depends on it); enable email in config")] EmailRequired, } pub type WebapiResult = Result; pub struct AppServices { pub db: Db, pub kv: Arc, pub jwt: JwtState, pub email: EmailService, pub queue: Option>, pub storage: Arc, pub auth: AuthService, pub issues: IssueService, pub post: PostService, pub project: ProjectService, pub repo: RepoService, pub space: SpaceService, pub user: UserService, pub mcp: McpService, } impl AppServices { pub async fn build(cfg: &AppConfig) -> WebapiResult { tracing::info!("building service stack"); let db = Db::from_config(&cfg.db).await?; tracing::info!("database connected"); let kv = Arc::new(RedisCache::new(&cfg.cache).await?); tracing::info!("cache connected"); let queue = QueueBuilder::from_config(cfg.queue.clone()) .build() .await? .map(Arc::new); tracing::info!("queue ready"); let email = match queue.clone() { Some(queue) => { let service = EmailServiceBuilder::new(cfg.email.clone(), queue) .build() .await?; match service { Some(service) => { tracing::info!("email service enabled"); service } None => return Err(WebapiError::EmailRequired), } } None => return Err(WebapiError::EmailRequired), }; let storage = storage_from_config(&cfg.storage)?; tracing::info!("storage ready"); let jwt = JwtState::new( db.writer(), cfg.jwt.clone(), Arc::new(RwLock::new(SigningKeyCache::new())), ); let pow = PowService::new(PowSettings::default()); let auth = AuthService::new( db.clone(), email.clone(), kv.clone(), pow, cfg.auth.clone(), jwt.clone(), ); let issues = IssueService::new(db.clone(), kv.clone(), jwt.clone(), storage.clone()) .with_config(Arc::new(cfg.issues.clone())); let post = PostService::new(db.clone(), kv.clone(), jwt.clone(), storage.clone()); let project = ProjectService::new(db.clone(), kv.clone(), jwt.clone()) .with_config(Arc::new(cfg.project.clone())); let repo = RepoService::new(db.clone(), kv.clone(), jwt.clone()) .with_config(Arc::new(cfg.repo.clone())); let space = SpaceService::new(db.clone(), kv.clone(), jwt.clone()) .with_config(Arc::new(cfg.space.clone())); let user = UserService::new(db.clone(), kv.clone(), jwt.clone(), storage.clone()) .with_config(Arc::new(cfg.user.clone())); let mcp = McpService::new( cfg.mcp.clone(), db.clone(), repo.clone(), issues.clone(), project.clone(), space.clone(), )?; tracing::info!("mcp ready"); tracing::info!("service stack ready"); Ok(Self { db, kv, jwt, email, queue, storage, auth, issues, post, project, repo, space, user, mcp, }) } }