From b22588cd0dce149401cabe08fbba0993e9d5018e Mon Sep 17 00:00:00 2001 From: Simon Bruder Date: Sat, 27 Jul 2024 22:15:30 +0200 Subject: [PATCH] Move app creation to lib.rs This makes integration testing much easier as it can reuse the app instance. --- src/lib.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 72 ++++++-------------------------------------------- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9ba3f8f..606bd1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,3 +11,79 @@ pub mod middleware; mod test_utils; pub use config::Config; + +use actix_identity::IdentityMiddleware; +use actix_session::{storage::CookieSessionStore, SessionMiddleware}; +use actix_web::middleware::ErrorHandlers; +use actix_web::{cookie::Key, http::StatusCode, web, App, HttpResponse}; +use base64::prelude::{Engine as _, BASE64_STANDARD}; +use log::warn; +use mime_guess::from_path; +use rust_embed::Embed; +use sqlx::PgPool; + +use crate::database::{ + ItemClassRepository, ItemEventRepository, ItemRepository, ItemStateRepository, +}; + +#[derive(Embed)] +#[folder = "static"] +struct Static; + +pub fn app( + config: &Config, + pool: &PgPool, +) -> App< + impl actix_web::dev::ServiceFactory< + actix_web::dev::ServiceRequest, + Config = (), + Response = actix_web::dev::ServiceResponse< + actix_web::body::EitherBody, + >, + Error = actix_web::Error, + InitError = (), + >, +> { + let secret_key = match config.secret_key { + Some(ref encoded) => Key::from( + &BASE64_STANDARD + .decode(encoded) + .expect("failed to decode base64 in SECRET_KEY"), + ), + None => { + warn!("SECRET_KEY was not specified, using randomly generated key"); + Key::generate() + } + }; + + App::new() + .app_data(web::Data::new(config.clone())) + .app_data(web::Data::new(pool.clone())) + .app_data(web::Data::new(ItemClassRepository::new(pool.clone()))) + .app_data(web::Data::new(ItemEventRepository::new(pool.clone()))) + .app_data(web::Data::new(ItemStateRepository::new(pool.clone()))) + .app_data(web::Data::new(ItemRepository::new(pool.clone()))) + .service(web::scope("/static").route( + "/{_:.*}", + web::get().to(|path: web::Path| async { + Static::get(&path) + .map(|embedded_file| match from_path(path.into_inner()).first() { + Some(mime_type) => HttpResponse::Ok() + .content_type(mime_type) + .body(embedded_file.data), + None => HttpResponse::Ok().body(embedded_file.data), + }) + .unwrap_or(HttpResponse::NotFound().body(())) + }), + )) + .configure(frontend::config) + .wrap(ErrorHandlers::new().handler( + StatusCode::UNAUTHORIZED, + middleware::error_handlers::redirect_to_login, + )) + .wrap(IdentityMiddleware::default()) + .wrap(SessionMiddleware::new( + CookieSessionStore::default(), + secret_key.clone(), + )) +} diff --git a/src/main.rs b/src/main.rs index 7347ff8..158eb7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,45 +2,20 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -use actix_identity::IdentityMiddleware; -use actix_session::{storage::CookieSessionStore, SessionMiddleware}; -use actix_web::middleware::ErrorHandlers; -use actix_web::{cookie::Key, http::StatusCode, web, App, HttpResponse, HttpServer}; -use base64::prelude::{Engine as _, BASE64_STANDARD}; +use actix_web::HttpServer; use clap::Parser; -use li7y::database::item_events::ItemEventRepository; -use li7y::database::item_states::ItemStateRepository; -use li7y::database::items::ItemRepository; -use li7y::database::ItemClassRepository; -use log::{info, warn}; -use mime_guess::from_path; -use rust_embed::Embed; +use log::info; use li7y::Config; -#[derive(Embed)] -#[folder = "static"] -struct Static; - #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let config = Config::parse(); - let secret_key = match config.secret_key { - Some(ref encoded) => Key::from( - &BASE64_STANDARD - .decode(encoded) - .expect("failed to decode base64 in SECRET_KEY"), - ), - None => { - warn!("SECRET_KEY was not specified, using randomly generated key"); - Key::generate() - } - }; - - let pool: sqlx::PgPool = sqlx::Pool::::connect(&config.database_url) + // This can’t be included in app, because app gets called in a (non-async) closure + let pool = sqlx::Pool::::connect(&config.database_url) .await .expect("failed to connect to database"); @@ -54,39 +29,8 @@ async fn main() -> std::io::Result<()> { info!("Starting on {address}:{port}"); - HttpServer::new(move || { - App::new() - .app_data(web::Data::new(config.clone())) - .app_data(web::Data::new(pool.clone())) - .app_data(web::Data::new(ItemClassRepository::new(pool.clone()))) - .app_data(web::Data::new(ItemEventRepository::new(pool.clone()))) - .app_data(web::Data::new(ItemStateRepository::new(pool.clone()))) - .app_data(web::Data::new(ItemRepository::new(pool.clone()))) - .service(web::scope("/static").route( - "/{_:.*}", - web::get().to(|path: web::Path| async { - Static::get(&path) - .map(|embedded_file| match from_path(path.into_inner()).first() { - Some(mime_type) => HttpResponse::Ok() - .content_type(mime_type) - .body(embedded_file.data), - None => HttpResponse::Ok().body(embedded_file.data), - }) - .unwrap_or(HttpResponse::NotFound().body(())) - }), - )) - .configure(li7y::frontend::config) - .wrap(ErrorHandlers::new().handler( - StatusCode::UNAUTHORIZED, - li7y::middleware::error_handlers::redirect_to_login, - )) - .wrap(IdentityMiddleware::default()) - .wrap(SessionMiddleware::new( - CookieSessionStore::default(), - secret_key.clone(), - )) - }) - .bind((address, port))? - .run() - .await + HttpServer::new(move || li7y::app(&config, &pool)) + .bind((address, port))? + .run() + .await }