1use derivative::Derivative;
2use haste_fhir_search::config::SearchConfig;
3use haste_repository::config::RepoConfig;
4use haste_worker::async_jobs::AsyncJobWorkerConfig;
5use serde::{Deserialize, Serialize};
6use std::{path::PathBuf, time::Duration};
7
8#[derive(Debug, Clone, Deserialize, Serialize)]
9#[serde(default)]
10pub struct ServerConfig {
11 pub port: u16,
13 pub allow_artifact_mutations: bool,
16 pub certification_dir: PathBuf,
18 pub api_uri: String,
20 pub admin_app_redirect_uri: String,
22
23 pub fhir: FHIRConfig,
24 pub repo: RepoConfig,
25 pub search: SearchConfig,
26 pub email: Option<EmailConfig>,
27 pub rate_limits: RateLimitsConfig,
28 pub max_request_body_size: usize,
29 pub monitoring: MonitoringConfig,
30 pub security: SecurityConfig,
31 pub operations: OperationsConfig,
32 pub signup: SignupConfig,
33 pub jobs: JobsConfig,
34 pub packages: PackagesConfig,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
41#[serde(default)]
42pub struct JobsConfig {
43 pub retention_seconds: u64,
46 pub max_live_per_project: i64,
50 pub retry_after_seconds: u64,
53 pub lease_seconds: u64,
56 pub poll_interval_ms: u64,
58 pub max_concurrent: usize,
60 pub maintenance_interval_seconds: u64,
62}
63
64impl JobsConfig {
65 #[must_use]
66 pub fn retention(&self) -> Duration {
67 Duration::from_secs(self.retention_seconds)
68 }
69
70 #[must_use]
71 pub fn worker_config(&self) -> AsyncJobWorkerConfig {
72 AsyncJobWorkerConfig {
73 poll_interval: Duration::from_millis(self.poll_interval_ms),
74 lease_duration: Duration::from_secs(self.lease_seconds),
75 max_concurrent_jobs: self.max_concurrent,
76 maintenance_interval: Duration::from_secs(self.maintenance_interval_seconds),
77 retention: self.retention(),
78 }
79 }
80}
81
82impl Default for JobsConfig {
83 fn default() -> Self {
84 Self {
85 retention_seconds: 24 * 60 * 60,
86 max_live_per_project: 10,
87 retry_after_seconds: 5,
88 lease_seconds: 60,
89 poll_interval_ms: 1000,
90 max_concurrent: 4,
91 maintenance_interval_seconds: 60,
92 }
93 }
94}
95
96#[derive(Debug, Clone, Deserialize, Serialize)]
99#[serde(default)]
100pub struct PackagesConfig {
101 pub registry_url: String,
106 pub skip_dependencies: Vec<String>,
112 pub max_package_bytes: u64,
114 pub max_files: usize,
116 pub max_install_packages: usize,
119 pub max_install_bytes: u64,
123 pub max_concurrent_installs: usize,
126 pub free_tier_install_interval_seconds: u64,
129 pub free_tier_shared_install_interval_seconds: u64,
135}
136
137impl Default for PackagesConfig {
138 fn default() -> Self {
139 Self {
140 registry_url: "https://packages.fhir.org".to_string(),
141 skip_dependencies: [
142 "hl7.terminology",
143 "hl7.terminology.r4",
144 "hl7.terminology.r5",
145 "us.nlm.vsac",
146 "us.cdc.phinvads",
147 "hl7.fhir.uv.xver-r5.r4",
148 "hl7.fhir.uv.xver-r4b.r4",
149 "hl7.fhir.uv.xver-r3.r4",
150 "hl7.fhir.uv.xver-r2.r4",
151 ]
152 .map(str::to_string)
153 .to_vec(),
154 max_package_bytes: 20 * 1024 * 1024,
155 max_files: 20_000,
156 max_install_packages: 50,
157 max_install_bytes: 200 * 1024 * 1024,
158 max_concurrent_installs: 1,
159 free_tier_install_interval_seconds: 120,
160 free_tier_shared_install_interval_seconds: 60,
161 }
162 }
163}
164
165#[derive(Debug, Clone, Deserialize, Serialize)]
167#[serde(default)]
168pub struct SignupConfig {
169 pub enabled: bool,
173 pub turnstile: Option<TurnstileConfig>,
175}
176
177#[derive(Derivative, Clone, Deserialize, Serialize)]
178#[derivative(Debug)]
179pub struct TurnstileConfig {
180 pub site_key: String,
182 #[derivative(Debug = "ignore")]
184 pub secret_key: String,
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize)]
188#[serde(default)]
189pub struct SecurityConfig {
190 pub publicize_fhir_metadata: bool,
191 pub mfa: MFAConfig,
192 pub encryption: SecretProviderConfig,
193 pub aes_key: Option<String>,
194 pub certification_key: Option<String>,
195}
196
197#[derive(Debug, Clone, Deserialize, Serialize)]
198#[serde(default)]
199pub struct MFAConfig {
200 pub max_credentials_per_user: usize,
201}
202
203#[derive(Debug, Clone, Deserialize, Serialize)]
204#[serde(default)]
205pub struct OperationsConfig {
206 pub deno_pool_threads: usize,
213}
214
215#[derive(Debug, Clone, Deserialize, Serialize)]
216#[serde(default)]
217#[derive(Default)]
218pub struct MonitoringConfig {
219 pub audit_enabled: bool,
220 pub ip_source: IpSource,
221}
222
223#[derive(Debug, Clone, Deserialize, Serialize)]
224#[serde(tag = "type", rename_all = "snake_case")]
225pub enum SecretProviderConfig {
226 Environment { prefix: Option<String> },
227 GCP { project_id: String },
228 AWS { region: String },
229}
230
231#[derive(Debug, Clone, Deserialize, Serialize)]
232#[serde(default)]
233pub struct FHIRConfig {
234 pub delete_limit: u64,
236}
237
238#[derive(Derivative, Clone, Deserialize, Serialize)]
239#[derivative(Debug)]
240#[serde(tag = "backend", rename_all = "snake_case")]
241pub enum EmailConfig {
242 SendGrid {
243 #[derivative(Debug = "ignore")]
244 api_key: String,
245 #[derivative(Debug = "ignore")]
246 from_address: String,
247 },
248 Log,
250}
251
252#[derive(Debug, Clone, Deserialize, Serialize)]
253#[serde(default)]
254pub struct RateLimitsConfig {
255 pub rate_limit_subscription_tiers: Option<[usize; 4]>,
256 pub rate_limit_window_seconds: u64,
257 pub rate_limit_operation_points: u32,
258}
259
260#[derive(Debug, Clone, Deserialize, Serialize, Default)]
261#[serde(rename_all = "snake_case")]
262pub enum IpSource {
263 #[default]
264 ConnectInfo,
265 CfConnectingIp,
266 XRealIp,
267}
268
269impl Default for FHIRConfig {
270 fn default() -> Self {
271 Self { delete_limit: 100 }
272 }
273}
274
275impl Default for ServerConfig {
276 fn default() -> Self {
277 Self {
278 port: 3000,
279 allow_artifact_mutations: false,
280 certification_dir: PathBuf::from("certifications"),
281 api_uri: "http://localhost:3000".into(),
282 admin_app_redirect_uri: "http://*.localhost:3001".into(),
283 fhir: FHIRConfig::default(),
284 repo: RepoConfig::default(),
285 search: SearchConfig::default(),
286 email: None,
287 max_request_body_size: 4 * 1024 * 1024,
288 rate_limits: RateLimitsConfig::default(),
289 monitoring: MonitoringConfig::default(),
290 security: SecurityConfig::default(),
291 operations: OperationsConfig::default(),
292 signup: SignupConfig::default(),
293 jobs: JobsConfig::default(),
294 packages: PackagesConfig::default(),
295 }
296 }
297}
298
299impl Default for SignupConfig {
300 fn default() -> Self {
301 Self {
302 enabled: true,
303 turnstile: None,
304 }
305 }
306}
307
308impl Default for RateLimitsConfig {
309 fn default() -> Self {
310 Self {
311 rate_limit_subscription_tiers: None,
312 rate_limit_window_seconds: 60 * 60 * 24, rate_limit_operation_points: 100,
314 }
315 }
316}
317
318impl Default for SecurityConfig {
319 fn default() -> Self {
320 Self {
321 publicize_fhir_metadata: true,
322 mfa: MFAConfig::default(),
323 encryption: SecretProviderConfig::default(),
324 aes_key: None,
325 certification_key: None,
326 }
327 }
328}
329
330impl Default for MFAConfig {
331 fn default() -> Self {
332 Self {
333 max_credentials_per_user: 1,
334 }
335 }
336}
337
338impl Default for OperationsConfig {
339 fn default() -> Self {
340 let available = std::thread::available_parallelism()
341 .map(std::num::NonZeroUsize::get)
342 .unwrap_or(4);
343
344 Self {
345 deno_pool_threads: (available / 2).max(2),
346 }
347 }
348}
349
350impl Default for SecretProviderConfig {
351 fn default() -> Self {
352 Self::Environment {
353 prefix: Some("HASTE_SECRET_".to_string()),
354 }
355 }
356}