use rocket::{ catch, catchers, fairing::AdHoc, form::Form, fs::FileServer, get, post, request::FlashMessage, response::{Flash, Redirect}, routes, Build, FromForm, Rocket, State, }; use rocket_dyn_templates::{tera::Context, Template}; use serde::Deserialize; use sqlx::SqlitePool; use crate::model::{ log::Log, tripdetails::TripDetails, triptype::TripType, user::{User, UserWithRoles}, usertrip::{UserTrip, UserTripDeleteError, UserTripError}, }; pub(crate) mod admin; mod auth; mod boatdamage; mod cox; mod ergo; mod log; mod misc; mod stat; #[derive(FromForm, Debug)] struct LoginForm<'r> { name: &'r str, password: &'r str, } #[post("/", data = "")] async fn wikiauth(db: &State, login: Form>) -> String { match User::login(db, login.name, login.password).await { Ok(_) => "SUCC".into(), Err(_) => "FAIL".into(), } } #[get("/")] async fn index(db: &State, user: User, flash: Option>) -> Template { let mut context = Context::new(); if user.has_role(db, "cox").await || user.has_role(db, "admin").await { let triptypes = TripType::all(db).await; context.insert("trip_types", &triptypes); } let days = user.get_days(db).await; if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); context.insert("days", &days); Template::render("index", context.into_json()) } #[get("/join/?")] async fn join( db: &State, trip_details_id: i64, user: User, user_note: Option, ) -> Flash { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/"), "Trip_details do not exist."); }; match UserTrip::create(db, &user, &trip_details, user_note).await { Ok(_) => { Log::create( db, format!( "User {} registered for trip_details.id={}", user.name, trip_details_id ), ) .await; Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!") } Err(UserTripError::EventAlreadyFull) => { Flash::error(Redirect::to("/"), "Event bereits ausgebucht!") } Err(UserTripError::AlreadyRegistered) => { Flash::error(Redirect::to("/"), "Du nimmst bereits teil!") } Err(UserTripError::AlreadyRegisteredAsCox) => { Flash::error(Redirect::to("/"), "Du hilfst bereits als Steuerperson aus!") } Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error( Redirect::to("/"), "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)", ), Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error( Redirect::to("/"), "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", ), Err(UserTripError::NotAllowedToAddGuest) => Flash::error( Redirect::to("/"), "Du darfst keine Gäste hinzufügen.", ), Err(UserTripError::DetailsLocked) => Flash::error( Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.", ), } } #[get("/remove//")] async fn remove_guest( db: &State, trip_details_id: i64, user: User, name: String, ) -> Flash { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, Some(name)).await { Ok(_) => { Log::create( db, format!( "User {} unregistered for trip_details.id={}", user.name, trip_details_id ), ) .await; Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( db, format!( "User {} tried to unregister for locked trip_details.id={}", user.name, trip_details_id ), ) .await; Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") } Err(UserTripDeleteError::GuestNotParticipating) => { Flash::error(Redirect::to("/"), "Gast nicht angemeldet.") } Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error( Redirect::to("/"), "Keine Berechtigung um den Gast zu entfernen.", ), } } #[get("/remove/")] async fn remove(db: &State, trip_details_id: i64, user: User) -> Flash { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, None).await { Ok(_) => { Log::create( db, format!( "User {} unregistered for trip_details.id={}", user.name, trip_details_id ), ) .await; Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( db, format!( "User {} tried to unregister for locked trip_details.id={}", user.name, trip_details_id ), ) .await; Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") } Err(_) => { panic!("Not possible to be here"); } } } #[catch(401)] //unauthorized fn unauthorized_error() -> Redirect { Redirect::to("/auth") } #[derive(Deserialize)] #[serde(crate = "rocket::serde")] pub struct Config { rss_key: String, } pub fn config(rocket: Rocket) -> Rocket { rocket .mount("/", routes![index, join, remove, remove_guest]) .mount("/auth", auth::routes()) .mount("/wikiauth", routes![wikiauth]) .mount("/log", log::routes()) .mount("/ergo", ergo::routes()) .mount("/stat", stat::routes()) .mount("/boatdamage", boatdamage::routes()) .mount("/cox", cox::routes()) .mount("/admin", admin::routes()) .mount("/", misc::routes()) .mount("/public", FileServer::from("static/")) .register("/", catchers![unauthorized_error]) .attach(Template::fairing()) .attach(AdHoc::config::()) } #[cfg(test)] mod test { use rocket::{ http::{ContentType, Status}, local::asynchronous::Client, }; use sqlx::SqlitePool; use crate::testdb; #[sqlx::test] fn test_index() { let db = testdb!(); let rocket = rocket::build().manage(db.clone()); let rocket = crate::tera::config(rocket); let client = Client::tracked(rocket).await.unwrap(); let login = client .post("/auth") .header(ContentType::Form) // Set the content type to form .body("name=cox&password=cox"); // Add the form data to the request body; login.dispatch().await; let req = client.get("/"); let response = req.dispatch().await; assert_eq!(response.status(), Status::Ok); assert!(response.into_string().await.unwrap().contains("Ausfahrten")); } #[sqlx::test] fn test_without_login() { let db = testdb!(); let rocket = rocket::build().manage(db.clone()); let rocket = crate::tera::config(rocket); let client = Client::tracked(rocket).await.unwrap(); let req = client.get("/"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.headers().get("Location").next(), Some("/auth")); } #[sqlx::test] fn test_join_and_remove() { let db = testdb!(); let rocket = rocket::build().manage(db.clone()); let rocket = crate::tera::config(rocket); let client = Client::tracked(rocket).await.unwrap(); let login = client .post("/auth") .header(ContentType::Form) // Set the content type to form .body("name=rower&password=rower"); // Add the form data to the request body; login.dispatch().await; let req = client.get("/join/1"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() .get("_flash") .expect("Expected flash cookie"); assert_eq!(flash_cookie.value(), "7:successErfolgreich angemeldet!"); let req = client.get("/remove/1"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() .get("_flash") .expect("Expected flash cookie"); assert_eq!(flash_cookie.value(), "7:successErfolgreich abgemeldet!"); } #[sqlx::test] fn test_join_invalid_event() { let db = testdb!(); let rocket = rocket::build().manage(db.clone()); let rocket = crate::tera::config(rocket); let client = Client::tracked(rocket).await.unwrap(); let login = client .post("/auth") .header(ContentType::Form) // Set the content type to form .body("name=rower&password=rower"); // Add the form data to the request body; login.dispatch().await; let req = client.get("/join/9999"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() .get("_flash") .expect("Expected flash cookie"); assert_eq!(flash_cookie.value(), "5:errorTrip_details do not exist."); } #[sqlx::test] fn test_public() { let db = testdb!(); let rocket = rocket::build().manage(db.clone()); let rocket = crate::tera::config(rocket); let client = Client::tracked(rocket).await.unwrap(); let req = client.get("/public/main.css"); let response = req.dispatch().await; assert_eq!(response.status(), Status::Ok); let req = client.get("/public/main.js"); let response = req.dispatch().await; assert_eq!(response.status(), Status::Ok); } }