headless_lms_server/
config.rs1use crate::{
4 OAuthClient,
5 domain::{models_requests::JwtKey, request_span_middleware::RequestSpan},
6};
7use actix_http::{StatusCode, body::MessageBody};
8use actix_web::{
9 HttpResponse,
10 error::InternalError,
11 web::{self, Data, PayloadConfig, ServiceConfig},
12};
13use anyhow::Context;
14use headless_lms_utils::{
15 ApplicationConfiguration, cache::Cache, file_store::FileStore, icu4x::Icu4xBlob,
16 ip_to_country::IpToCountryMapper,
17};
18use oauth2::{AuthUrl, ClientId, ClientSecret, TokenUrl, basic::BasicClient};
19use sqlx::{PgPool, postgres::PgPoolOptions};
20use std::{env, sync::Arc};
21use url::Url;
22
23pub struct ServerConfigBuilder {
24 pub database_url: String,
25 pub oauth_application_id: String,
26 pub oauth_secret: String,
27 pub auth_url: Url,
28 pub icu4x_postcard_path: String,
29 pub file_store: Arc<dyn FileStore + Send + Sync>,
30 pub app_conf: ApplicationConfiguration,
31 pub redis_url: String,
32 pub jwt_password: String,
33}
34
35impl ServerConfigBuilder {
36 pub fn try_from_env() -> anyhow::Result<Self> {
37 Ok(Self {
38 database_url: env::var("DATABASE_URL").context("DATABASE_URL must be defined")?,
39 oauth_application_id: env::var("OAUTH_APPLICATION_ID")
40 .context("OAUTH_APPLICATION_ID must be defined")?,
41 oauth_secret: env::var("OAUTH_SECRET").context("OAUTH_SECRET must be defined")?,
42 auth_url: "https://tmc.mooc.fi/oauth/token"
43 .parse()
44 .context("Failed to parse auth_url")?,
45 icu4x_postcard_path: env::var("ICU4X_POSTCARD_PATH")
46 .context("ICU4X_POSTCARD_PATH must be defined")?,
47 file_store: crate::setup_file_store(),
48 app_conf: ApplicationConfiguration::try_from_env()?,
49 redis_url: env::var("REDIS_URL").context("REDIS_URL must be defined")?,
50 jwt_password: env::var("JWT_PASSWORD").context("JWT_PASSWORD must be defined")?,
51 })
52 }
53
54 pub async fn build(self) -> anyhow::Result<ServerConfig> {
55 let json_config = web::JsonConfig::default().limit(2_097_152).error_handler(
56 |err, _req| -> actix_web::Error {
57 info!("Bad request: {}", &err);
58 let body = format!("{{\"title\": \"Bad Request\", \"message\": \"{}\"}}", &err);
59 let response = HttpResponse::with_body(StatusCode::BAD_REQUEST, body.boxed());
61 InternalError::from_response(err, response).into()
62 },
63 );
64 let json_config = Data::new(json_config);
65
66 let payload_config = PayloadConfig::default().limit(2_097_152);
67 let payload_config = Data::new(payload_config);
68
69 let db_pool = PgPoolOptions::new()
70 .max_connections(15)
71 .min_connections(5)
72 .connect(&self.database_url)
73 .await?;
74 let db_pool = Data::new(db_pool);
75
76 let oauth_client: OAuthClient = BasicClient::new(ClientId::new(self.oauth_application_id))
77 .set_client_secret(ClientSecret::new(self.oauth_secret))
78 .set_auth_uri(AuthUrl::from_url(self.auth_url.clone()))
79 .set_token_uri(TokenUrl::from_url(self.auth_url));
80 let oauth_client = Data::new(oauth_client);
81
82 let icu4x_blob = Icu4xBlob::new(&self.icu4x_postcard_path)?;
83 let icu4x_blob = Data::new(icu4x_blob);
84
85 let app_conf = Data::new(self.app_conf);
86
87 let ip_to_country_mapper = IpToCountryMapper::new(&app_conf)?;
88 let ip_to_country_mapper = Data::new(ip_to_country_mapper);
89
90 let cache = Cache::new(&self.redis_url)?;
91 let cache = Data::new(cache);
92
93 let jwt_key = JwtKey::new(&self.jwt_password)?;
94 let jwt_key = Data::new(jwt_key);
95
96 let config = ServerConfig {
97 json_config,
98 db_pool,
99 oauth_client,
100 icu4x_blob,
101 ip_to_country_mapper,
102 file_store: self.file_store,
103 app_conf,
104 jwt_key,
105 cache,
106 payload_config,
107 };
108 Ok(config)
109 }
110}
111
112#[derive(Clone)]
113pub struct ServerConfig {
114 pub payload_config: Data<PayloadConfig>,
115 pub json_config: Data<web::JsonConfig>,
116 pub db_pool: Data<PgPool>,
117 pub oauth_client: Data<OAuthClient>,
118 pub icu4x_blob: Data<Icu4xBlob>,
119 pub ip_to_country_mapper: Data<IpToCountryMapper>,
120 pub file_store: Arc<dyn FileStore + Send + Sync>,
121 pub app_conf: Data<ApplicationConfiguration>,
122 pub cache: Data<Cache>,
123 pub jwt_key: Data<JwtKey>,
124}
125
126pub fn configure(config: &mut ServiceConfig, server_config: ServerConfig) {
128 let ServerConfig {
129 json_config,
130 db_pool,
131 oauth_client,
132 icu4x_blob,
133 ip_to_country_mapper,
134 file_store,
135 app_conf,
136 jwt_key,
137 cache,
138 payload_config,
139 } = server_config;
140 let file_store = Data::from(file_store as Arc<dyn FileStore>);
143 config
144 .app_data(payload_config)
145 .app_data(json_config)
146 .app_data(db_pool)
147 .app_data(oauth_client)
148 .app_data(icu4x_blob)
149 .app_data(ip_to_country_mapper)
150 .app_data(file_store)
151 .app_data(app_conf)
152 .app_data(jwt_key)
153 .app_data(cache)
154 .service(
155 web::scope("/api/v0")
156 .wrap(RequestSpan)
157 .configure(crate::controllers::configure_controllers),
158 );
159}