pubky_homeserver/
app_context.rs1use crate::services::user_service::UserService;
9#[cfg(any(test, feature = "testing"))]
10use crate::MockDataDir;
11use crate::{
12 metrics_server::routes::metrics::{Metrics, MetricsInitError},
13 persistence::{
14 files::{events::EventsService, FileIoError, FileService},
15 sql::{Migrator, PgEventListener, SqlDb},
16 },
17 ConfigToml, DataDir,
18};
19use pubky_common::crypto::Keypair;
20use std::sync::Arc;
21use std::time::Duration;
22
23#[derive(Debug, thiserror::Error)]
25pub enum AppContextConversionError {
26 #[error("Failed to ensure data directory exists and is writable: {0}")]
28 DataDir(anyhow::Error),
29 #[error("Failed to read or create config file: {0}")]
31 Config(anyhow::Error),
32 #[error("Failed to read or create keypair: {0}")]
34 Keypair(anyhow::Error),
35 #[error("Failed to open SQL DB: {0}")]
37 SqlDb(sqlx::Error),
38 #[error("Failed to run migrations: {0}")]
40 Migrations(anyhow::Error),
41 #[error("Failed to build storage operator: {0}")]
43 Storage(FileIoError),
44 #[error("Failed to build pkarr client: {0}")]
46 Pkarr(pkarr::errors::BuildError),
47 #[error("Failed to start Postgres event listener: {0}")]
49 PgEventListener(sqlx::Error),
50 #[error("Failed to initialize metrics: {0}")]
52 Metrics(MetricsInitError),
53}
54
55#[derive(Clone)]
61pub struct AppContext {
62 pub(crate) sql_db: SqlDb,
64 pub(crate) file_service: FileService,
66 pub(crate) config_toml: ConfigToml,
67 pub(crate) data_dir: Arc<dyn DataDir>,
69 pub(crate) keypair: Keypair,
70 pub(crate) pkarr_client: pkarr::Client,
73 pub(crate) pkarr_builder: pkarr::ClientBuilder,
76 pub(crate) events_service: EventsService,
78 pub(crate) metrics: Metrics,
80 _pg_event_listener: Arc<PgEventListener>,
84 pub(crate) user_service: UserService,
86}
87
88impl AppContext {
89 #[cfg(any(test, feature = "testing"))]
91 pub async fn test() -> Self {
92 let data_dir = MockDataDir::test();
93 Self::read_from(data_dir)
94 .await
95 .expect("failed to build AppContext from DataDirMock")
96 }
97
98 pub async fn read_from<D: DataDir + 'static>(
100 dir: D,
101 ) -> Result<Self, AppContextConversionError> {
102 dir.ensure_data_dir_exists_and_is_writable()
103 .map_err(AppContextConversionError::DataDir)?;
104 let conf = dir
105 .read_or_create_config_file()
106 .map_err(AppContextConversionError::Config)?;
107 let keypair = dir
108 .read_or_create_keypair()
109 .map_err(AppContextConversionError::Keypair)?;
110
111 let sql_db = Self::connect_to_sql_db(&conf).await?;
112 Migrator::new(&sql_db)
113 .run()
114 .await
115 .map_err(AppContextConversionError::Migrations)?;
116
117 let events_service = EventsService::new(1000);
118
119 let pg_event_listener = PgEventListener::start(sql_db.pool(), events_service.clone())
120 .await
121 .map_err(AppContextConversionError::PgEventListener)?;
122
123 let user_service = UserService::new(sql_db.clone());
124
125 let file_service = FileService::new_from_config(
126 &conf,
127 dir.path(),
128 sql_db.clone(),
129 events_service.clone(),
130 user_service.clone(),
131 )
132 .map_err(AppContextConversionError::Storage)?;
133 let pkarr_builder = Self::build_pkarr_builder_from_config(&conf);
134
135 Ok(Self {
136 sql_db,
137 pkarr_client: pkarr_builder
138 .clone()
139 .build()
140 .map_err(AppContextConversionError::Pkarr)?,
141 file_service,
142 pkarr_builder,
143 config_toml: conf,
144 keypair,
145 data_dir: Arc::new(dir),
146 events_service,
147 metrics: Metrics::new().map_err(AppContextConversionError::Metrics)?,
148 _pg_event_listener: Arc::new(pg_event_listener),
149 user_service,
150 })
151 }
152}
153
154impl AppContext {
155 fn build_pkarr_builder_from_config(config_toml: &ConfigToml) -> pkarr::ClientBuilder {
157 let mut builder = pkarr::ClientBuilder::default();
158 if let Some(bootstrap_nodes) = &config_toml.pkdns.dht_bootstrap_nodes {
159 let nodes = bootstrap_nodes
160 .iter()
161 .map(|node| node.to_string())
162 .collect::<Vec<String>>();
163 builder.bootstrap(&nodes);
164
165 builder.no_relays();
169 }
170
171 if let Some(relays) = &config_toml.pkdns.dht_relay_nodes {
172 builder
173 .relays(relays)
174 .expect("parameters are already urls and therefore valid.");
175 }
176 if let Some(request_timeout) = &config_toml.pkdns.dht_request_timeout_ms {
177 let duration = Duration::from_millis(request_timeout.get());
178 builder.request_timeout(duration);
179 }
180 builder
181 }
182
183 async fn connect_to_sql_db(
188 config_toml: &ConfigToml,
189 ) -> Result<SqlDb, AppContextConversionError> {
190 #[cfg(any(test, feature = "testing"))]
191 {
192 return if config_toml.general.database_url.is_test_db() {
195 Ok(SqlDb::test().await)
196 } else {
197 SqlDb::connect(&config_toml.general.database_url)
198 .await
199 .map_err(AppContextConversionError::SqlDb)
200 };
201 }
202
203 #[cfg(not(any(test, feature = "testing")))]
204 {
205 return SqlDb::connect(&config_toml.general.database_url)
207 .await
208 .map_err(AppContextConversionError::SqlDb);
209 }
210 }
211}