Skip to content
Clone

feat(webapi): add webapi services module

zhenyi committed
parent 1cfdf6f commit 241a911 Browse files

1 file changed

+146 −0

Added lib/webapi/services.rs View lib/webapi/services.rs
@@ -0,0 +1,146 @@
1use std::sync::Arc;
2
3use auth::features::AuthService;
4use auth::pow::{PowService, PowSettings};
5use cache::redis::RedisCache;
6use db::conn::Db;
7use email::{EmailService, EmailServiceBuilder};
8use issues::service::IssueService;
9use jwt::JwtState;
10use jwt::keys::SigningKeyCache;
11use project::service::ProjectService;
12use queue::{Queue, QueueBuilder};
13use repo::service::RepoService;
14use space::features::SpaceService;
15use storage::StorageRepository;
16use storage::repo::from_config as storage_from_config;
17use thiserror::Error;
18use tokio::sync::RwLock;
19use user::features::UserService;
20
21use crate::config::AppConfig;
22
23/// Error raised while constructing the service stack.
24#[derive(Debug, Error)]
25pub enum WebapiError {
26 #[error("database: {0}")]
27 Db(#[from] db::conn::DbError),
28 #[error("cache: {0}")]
29 Cache(#[from] cache::redis::RedisError),
30 #[error("email: {0}")]
31 Email(#[from] email::EmailError),
32 #[error("queue: {0}")]
33 Queue(#[from] queue::QueueError),
34 #[error("storage: {0}")]
35 Storage(#[from] storage::StorageError),
36 /// Auth routes require a working email transport (verification codes,
37 /// password reset, email reclaim). Refuse to boot without one.
38 #[error("email service is required (auth depends on it); enable email in config")]
39 EmailRequired,
40}
41
42pub type WebapiResult<T> = Result<T, WebapiError>;
43
44/// All services built from an [`AppConfig`], ready to be handed to the router.
45pub struct AppServices {
46 pub db: Db,
47 pub kv: Arc<RedisCache>,
48 pub jwt: JwtState,
49 pub email: EmailService,
50 pub queue: Option<Arc<Queue>>,
51 pub storage: Arc<dyn StorageRepository>,
52 pub auth: AuthService,
53 pub issues: IssueService,
54 pub project: ProjectService,
55 pub repo: RepoService,
56 pub space: SpaceService,
57 pub user: UserService,
58}
59
60impl AppServices {
61 /// Build the full service stack from configuration.
62 ///
63 /// The queue is optional (its settings may disable it); when it is
64 /// disabled the email service cannot be built either, and because the
65 /// auth service requires a concrete email transport this fails startup
66 /// with [`WebapiError::EmailRequired`].
67 pub async fn build(cfg: &AppConfig) -> WebapiResult<Self> {
68 tracing::info!("building service stack");
69
70 let db = Db::from_config(&cfg.db).await?;
71 tracing::info!("database connected");
72
73 let kv = Arc::new(RedisCache::new(&cfg.cache).await?);
74 tracing::info!("cache connected");
75
76 let queue = QueueBuilder::from_config(cfg.queue.clone())
77 .build()
78 .await?
79 .map(Arc::new);
80 tracing::info!("queue ready");
81
82 let email = match queue.clone() {
83 Some(queue) => {
84 let service = EmailServiceBuilder::new(cfg.email.clone(), queue)
85 .build()
86 .await?;
87 match service {
88 Some(service) => {
89 tracing::info!("email service enabled");
90 service
91 }
92 None => return Err(WebapiError::EmailRequired),
93 }
94 }
95 None => return Err(WebapiError::EmailRequired),
96 };
97
98 let storage = storage_from_config(&cfg.storage)?;
99 tracing::info!("storage ready");
100
101 let jwt = JwtState::new(
102 db.writer(),
103 cfg.jwt.clone(),
104 Arc::new(RwLock::new(SigningKeyCache::new())),
105 );
106
107 let pow = PowService::new(PowSettings::default());
108
109 let auth = AuthService::new(
110 db.clone(),
111 email.clone(),
112 kv.clone(),
113 pow,
114 cfg.auth.clone(),
115 jwt.clone(),
116 );
117
118 let issues = IssueService::new(db.clone(), kv.clone(), jwt.clone(), storage.clone())
119 .with_config(Arc::new(cfg.issues.clone()));
120 let project = ProjectService::new(db.clone(), kv.clone(), jwt.clone())
121 .with_config(Arc::new(cfg.project.clone()));
122 let repo = RepoService::new(db.clone(), kv.clone(), jwt.clone())
123 .with_config(Arc::new(cfg.repo.clone()));
124 let space = SpaceService::new(db.clone(), kv.clone(), jwt.clone())
125 .with_config(Arc::new(cfg.space.clone()));
126 let user = UserService::new(db.clone(), kv.clone(), jwt.clone(), storage.clone())
127 .with_config(Arc::new(cfg.user.clone()));
128
129 tracing::info!("service stack ready");
130
131 Ok(Self {
132 db,
133 kv,
134 jwt,
135 email,
136 queue,
137 storage,
138 auth,
139 issues,
140 project,
141 repo,
142 space,
143 user,
144 })
145 }
146}