use axum::{error_handling::HandleErrorLayer, routing::IntoMakeService, BoxError}; use sqlx::SqlitePool; #[macro_use] extern crate justerror; /// Some public interfaces for interacting with the database outside of the web /// app pub use db::get_db_pool; pub mod import_utils; pub use users::User; pub use watches::{ShowKind, Watch, WatchQuest}; pub type WWRouter = axum::Router; // everything else is private to the crate mod auth; mod db; mod generic_handlers; mod login; mod signup; mod templates; mod users; mod util; mod watches; // things we want in the crate namespace use auth::AuthSession; use optional_optional_user::OptionalOptionalUser; use templates::*; use watches::templates::*; /// Returns the router to be used as a service or test object, you do you. pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService { // don't bother bringing handlers into the whole crate namespace use auth::*; use axum::{middleware, routing::get}; use generic_handlers::{handle_slash, handle_slash_redir}; use login::{get_login, get_logout, post_login, post_logout}; use signup::{get_create_user, get_signup_success, post_create_user}; use tower_http::services::ServeDir; use watches::handlers::{ get_add_new_watch, get_search_watch, get_watch, get_watch_status, get_watches, post_add_new_watch, post_add_watch_quest, }; let auth_layer = { let sessions = session_layer(db_pool.clone()).await; let store = AuthStore::new(db_pool.clone()); tower::ServiceBuilder::new() .layer(HandleErrorLayer::new(|_: BoxError| async { http::StatusCode::BAD_REQUEST })) .layer(axum_login::AuthManagerLayerBuilder::new(store, sessions).build()) }; let assets_dir = std::env::current_dir().unwrap().join("assets"); let assets_svc = ServeDir::new(assets_dir.as_path()); axum::Router::new() .route("/", get(handle_slash).post(handle_slash)) .nest_service("/assets", assets_svc) .route("/signup", get(get_create_user).post(post_create_user)) .route("/signup_success/:id", get(get_signup_success)) .route("/login", get(get_login).post(post_login)) .route("/logout", get(get_logout).post(post_logout)) .route("/watches", get(get_watches)) .route("/watch", get(get_watch)) .route("/watch/:id", get(get_watch)) .route("/watch/status/:id", get(get_watch_status)) .route("/search", get(get_search_watch)) .route("/add", get(get_add_new_watch).post(post_add_new_watch)) .route( "/add/watch", get(get_search_watch).post(post_add_watch_quest), ) .fallback(handle_slash_redir) .layer(middleware::from_fn_with_state( db_pool.clone(), users::handle_update_last_seen, )) .layer(auth_layer) .with_state(db_pool) .into_make_service() } #[cfg(test)] pub mod test_utils; //-************************************************************************ // tests for the proc macro for optional user //-************************************************************************ #[cfg(test)] mod test { use super::{MainPage, OptionalOptionalUser, SignupSuccessPage, User}; #[test] fn main_page_has_optional_user() { assert!(MainPage::default().has_optional_user()); assert!(MainPage::default().has_user()); } #[test] fn signup_success_has_no_optional_user() { assert!(!SignupSuccessPage::default().has_optional_user()); } #[test] fn user_is_not_optional() { #[derive(Default, OptionalOptionalUser)] struct TestThing { user: User, } assert!(!TestThing::default().has_optional_user()); assert!(TestThing::default().has_mandatory_user()); assert!(TestThing::default().has_user()); } #[test] fn user_is_not_user() { #[derive(Default, OptionalOptionalUser)] struct TestThing { user: Option, } assert!(!TestThing::default().has_optional_user()); assert!(!TestThing::default().has_user()); } }