diff --git a/server/chroma/src/app.rs b/server/chroma/src/app.rs deleted file mode 100644 index 5538d96..0000000 --- a/server/chroma/src/app.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::time::Duration; - -use actix_cors::Cors; -use actix_web::{web, App, HttpServer}; -use anyhow::{anyhow, bail, Result}; -use cabbage::KoalaApi; -use noiseless_tracing_actix_web::NoiselessRootSpanBuilder; -use tracing::{info, warn}; -use tracing_actix_web::TracingLogger; - -use dal::database::Database; -use dal::storage_engine::{S3Config, Storage}; - -use crate::config::Config; -use crate::routes; -use crate::routes::appdata::{AlbumIdCache, AppData, Ratelimits, SessionIdCache, WebData}; -use crate::routes::routable::Routable; - -/// Run the chroma server and will block until the server is stopped or crashes -/// -/// # Errors -/// -/// - If the configuration is invalid -/// - If the database connection cannot be completed -/// - If the S3 configuration is invalid -/// - If the port for the server is already in use -/// - If a problem occurs in one of the server routes -pub async fn run() -> Result<()> { - // Initialize chroma's core components - let config = init_config()?; - let db = init_database(&config).await?; - let storage = init_storage(&config).await?; - - // Package the core components up into the AppData struct - let app_data = AppData { - koala: KoalaApi::new(config.koala_base_redirect_uri().clone())?, - db, - storage, - config, - ratelimits: Ratelimits::new(), - }; - - // Run the webserver using the AppData until stopped or crash - start_webserver(app_data).await -} - -/// Parses the configuration using -fn init_config() -> Result { - info!("parsing config"); - let config = Config::parse().map_err(|err| anyhow!("failed to parse config: {:#}", err))?; - - if !config.validate() { - bail!("config is not valid"); - } - - if !config.service_tokens.is_empty() { - warn!("there are service tokens configured; make sure these are, and stay, confidential!"); - } - - Ok(config) -} - -async fn init_database(config: &Config) -> Result { - info!("initializing database connection"); - Database::new(config.database_config().unwrap()) - .await - .map_err(|err| anyhow!("failed to initialize database connection: {:#}", err)) -} - -async fn init_storage(config: &Config) -> Result { - info!("initializing S3 storage engine"); - Storage::new(S3Config { - bucket_name: config.s3_bucket_name.clone().unwrap(), - endpoint_url: config.s3_endpoint_url.clone().unwrap(), - region: config.s3_region.clone().unwrap(), - access_key_id: config.s3_access_key_id.clone().unwrap(), - secret_access_key: config.s3_secret_access_key.clone().unwrap(), - use_path_style: config.s3_force_path_style(), - create_bucket: config.s3_create_bucket_on_startup(), - }) - .await - .map_err(|err| anyhow!("failed to initialize S3 storage engine: {:#}", err)) -} - -async fn start_webserver(app_data: AppData) -> Result<()> { - info!("starting web server"); - HttpServer::new(move || { - App::new() - .wrap(Cors::permissive()) - .wrap(TracingLogger::::new()) - .app_data(WebData::new(app_data.clone())) - .app_data(web::Data::new( - SessionIdCache::builder() - .max_capacity(10000) - .time_to_live(Duration::from_secs(30)) - .build(), - )) - .app_data(web::Data::new( - AlbumIdCache::builder().max_capacity(10000).build(), - )) - .configure(routes::Router::configure) - }) - .bind(&format!( - "0.0.0.0:{}", - std::env::var("HTTP_PORT").unwrap_or("8000".into()) - )) - .map_err(|err| anyhow!("failed to bind web server to port 8000: {:#}", err))? - .run() - .await?; - - Ok(()) -} diff --git a/server/chroma/src/exit.rs b/server/chroma/src/exit.rs new file mode 100644 index 0000000..9d2dcb2 --- /dev/null +++ b/server/chroma/src/exit.rs @@ -0,0 +1,24 @@ +use std::process::{ExitCode, Termination}; + +use tracing::error; + +pub enum Exit { + Ok, + Err(anyhow::Error), +} + +impl Termination for Exit { + fn report(self) -> ExitCode { + match self { + Exit::Ok => ExitCode::from(0), + Exit::Err(err) => { + error!("{}", err); + err.chain() + .skip(1) + .for_each(|cause| error!("because: {}", cause)); + + ExitCode::from(1) + } + } + } +} diff --git a/server/chroma/src/main.rs b/server/chroma/src/main.rs index 5933035..8ffaef2 100644 --- a/server/chroma/src/main.rs +++ b/server/chroma/src/main.rs @@ -1,21 +1,44 @@ extern crate core; -use std::process::ExitCode; +use std::time::Duration; +use actix_cors::Cors; +use actix_web::{web, App, HttpServer}; +use anyhow::{anyhow, bail, Result}; +use cabbage::KoalaApi; use dotenv::dotenv; +use noiseless_tracing_actix_web::NoiselessRootSpanBuilder; use tracing::level_filters::LevelFilter; -use tracing::{error, warn}; +use tracing::{info, warn}; +use tracing_actix_web::TracingLogger; use tracing_subscriber::fmt::layer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; -mod app; +use dal::database::Database; +use dal::storage_engine::{S3Config, Storage}; + +use crate::config::Config; +use crate::exit::Exit; +use crate::routes::appdata::{AlbumIdCache, AppData, Ratelimits, SessionIdCache, WebData}; +use crate::routes::routable::Routable; + mod config; +mod exit; mod routes; +/// Run the chroma server and will block until the server is stopped or crashes +/// +/// # Errors +/// +/// - If the configuration is invalid +/// - If the database connection cannot be completed +/// - If the S3 configuration is invalid +/// - If the port for the server is already in use +/// - If a problem occurs in one of the server routes #[tokio::main] -async fn main() -> ExitCode { +async fn main() -> Exit { // Try to load the environment variables from the .env file let dotenv_err = dotenv().err(); @@ -27,15 +50,37 @@ async fn main() -> ExitCode { warn!("failed to load .env file: {:#}", err); } - // Run the server - if let Err(err) = app::run().await { - error!("{}", err); - err.chain() - .skip(1) - .for_each(|cause| error!("because: {}", cause)); - ExitCode::from(1) - } else { - ExitCode::from(0) + // Initialize chroma's core components + let config = match init_config() { + Ok(v) => v, + Err(err) => return Exit::Err(err), + }; + let db = match init_database(&config).await { + Ok(v) => v, + Err(err) => return Exit::Err(err), + }; + let storage = match init_storage(&config).await { + Ok(v) => v, + Err(err) => return Exit::Err(err), + }; + let koala = match KoalaApi::new(config.koala_base_redirect_uri().clone()) { + Ok(v) => v, + Err(err) => return Exit::Err(err.into()), + }; + + // Package the core components up into the AppData struct + let app_data = AppData { + koala, + db, + storage, + config, + ratelimits: Ratelimits::new(), + }; + + // Run the webserver using the AppData until stopped or crash + match start_webserver(app_data).await { + Ok(_) => Exit::Ok, + Err(err) => Exit::Err(err), } } @@ -49,3 +94,70 @@ fn init_tracing() { ) .init(); } + +/// Parses the configuration using +fn init_config() -> anyhow::Result { + info!("parsing config"); + let config = Config::parse().map_err(|err| anyhow!("failed to parse config: {:#}", err))?; + + if !config.validate() { + bail!("config is not valid"); + } + + if !config.service_tokens.is_empty() { + warn!("there are service tokens configured; make sure these are, and stay, confidential!"); + } + + Ok(config) +} + +async fn init_database(config: &Config) -> anyhow::Result { + info!("initializing database connection"); + Database::new(config.database_config().unwrap()) + .await + .map_err(|err| anyhow!("failed to initialize database connection: {:#}", err)) +} + +async fn init_storage(config: &Config) -> anyhow::Result { + info!("initializing S3 storage engine"); + Storage::new(S3Config { + bucket_name: config.s3_bucket_name.clone().unwrap(), + endpoint_url: config.s3_endpoint_url.clone().unwrap(), + region: config.s3_region.clone().unwrap(), + access_key_id: config.s3_access_key_id.clone().unwrap(), + secret_access_key: config.s3_secret_access_key.clone().unwrap(), + use_path_style: config.s3_force_path_style(), + create_bucket: config.s3_create_bucket_on_startup(), + }) + .await + .map_err(|err| anyhow!("failed to initialize S3 storage engine: {:#}", err)) +} + +async fn start_webserver(app_data: AppData) -> Result<()> { + info!("starting web server"); + HttpServer::new(move || { + App::new() + .wrap(Cors::permissive()) + .wrap(TracingLogger::::new()) + .app_data(WebData::new(app_data.clone())) + .app_data(web::Data::new( + SessionIdCache::builder() + .max_capacity(10000) + .time_to_live(Duration::from_secs(30)) + .build(), + )) + .app_data(web::Data::new( + AlbumIdCache::builder().max_capacity(10000).build(), + )) + .configure(routes::Router::configure) + }) + .bind(&format!( + "0.0.0.0:{}", + std::env::var("HTTP_PORT").unwrap_or("8000".into()) + )) + .map_err(|err| anyhow!("failed to bind web server to port 8000: {:#}", err))? + .run() + .await?; + + Ok(()) +}