d977f67
zhenyi
refactor(dvcs): refresh dvcs/git, dvcs/gitsmart, dvcs/gitssh, dvcs/gitsync, dvcs/gitauth
d977f67
use std::sync::Arc;
use auth::features::AuthService;
use auth::pow::PowService;
use cache::redis::RedisCache;
use config::AppConfig;
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<T> = Result<T, WebapiError>;
pub struct AppServices {
pub db: Db,
pub kv: Arc<RedisCache>,
pub jwt: JwtState,
pub email: EmailService,
pub queue: Option<Arc<Queue>>,
pub storage: Arc<dyn StorageRepository>,
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<Self> {
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())),
);
// 启动时从数据库加载活动签名密钥,否则缓存为空会导致 NoActiveKey
if let Err(e) = jwt.cache.write().await.load_from_db(&db.writer()).await {
tracing::warn!("failed to load signing keys: {e}");
}
let pow = PowService::new(cfg.pow.clone());
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,
})
}
}