From 3e2e058bcce1e10ad5447f0fac5a409c35a910e8 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 14:08:15 +0100 Subject: [PATCH 01/72] limit users to proper role, Fixes #135 --- seeds.sql | 8 + src/model/logbook.rs | 4 + src/model/user.rs | 74 ++++-- src/tera/boatdamage.rs | 11 +- src/tera/cox.rs | 2 +- src/tera/log.rs | 45 ++-- src/tera/mod.rs | 262 +++------------------ src/tera/planned.rs | 273 ++++++++++++++++++++++ src/tera/stat.rs | 12 +- templates/includes/macros.html.tera | 4 +- templates/index.html.tera | 346 ++++++---------------------- templates/planned.html.tera | 294 +++++++++++++++++++++++ 12 files changed, 770 insertions(+), 565 deletions(-) create mode 100644 src/tera/planned.rs create mode 100644 templates/planned.html.tera diff --git a/seeds.sql b/seeds.sql index 9acfe7f..ba7b50f 100644 --- a/seeds.sql +++ b/seeds.sql @@ -2,18 +2,26 @@ INSERT INTO "role" (name) VALUES ('admin'); INSERT INTO "role" (name) VALUES ('cox'); INSERT INTO "role" (name) VALUES ('scheckbuch'); INSERT INTO "role" (name) VALUES ('tech'); +INSERT INTO "role" (name) VALUES ('Donau Linz'); INSERT INTO "user" (name, pw) VALUES('admin', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM'); INSERT INTO "user_role" (user_id, role_id) VALUES(1,1); INSERT INTO "user_role" (user_id, role_id) VALUES(1,2); +INSERT INTO "user_role" (user_id, role_id) VALUES(1,5); INSERT INTO "user" (name, pw) VALUES('rower', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); +INSERT INTO "user_role" (user_id, role_id) VALUES(2,5); INSERT INTO "user" (name, pw) VALUES('guest', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$GF6gizbI79Bh0zA9its8S0gram956v+YIV8w8VpwJnQ'); +INSERT INTO "user_role" (user_id, role_id) VALUES(3,5); INSERT INTO "user_role" (user_id, role_id) VALUES(3,3); INSERT INTO "user" (name, pw) VALUES('cox', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); +INSERT INTO "user_role" (user_id, role_id) VALUES(4,5); INSERT INTO "user_role" (user_id, role_id) VALUES(4,2); INSERT INTO "user" (name) VALUES('new'); +INSERT INTO "user_role" (user_id, role_id) VALUES(5,5); INSERT INTO "user" (name, pw) VALUES('cox2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); +INSERT INTO "user_role" (user_id, role_id) VALUES(6,5); INSERT INTO "user_role" (user_id, role_id) VALUES(6,2); INSERT INTO "user" (name, pw) VALUES('rower2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); +INSERT INTO "user_role" (user_id, role_id) VALUES(7,5); INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('10:00', 2, '1970-01-01', 'trip_details for a planned event'); INSERT INTO "planned_event" (name, planned_amount_cox, trip_details_id) VALUES('test-planned-event', 2, 1); diff --git a/src/model/logbook.rs b/src/model/logbook.rs index 98d6c87..dfb8278 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -264,6 +264,10 @@ ORDER BY departure DESC return Err(LogbookCreateError::BoatNotFound); }; + if boat.amount_seats == 1 && log.rowers.is_empty() { + log.rowers = vec![created_by_user.id]; + } + if boat.amount_seats == 1 { log.shipmaster = Some(log.rowers[0]); log.steering_person = Some(log.rowers[0]); diff --git a/src/model/user.rs b/src/model/user.rs index 3fc4943..eb7edbd 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -440,23 +440,20 @@ impl<'r> FromRequest<'r> for User { Ok(user_id) => { let db = req.rocket().state::().unwrap(); let Some(user) = User::find_by_id(db, user_id).await else { - return Outcome::Error((Status::Unauthorized, LoginError::UserNotFound)); + return Outcome::Error((Status::Forbidden, LoginError::UserNotFound)); }; if user.deleted { - return Outcome::Error((Status::Unauthorized, LoginError::UserDeleted)); + return Outcome::Error((Status::Forbidden, LoginError::UserDeleted)); } user.logged_in(db).await; let mut cookie = Cookie::new("loggedin_user", format!("{}", user.id)); - cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(12)); + cookie.set_expires(OffsetDateTime::now_utc() + Duration::weeks(2)); req.cookies().add_private(cookie); Outcome::Success(user) } - Err(_) => { - println!("{:?}", user_id.value()); - Outcome::Error((Status::Unauthorized, LoginError::DeserializationError)) - } + Err(_) => Outcome::Error((Status::Unauthorized, LoginError::DeserializationError)), }, None => Outcome::Error((Status::Unauthorized, LoginError::NotLoggedIn)), } @@ -487,7 +484,7 @@ impl<'r> FromRequest<'r> for TechUser { if user.has_role(db, "tech").await { Outcome::Success(TechUser { user }) } else { - Outcome::Error((Status::Unauthorized, LoginError::NotACox)) + Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), @@ -530,7 +527,7 @@ impl<'r> FromRequest<'r> for CoxUser { if user.has_role(db, "cox").await { Outcome::Success(CoxUser { user }) } else { - Outcome::Error((Status::Unauthorized, LoginError::NotACox)) + Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), @@ -555,7 +552,7 @@ impl<'r> FromRequest<'r> for AdminUser { if user.has_role(db, "admin").await { Outcome::Success(AdminUser { user }) } else { - Outcome::Error((Status::Unauthorized, LoginError::NotACox)) + Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), @@ -565,22 +562,65 @@ impl<'r> FromRequest<'r> for AdminUser { } #[derive(Debug, Serialize, Deserialize)] -pub struct NonGuestUser { - pub(crate) user: User, -} +pub struct AllowedForPlannedTripsUser(pub(crate) User); #[async_trait] -impl<'r> FromRequest<'r> for NonGuestUser { +impl<'r> FromRequest<'r> for AllowedForPlannedTripsUser { type Error = LoginError; async fn from_request(req: &'r Request<'_>) -> request::Outcome { let db = req.rocket().state::().unwrap(); match User::from_request(req).await { Outcome::Success(user) => { - if !user.has_role(db, "scheckbuch").await { - Outcome::Success(NonGuestUser { user }) + if user.has_role(db, "Donau Linz").await { + Outcome::Success(AllowedForPlannedTripsUser(user)) + } else if user.has_role(db, "scheckbuch").await { + Outcome::Success(AllowedForPlannedTripsUser(user)) } else { - Outcome::Error((Status::Unauthorized, LoginError::NotACox)) + Outcome::Error((Status::Forbidden, LoginError::NotACox)) + } + } + Outcome::Error(f) => Outcome::Error(f), + Outcome::Forward(f) => Outcome::Forward(f), + } + } +} + +impl Into for AllowedForPlannedTripsUser { + fn into(self) -> User { + self.0 + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct DonauLinzUser(pub(crate) User); + +impl Into for DonauLinzUser { + fn into(self) -> User { + self.0 + } +} + +impl Deref for DonauLinzUser { + type Target = User; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[async_trait] +impl<'r> FromRequest<'r> for DonauLinzUser { + type Error = LoginError; + + async fn from_request(req: &'r Request<'_>) -> request::Outcome { + let db = req.rocket().state::().unwrap(); + match User::from_request(req).await { + Outcome::Success(user) => { + if user.has_role(db, "Donau Linz").await { + Outcome::Success(DonauLinzUser(user)) + } else { + Outcome::Error((Status::Forbidden, LoginError::NotACox)) } } Outcome::Error(f) => Outcome::Error(f), diff --git a/src/tera/boatdamage.rs b/src/tera/boatdamage.rs index c798791..39088af 100644 --- a/src/tera/boatdamage.rs +++ b/src/tera/boatdamage.rs @@ -13,7 +13,7 @@ use crate::{ model::{ boat::Boat, boatdamage::{BoatDamage, BoatDamageFixed, BoatDamageToAdd, BoatDamageVerified}, - user::{CoxUser, NonGuestUser, TechUser, User, UserWithRoles}, + user::{CoxUser, DonauLinzUser, TechUser, User, UserWithRoles}, }, tera::log::KioskCookie, }; @@ -45,7 +45,7 @@ async fn index_kiosk( async fn index( db: &State, flash: Option>, - user: NonGuestUser, + user: DonauLinzUser, ) -> Template { let boatdamages = BoatDamage::all(db).await; let boats = Boat::all(db).await; @@ -59,7 +59,7 @@ async fn index( context.insert("boats", &boats); context.insert( "loggedin_user", - &UserWithRoles::from_user(user.user, db).await, + &UserWithRoles::from_user(user.into(), db).await, ); Template::render("boatdamages", context.into_json()) @@ -76,13 +76,14 @@ pub struct FormBoatDamageToAdd<'r> { async fn create<'r>( db: &State, data: Form>, - user: NonGuestUser, + user: DonauLinzUser, ) -> Flash { + let user: User = user.into(); let boatdamage_to_add = BoatDamageToAdd { boat_id: data.boat_id, desc: data.desc, lock_boat: data.lock_boat, - user_id_created: user.user.id as i32, + user_id_created: user.id as i32, }; match BoatDamage::create(db, boatdamage_to_add).await { Ok(_) => Flash::success( diff --git a/src/tera/cox.rs b/src/tera/cox.rs index a62e2b7..808f842 100644 --- a/src/tera/cox.rs +++ b/src/tera/cox.rs @@ -391,7 +391,7 @@ mod test { .body("name=cox&password=cox"); // Add the form data to the request body; login.dispatch().await; - let req = client.get("/join/1"); + let req = client.get("/planned/join/1"); let _ = req.dispatch().await; let req = client.get("/cox/join/1"); diff --git a/src/tera/log.rs b/src/tera/log.rs index 566c874..51f531b 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -23,7 +23,7 @@ use crate::model::{ LogbookUpdateError, }, logtype::LogType, - user::{NonGuestUser, User, UserWithRoles, UserWithWaterStatus}, + user::{DonauLinzUser, User, UserWithRoles, UserWithWaterStatus}, }; pub struct KioskCookie(String); @@ -44,9 +44,9 @@ impl<'r> FromRequest<'r> for KioskCookie { async fn index( db: &State, flash: Option>, - user: NonGuestUser, + user: DonauLinzUser, ) -> Template { - let boats = Boat::for_user(db, &user.user).await; + let boats = Boat::for_user(db, &user).await; let coxes: Vec = futures::future::join_all( User::cox(db) @@ -78,7 +78,7 @@ async fn index( context.insert("logtypes", &logtypes); context.insert( "loggedin_user", - &UserWithRoles::from_user(user.user, db).await, + &UserWithRoles::from_user(user.into(), db).await, ); context.insert("on_water", &on_water); context.insert("distances", &distances); @@ -87,12 +87,12 @@ async fn index( } #[get("/show", rank = 2)] -async fn show(db: &State, user: NonGuestUser) -> Template { +async fn show(db: &State, user: DonauLinzUser) -> Template { let logs = Logbook::completed(db).await; Template::render( "log.completed", - context!(logs, loggedin_user: &UserWithRoles::from_user(user.user, db).await), + context!(logs, loggedin_user: &UserWithRoles::from_user(user.into(), db).await), ) } @@ -166,12 +166,12 @@ async fn kiosk( async fn create_logbook( db: &SqlitePool, data: Form, - user: &NonGuestUser, + user: &DonauLinzUser, ) -> Flash { match Logbook::create( db, data.into_inner(), - &user.user + &user ) .await { @@ -197,14 +197,11 @@ async fn create_logbook( async fn create( db: &State, data: Form, - user: NonGuestUser, + user: DonauLinzUser, ) -> Flash { Log::create( db, - format!( - "User {} tries to create log entry={:?}", - user.user.name, data - ), + format!("User {} tries to create log entry={:?}", &user.name, data), ) .await; @@ -238,14 +235,14 @@ async fn create_kiosk( ) .await; - create_logbook(db, data, &NonGuestUser { user: creator }).await //TODO: fixme + create_logbook(db, data, &DonauLinzUser(creator)).await //TODO: fixme } async fn home_logbook( db: &SqlitePool, data: Form, logbook_id: i32, - user: &NonGuestUser, + user: &DonauLinzUser, ) -> Flash { let logbook: Option = Logbook::find_by_id(db, logbook_id).await; let Some(logbook) = logbook else { @@ -255,7 +252,7 @@ async fn home_logbook( ); }; - match logbook.home(db, &user.user, data.into_inner()).await { + match logbook.home(db, &user, data.into_inner()).await { Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"), Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")), Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."), @@ -285,11 +282,11 @@ async fn home_kiosk( db, data, logbook_id, - &NonGuestUser { - user: User::find_by_id(db, logbook.shipmaster as i32) + &DonauLinzUser( + User::find_by_id(db, logbook.shipmaster as i32) .await - .unwrap(), //TODO: fixme - }, + .unwrap(), + ), //TODO: fixme ) .await } @@ -299,13 +296,13 @@ async fn home( db: &State, data: Form, logbook_id: i32, - user: NonGuestUser, + user: DonauLinzUser, ) -> Flash { Log::create( db, format!( "User {} tries to finish log entry {logbook_id} {data:?}", - user.user.name + &user.name ), ) .await; @@ -314,12 +311,12 @@ async fn home( } #[get("//delete", rank = 2)] -async fn delete(db: &State, logbook_id: i32, user: User) -> Flash { +async fn delete(db: &State, logbook_id: i32, user: DonauLinzUser) -> Flash { let logbook = Logbook::find_by_id(db, logbook_id).await; if let Some(logbook) = logbook { Log::create( db, - format!("User {} tries to delete log entry {logbook_id}", user.name), + format!("User {} tries to delete log entry {logbook_id}", &user.name), ) .await; match logbook.delete(db, &user).await { diff --git a/src/tera/mod.rs b/src/tera/mod.rs index 7e3d6c0..17ab646 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -8,17 +8,12 @@ use rocket::{ response::{Flash, Redirect}, routes, Build, FromForm, Rocket, State, }; -use rocket_dyn_templates::{tera::Context, Template}; +use rocket_dyn_templates::Template; use serde::Deserialize; use sqlx::SqlitePool; +use tera::Context; -use crate::model::{ - log::Log, - tripdetails::TripDetails, - triptype::TripType, - user::{User, UserWithRoles}, - usertrip::{UserTrip, UserTripDeleteError, UserTripError}, -}; +use crate::model::user::{User, UserWithRoles}; pub(crate) mod admin; mod auth; @@ -27,6 +22,7 @@ mod cox; mod ergo; mod log; mod misc; +mod planned; mod stat; #[derive(FromForm, Debug)] @@ -35,6 +31,16 @@ struct LoginForm<'r> { password: &'r str, } +#[get("/")] +async fn index(db: &State, user: User, flash: Option>) -> Template { + let mut context = Context::new(); + if let Some(msg) = flash { + context.insert("flash", &msg.into_inner()); + } + context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); + Template::render("index", context.into_json()) +} + #[post("/", data = "")] async fn wikiauth(db: &State, login: Form>) -> String { match User::login(db, login.name, login.password).await { @@ -43,164 +49,16 @@ async fn wikiauth(db: &State, login: Form>) -> String } } -#[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 +#[catch(401)] //Unauthorized fn unauthorized_error() -> Redirect { Redirect::to("/auth") } +#[catch(403)] //forbidden +fn forbidden_error() -> Flash { + Flash::error(Redirect::to("/"), "Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at.") +} + #[derive(Deserialize)] #[serde(crate = "rocket::serde")] pub struct Config { @@ -210,10 +68,11 @@ pub struct Config { pub fn config(rocket: Rocket) -> Rocket { rocket - .mount("/", routes![index, join, remove, remove_guest]) + .mount("/", routes![index]) .mount("/auth", auth::routes()) .mount("/wikiauth", routes![wikiauth]) .mount("/log", log::routes()) + .mount("/planned", planned::routes()) .mount("/ergo", ergo::routes()) .mount("/stat", stat::routes()) .mount("/boatdamage", boatdamage::routes()) @@ -221,7 +80,7 @@ pub fn config(rocket: Rocket) -> Rocket { .mount("/admin", admin::routes()) .mount("/", misc::routes()) .mount("/public", FileServer::from("static/")) - .register("/", catchers![unauthorized_error]) + .register("/", catchers![unauthorized_error, forbidden_error]) .attach(Template::fairing()) .attach(AdHoc::config::()) } @@ -255,7 +114,11 @@ mod test { assert_eq!(response.status(), Status::Ok); - assert!(response.into_string().await.unwrap().contains("Ausfahrten")); + assert!(response + .into_string() + .await + .unwrap() + .contains("Ruderassistent")); } #[sqlx::test] @@ -274,75 +137,6 @@ mod test { 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!(); diff --git a/src/tera/planned.rs b/src/tera/planned.rs new file mode 100644 index 0000000..f2c32fb --- /dev/null +++ b/src/tera/planned.rs @@ -0,0 +1,273 @@ +use rocket::{ + get, + request::FlashMessage, + response::{Flash, Redirect}, + routes, Route, State, +}; +use rocket_dyn_templates::Template; +use sqlx::SqlitePool; +use tera::Context; + +use crate::model::{ + log::Log, + tripdetails::TripDetails, + triptype::TripType, + user::{AllowedForPlannedTripsUser, User, UserWithRoles}, + usertrip::{UserTrip, UserTripDeleteError, UserTripError}, +}; + +#[get("/")] +async fn index( + db: &State, + user: AllowedForPlannedTripsUser, + flash: Option>, +) -> Template { + let user: User = user.into(); + + 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.into(), db).await, + ); + context.insert("days", &days); + Template::render("planned", context.into_json()) +} + +#[get("/join/?")] +async fn join( + db: &State, + trip_details_id: i64, + user: AllowedForPlannedTripsUser, + user_note: Option, +) -> Flash { + let user: User = user.into(); + + 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: AllowedForPlannedTripsUser, + name: String, +) -> Flash { + let user: User = user.into(); + + 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: AllowedForPlannedTripsUser, +) -> Flash { + let user: User = user.into(); + + 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"); + } + } +} + +pub fn routes() -> Vec { + routes![index, join, remove, remove_guest] +} + +#[cfg(test)] +mod test { + use rocket::{ + http::{ContentType, Status}, + local::asynchronous::Client, + }; + use sqlx::SqlitePool; + + use crate::testdb; + + #[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("/planned/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("/planned/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("/planned/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."); + } +} diff --git a/src/tera/stat.rs b/src/tera/stat.rs index 0dfee21..5db94fb 100644 --- a/src/tera/stat.rs +++ b/src/tera/stat.rs @@ -4,19 +4,19 @@ use sqlx::SqlitePool; use crate::model::{ stat::{self, Stat}, - user::{NonGuestUser, UserWithRoles}, + user::{DonauLinzUser, UserWithRoles}, }; use super::log::KioskCookie; #[get("/boats?", rank = 2)] -async fn index_boat(db: &State, user: NonGuestUser, year: Option) -> Template { +async fn index_boat(db: &State, user: DonauLinzUser, year: Option) -> Template { let stat = Stat::boats(db, year).await; let kiosk = false; Template::render( "stat.boats", - context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, kiosk), + context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, kiosk), ) } @@ -33,15 +33,15 @@ async fn index_boat_kiosk( } #[get("/?", rank = 2)] -async fn index(db: &State, user: NonGuestUser, year: Option) -> Template { +async fn index(db: &State, user: DonauLinzUser, year: Option) -> Template { let stat = Stat::people(db, year).await; let guest_km = Stat::guest(db, year).await; - let personal = stat::get_personal(db, &user.user).await; + let personal = stat::get_personal(db, &user).await; let kiosk = false; Template::render( "stat.people", - context!(loggedin_user: &UserWithRoles::from_user(user.user, db).await, stat, personal, kiosk, guest_km), + context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, personal, kiosk, guest_km), ) } diff --git a/templates/includes/macros.html.tera b/templates/includes/macros.html.tera index 3d4fbf3..c2691b4 100644 --- a/templates/includes/macros.html.tera +++ b/templates/includes/macros.html.tera @@ -10,7 +10,7 @@ -
+
+
{% if flash %} {{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} {% endif %} - -

Ausfahrten

- - {% include "includes/buttons" %} - - {% for day in days %} - {% set amount_trips = day.planned_events | length + day.trips | length %} - {% set_global day_cox_needed = false %} - {% if day.planned_events | length > 0 %} - {% for planned_event in day.planned_events %} - {% if planned_event.cox_needed %} - {% set_global day_cox_needed = true %} - {% endif %} - {% endfor %} - {% endif %} - -
-
-

{{ day.day| date(format="%d.%m.%Y") }} - {{ day.day | date(format="%A", locale="de_AT") }} -

- - {% if day.planned_events | length > 0 or day.trips | length > 0 %} -
- - {# --- START Events --- #} - {% if day.planned_events | length > 0 %} - {% for planned_event in day.planned_events | sort(attribute="planned_starting_time") %} - {% set amount_cur_cox = planned_event.cox | length %} - {% set amount_cox_missing = planned_event.planned_amount_cox - amount_cur_cox %} -
-
- -
- {# --- START Row Buttons --- #} - {% set_global cur_user_participates = false %} - {% for rower in planned_event.rower%} - {% if rower.name == loggedin_user.name %} - {% set_global cur_user_participates = true %} - {% endif %} - {% endfor %} - {% if cur_user_participates %} - Abmelden - {% endif %} - {% if planned_event.max_people > planned_event.rower | length %} - {% if cur_user_participates == false %} - Mitrudern - {% endif %} - {% endif %} - {# --- END Row Buttons --- #} - - {# --- START Cox Buttons --- #} - {% if "cox" in loggedin_user.roles %} - {% set_global cur_user_participates = false %} - {% for cox in planned_event.cox %} - {% if cox.name == loggedin_user.name %} - {% set_global cur_user_participates = true %} - {% endif %} - {% endfor %} - {% if cur_user_participates %} - - {% include "includes/cox-icon" %} - Abmelden - - {% else %} - - {% include "includes/cox-icon" %} - Steuern - - {% endif %} - {% endif %} - {# --- END Cox Buttons --- #} -
-
- - {# --- START Sidebar Content --- #} - - {# --- END Sidebar Content --- #} -
- {% endfor %} - {% endif %} - {# --- END Events --- #} + {% if "scheckbuch" in loggedin_user.roles %} +
+ +
+ {% endif %} - {# --- START Trips --- #} - {% if day.trips | length > 0 %} - {% for trip in day.trips | sort(attribute="planned_starting_time") %} -
-
-
- {% if trip.max_people == 0 %} - ⚠ - {{ trip.planned_starting_time }} - Uhr - (Absage - {{ trip.cox_name }} - {% if trip.trip_type %} - - - {{ trip.trip_type.icon | safe }}{{ trip.trip_type.name }} - {% endif %}) - {% else %} - {{ trip.planned_starting_time }} - Uhr - ({{ trip.cox_name }}{% if trip.trip_type %} - {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}{% endif %}) - {% endif %} -
- - Details - -
+ {% if "admin" in loggedin_user.roles %} +
+ +
+ {% endif %} -
- {% set_global cur_user_participates = false %} - {% for rower in trip.rower %} - {% if rower.name == loggedin_user.name %} - {% set_global cur_user_participates = true %} - {% endif %} - {% endfor %} - {% if cur_user_participates %} - Abmelden - {% endif %} - {% if trip.max_people > trip.rower | length and trip.cox_id != loggedin_user.id and cur_user_participates == false%} - Mitrudern - {% endif %} -
-
- {# --- START Sidebar Content --- #} - -
- {# --- END Sidebar Content --- #} -
- {% endfor %} - {% endif %} - {# --- END Trips --- #} -
- {% endif %} -
- - {# --- START Add Buttons --- #} - {% if "admin" in loggedin_user.roles or "cox" in loggedin_user.roles %} -
- {% if "admin" in loggedin_user.roles %} - - - {% include "includes/plus-icon" %} - - Event - - {% endif %} - - {% if "cox" in loggedin_user.roles %} - - - {% include "includes/plus-icon" %} - - Ausfahrt - - {% endif %} -
- {% endif %} - {# --- END Add Buttons --- #} -
- {% endfor %} -
-
- -{% if "cox" in loggedin_user.roles %} - {% include "forms/trip" %} -{% endif %} - -{% if "admin" in loggedin_user.roles %} - {% include "forms/event" %} -{% endif %}{% endblock content %} +{% endblock content%} diff --git a/templates/planned.html.tera b/templates/planned.html.tera new file mode 100644 index 0000000..be6f862 --- /dev/null +++ b/templates/planned.html.tera @@ -0,0 +1,294 @@ +{% import "includes/macros" as macros %} + +{% extends "base" %} + +{% block content %} +
+ {% if flash %} + {{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} + {% endif %} + +

Ausfahrten

+ + {% include "includes/buttons" %} + + {% for day in days %} + {% set amount_trips = day.planned_events | length + day.trips | length %} + {% set_global day_cox_needed = false %} + {% if day.planned_events | length > 0 %} + {% for planned_event in day.planned_events %} + {% if planned_event.cox_needed %} + {% set_global day_cox_needed = true %} + {% endif %} + {% endfor %} + {% endif %} + +
+
+

{{ day.day| date(format="%d.%m.%Y") }} + {{ day.day | date(format="%A", locale="de_AT") }} +

+ + {% if day.planned_events | length > 0 or day.trips | length > 0 %} +
+ + {# --- START Events --- #} + {% if day.planned_events | length > 0 %} + {% for planned_event in day.planned_events | sort(attribute="planned_starting_time") %} + {% set amount_cur_cox = planned_event.cox | length %} + {% set amount_cox_missing = planned_event.planned_amount_cox - amount_cur_cox %} +
+
+
+ + {{ planned_event.planned_starting_time }} + Uhr + + ({{ planned_event.name }}{% if planned_event.trip_type %} - {{ planned_event.trip_type.icon | safe }} {{ planned_event.trip_type.name }}{% endif %})
+ + + Details + +
+
+ {# --- START Row Buttons --- #} + {% set_global cur_user_participates = false %} + {% for rower in planned_event.rower%} + {% if rower.name == loggedin_user.name %} + {% set_global cur_user_participates = true %} + {% endif %} + {% endfor %} + {% if cur_user_participates %} + Abmelden + {% endif %} + {% if planned_event.max_people > planned_event.rower | length %} + {% if cur_user_participates == false %} + Mitrudern + {% endif %} + {% endif %} + {# --- END Row Buttons --- #} + + {# --- START Cox Buttons --- #} + {% if "cox" in loggedin_user.roles %} + {% set_global cur_user_participates = false %} + {% for cox in planned_event.cox %} + {% if cox.name == loggedin_user.name %} + {% set_global cur_user_participates = true %} + {% endif %} + {% endfor %} + {% if cur_user_participates %} + + {% include "includes/cox-icon" %} + Abmelden + + {% else %} + + {% include "includes/cox-icon" %} + Steuern + + {% endif %} + {% endif %} + {# --- END Cox Buttons --- #} +
+
+ + {# --- START Sidebar Content --- #} + + {# --- END Sidebar Content --- #} +
+ {% endfor %} + {% endif %} + {# --- END Events --- #} + + {# --- START Trips --- #} + {% if day.trips | length > 0 %} + {% for trip in day.trips | sort(attribute="planned_starting_time") %} +
+
+
+ {% if trip.max_people == 0 %} + ⚠ + {{ trip.planned_starting_time }} + Uhr + (Absage + {{ trip.cox_name }} + {% if trip.trip_type %} + - + {{ trip.trip_type.icon | safe }}{{ trip.trip_type.name }} + {% endif %}) + {% else %} + {{ trip.planned_starting_time }} + Uhr + ({{ trip.cox_name }}{% if trip.trip_type %} - {{ trip.trip_type.icon | safe }} {{ trip.trip_type.name }}{% endif %}) + {% endif %} +
+ + Details + +
+ +
+ {% set_global cur_user_participates = false %} + {% for rower in trip.rower %} + {% if rower.name == loggedin_user.name %} + {% set_global cur_user_participates = true %} + {% endif %} + {% endfor %} + {% if cur_user_participates %} + Abmelden + {% endif %} + {% if trip.max_people > trip.rower | length and trip.cox_id != loggedin_user.id and cur_user_participates == false%} + Mitrudern + {% endif %} +
+
+ {# --- START Sidebar Content --- #} + + {# --- END Sidebar Content --- #} +
+ {% endfor %} + {% endif %} + {# --- END Trips --- #} +
+ {% endif %} +
+ + {# --- START Add Buttons --- #} + {% if "admin" in loggedin_user.roles or "cox" in loggedin_user.roles %} +
+ {% if "admin" in loggedin_user.roles %} + + + {% include "includes/plus-icon" %} + + Event + + {% endif %} + + {% if "cox" in loggedin_user.roles %} + + + {% include "includes/plus-icon" %} + + Ausfahrt + + {% endif %} +
+ {% endif %} + {# --- END Add Buttons --- #} +
+ {% endfor %} +
+ + +{% if "cox" in loggedin_user.roles %} + {% include "forms/trip" %} +{% endif %} + +{% if "admin" in loggedin_user.roles %} + {% include "forms/event" %} +{% endif %}{% endblock content %} From a4b8bf1e3f3c4f743975be417d04bf022ea934a6 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 14:24:58 +0100 Subject: [PATCH 02/72] improve code w/ clippy --- src/tera/admin/mail.rs | 4 ++-- src/tera/log.rs | 2 +- src/tera/planned.rs | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/tera/admin/mail.rs b/src/tera/admin/mail.rs index e45c260..aa3af59 100644 --- a/src/tera/admin/mail.rs +++ b/src/tera/admin/mail.rs @@ -50,9 +50,9 @@ async fn update( ) -> Flash { let d = data.into_inner(); if Mail::send(db, d, config.smtp_pw.clone()).await { - return Flash::success(Redirect::to("/admin/mail"), "Mail versendet"); + Flash::success(Redirect::to("/admin/mail"), "Mail versendet") } else { - return Flash::error(Redirect::to("/admin/mail"), "Fehler"); + Flash::error(Redirect::to("/admin/mail"), "Fehler") } } diff --git a/src/tera/log.rs b/src/tera/log.rs index 51f531b..0d480e0 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -252,7 +252,7 @@ async fn home_logbook( ); }; - match logbook.home(db, &user, data.into_inner()).await { + match logbook.home(db,user, data.into_inner()).await { Ok(_) => Flash::success(Redirect::to("/log"), "Ausfahrt korrekt eingetragen"), Err(LogbookUpdateError::TooManyRowers(expected, actual)) => Flash::error(Redirect::to("/log"), format!("Zu viele Ruderer (Boot fasst maximal {expected}, es wurden jedoch {actual} Ruderer ausgewählt)")), Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday) => Flash::error(Redirect::to("/log"), "Nur Ausfahrten, die heute enden dürfen eingetragen werden. Für einen Nachtrag schreibe alle Daten Philipp (Tel. nr. siehe Signal oder it@rudernlinz.at)."), diff --git a/src/tera/planned.rs b/src/tera/planned.rs index f2c32fb..0f9e00c 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -36,10 +36,7 @@ async fn index( if let Some(msg) = flash { context.insert("flash", &msg.into_inner()); } - context.insert( - "loggedin_user", - &UserWithRoles::from_user(user.into(), db).await, - ); + context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); context.insert("days", &days); Template::render("planned", context.into_json()) } From 87e4b8fbb4dc721a6159257b17a1658a9dbcf660 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 15:10:05 +0100 Subject: [PATCH 03/72] fix redirect --- src/tera/planned.rs | 32 ++++++++++++++++---------------- templates/planned.html.tera | 8 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/tera/planned.rs b/src/tera/planned.rs index 0f9e00c..6c9f44f 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -64,31 +64,31 @@ async fn join( ), ) .await; - Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!") + Flash::success(Redirect::to("/planned"), "Erfolgreich angemeldet!") } Err(UserTripError::EventAlreadyFull) => { - Flash::error(Redirect::to("/"), "Event bereits ausgebucht!") + Flash::error(Redirect::to("/planned"), "Event bereits ausgebucht!") } Err(UserTripError::AlreadyRegistered) => { - Flash::error(Redirect::to("/"), "Du nimmst bereits teil!") + Flash::error(Redirect::to("/planned"), "Du nimmst bereits teil!") } Err(UserTripError::AlreadyRegisteredAsCox) => { - Flash::error(Redirect::to("/"), "Du hilfst bereits als Steuerperson aus!") + Flash::error(Redirect::to("/planned"), "Du hilfst bereits als Steuerperson aus!") } Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error( - Redirect::to("/"), + Redirect::to("/planned"), "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)", ), Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error( - Redirect::to("/"), + Redirect::to("/planned"), "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", ), Err(UserTripError::NotAllowedToAddGuest) => Flash::error( - Redirect::to("/"), + Redirect::to("/planned"), "Du darfst keine Gäste hinzufügen.", ), Err(UserTripError::DetailsLocked) => Flash::error( - Redirect::to("/"), + Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.", ), } @@ -104,7 +104,7 @@ async fn remove_guest( let user: User = user.into(); let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { - return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); + return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, Some(name)).await { @@ -118,7 +118,7 @@ async fn remove_guest( ) .await; - Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( @@ -130,13 +130,13 @@ async fn remove_guest( ) .await; - Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") + Flash::error(Redirect::to("/planned"), "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.") + Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.") } Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error( - Redirect::to("/"), + Redirect::to("/planned"), "Keine Berechtigung um den Gast zu entfernen.", ), } @@ -151,7 +151,7 @@ async fn remove( let user: User = user.into(); let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { - return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); + return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, None).await { @@ -165,7 +165,7 @@ async fn remove( ) .await; - Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( @@ -177,7 +177,7 @@ async fn remove( ) .await; - Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") + Flash::error(Redirect::to("/planned"), "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"); diff --git a/templates/planned.html.tera b/templates/planned.html.tera index be6f862..4b65829 100644 --- a/templates/planned.html.tera +++ b/templates/planned.html.tera @@ -116,7 +116,7 @@ {# --- END List Rowers --- #} {% if "admin" in loggedin_user.roles %} -
+ {{ macros::input(label='Gast', class="input rounded-t", name='user_note', type='text', required=true) }}
@@ -198,10 +198,10 @@ {% endif %} {% endfor %} {% if cur_user_participates %} - Abmelden + Abmelden {% endif %} {% if trip.max_people > trip.rower | length and trip.cox_id != loggedin_user.id and cur_user_participates == false%} - Mitrudern + Mitrudern {% endif %} @@ -215,7 +215,7 @@ {% set amount_cur_rower = trip.rower | length %} {{ macros::box(participants=trip.rower, empty_seats=trip.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=trip.trip_details_id, allow_removing=loggedin_user.id == trip.cox_id) }} {% if trip.cox_id == loggedin_user.id %} -
+ {{ macros::input(label='Gast', class="input rounded-t", name='user_note', type='text', required=true) }}
From a95ff3657f293f393753911c2e15f0e0e72ee6c6 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 15:11:35 +0100 Subject: [PATCH 04/72] fix ci --- src/tera/planned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tera/planned.rs b/src/tera/planned.rs index 6c9f44f..0b9f117 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -217,7 +217,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/")); + assert_eq!(response.headers().get("Location").next(), Some("/planned")); let flash_cookie = response .cookies() From 5788d3d9f4bd7ccc87bc3cb89387d9e881de030f Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 15:49:57 +0100 Subject: [PATCH 05/72] fix ci --- src/tera/planned.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tera/planned.rs b/src/tera/planned.rs index 0b9f117..f4e1cc2 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -230,7 +230,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/")); + assert_eq!(response.headers().get("Location").next(), Some("/planned")); let flash_cookie = response .cookies() From bbfc94d54bff36dffe1f6c1ea8c88a7a7e9f2458 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 Jan 2024 15:52:27 +0100 Subject: [PATCH 06/72] add mail --- templates/index.html.tera | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/index.html.tera b/templates/index.html.tera index 30aa624..af61c36 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -75,6 +75,7 @@
  • Boote
  • User
  • Mail
  • +
  • Logs (beautifully layouted)
  • From ce3562f07911fc5a017dd12fff448cc41decd299 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:00:02 +0100 Subject: [PATCH 07/72] add playwright tests --- .gitea/workflows/action.yml | 27 ++++++++++-- frontend/.gitignore | 5 +++ frontend/package.json | 2 + frontend/playwright.config.ts | 75 ++++++++++++++++++++++++++++++++++ frontend/tests/example.spec.ts | 22 ++++++++++ 5 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 frontend/playwright.config.ts create mode 100644 frontend/tests/example.spec.ts diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 4b7a8f7..7af2ef5 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -9,7 +9,28 @@ env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} jobs: - test: + test-frontend: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + + test-backend: runs-on: ubuntu-latest container: rust:latest @@ -35,7 +56,7 @@ jobs: deploy-staging: runs-on: ubuntu-latest container: rust:latest - needs: [test] + needs: [test_frontend, test_backend] if: github.ref == 'refs/heads/staging' steps: - name: Setup Environment @@ -95,7 +116,7 @@ jobs: deploy-main: runs-on: ubuntu-latest container: rust:latest - needs: [test] + needs: [test_frontend, test_backend] if: github.ref == 'refs/heads/main' steps: - name: Setup Environment diff --git a/frontend/.gitignore b/frontend/.gitignore index d8b83df..bdf87f1 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1 +1,6 @@ package-lock.json +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/frontend/package.json b/frontend/package.json index 8b82d1e..0b42352 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,9 @@ "preview": "vite preview" }, "devDependencies": { + "@playwright/test": "^1.40.1", "@types/d3": "^7.4.1", + "@types/node": "^20.11.4", "autoprefixer": "^10.4.14", "postcss": "^8.4.21", "sass": "^1.60.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..a12a9b0 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,75 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + //{ + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + //}, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + //{ + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + //}, + + /* Test against branded browsers. */ + //{ + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + //}, + //{ + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + //}, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'cd .. && ./test_db.sh && cargo r', + }, +}); diff --git a/frontend/tests/example.spec.ts b/frontend/tests/example.spec.ts new file mode 100644 index 0000000..18740ca --- /dev/null +++ b/frontend/tests/example.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test'; + +test('cox can create trip', async ({ page }) => { + await page.goto('http://localhost:8000/auth'); + await page.getByPlaceholder('Name').click(); + await page.getByPlaceholder('Name').fill('cox'); + await page.getByPlaceholder('Name').press('Tab'); + await page.getByPlaceholder('Passwort').fill('cox'); + await page.getByRole('button', { name: 'Einloggen' }).click(); + await expect(page.locator('body')).toContainText('Login erfolgreich'); + await page.locator('li').filter({ hasText: 'Geplante Ausfahrten' }).click(); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await page.locator('.relative').first().click(); + await page.locator('#sidebar #planned_starting_time').click(); + await page.locator('#sidebar #planned_starting_time').press('ArrowLeft'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.locator('#sidebar #planned_starting_time').fill('14:00'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.getByRole('spinbutton').fill('4'); + await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); + await expect(page.locator('body')).toContainText('Ausfahrt erfolgreich erstellt.'); +}); From 537458b58e5db70461df9958ff26728df77b526f Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:07:40 +0100 Subject: [PATCH 08/72] try --- .gitea/workflows/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 7af2ef5..3a6530c 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -14,9 +14,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 + - name: Setup Environment + run: | + apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - name: Install dependencies run: npm ci - name: Install Playwright Browsers From bd650f738c8cde303ebc85922528fb9c26c958b6 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:09:09 +0100 Subject: [PATCH 09/72] try --- .gitea/workflows/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 3a6530c..2595cdc 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -14,9 +14,9 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup Environment - run: | - apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y + - name: Setup Environment + run: | + apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - name: Install dependencies run: npm ci - name: Install Playwright Browsers From 4ab1a36fcd780dffddd69655f2037fe8b30294a5 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:09:45 +0100 Subject: [PATCH 10/72] try --- .gitea/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 2595cdc..b822bbd 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - name: Setup Environment run: | apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y + - uses: actions/checkout@v3 - name: Install dependencies run: npm ci - name: Install Playwright Browsers From c1493ad9143679d5e27dd3580ea6e008d9a945f2 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:10:13 +0100 Subject: [PATCH 11/72] try --- .gitea/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index b822bbd..6b5efc2 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -11,7 +11,7 @@ env: jobs: test-frontend: runs-on: ubuntu-latest - + container: rust:latest steps: - name: Setup Environment run: | From ae673657b0436c9f3349e128c0400f77b85f3fb8 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:13:41 +0100 Subject: [PATCH 12/72] try --- .gitea/workflows/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 6b5efc2..2966061 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -18,16 +18,16 @@ jobs: apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - uses: actions/checkout@v3 - name: Install dependencies - run: npm ci + run: cd frontend && npm ci - name: Install Playwright Browsers - run: npx playwright install --with-deps + run: cd frontend && npx playwright install --with-deps - name: Run Playwright tests - run: npx playwright test + run: cd frontend && npx playwright test - uses: actions/upload-artifact@v3 if: always() with: name: playwright-report - path: playwright-report/ + path: frontend/playwright-report/ retention-days: 30 test-backend: From 98d8b512eea58d8bc31eb33cd15e5627bd4a2cf6 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:16:56 +0100 Subject: [PATCH 13/72] try --- .gitea/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 2966061..633e9b8 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -18,7 +18,7 @@ jobs: apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - uses: actions/checkout@v3 - name: Install dependencies - run: cd frontend && npm ci + run: cd frontend && npm install - name: Install Playwright Browsers run: cd frontend && npx playwright install --with-deps - name: Run Playwright tests From 875799c3d71789264f4d7700846e3026a5c0b447 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:42:57 +0100 Subject: [PATCH 14/72] try --- .gitea/workflows/action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 633e9b8..f637ba7 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -17,6 +17,10 @@ jobs: run: | apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - uses: actions/checkout@v3 + - name: Build + run: | + cargo build + cd frontend && npm install && npm run build - name: Install dependencies run: cd frontend && npm install - name: Install Playwright Browsers From b633a4bfee3b29c056f928d75348efaa9689b9b9 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 16:55:47 +0100 Subject: [PATCH 15/72] try --- .gitea/workflows/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index f637ba7..62422d6 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -17,6 +17,8 @@ jobs: run: | apt-get update -qq && apt-get install -y -qq sshpass musl musl-tools sqlite3 curl gnupg && mkdir -p /etc/apt/keyrings | curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_16.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -y && apt-get install npm -y - uses: actions/checkout@v3 + - name: Run Test DB Script + run: ./test_db.sh - name: Build run: | cargo build From 442453bef388be5c88c4dd53e2633f40c33858ee Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 19:17:17 +0100 Subject: [PATCH 16/72] try --- .gitea/workflows/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 62422d6..7b5f418 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -54,6 +54,7 @@ jobs: - name: Build run: | cargo build + mkdir svelte && cd svelte && mkdir build && cd .. cd frontend && npm install && npm run build - name: Run Tests From 64bb15cc8b6eb21c085bcfaadd070b98bc3b0610 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 19:40:20 +0100 Subject: [PATCH 17/72] try --- .gitea/workflows/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 7b5f418..9ab476b 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -20,7 +20,8 @@ jobs: - name: Run Test DB Script run: ./test_db.sh - name: Build - run: | + run: + mkdir svelte && cd svelte && mkdir build && cd .. cargo build cd frontend && npm install && npm run build - name: Install dependencies @@ -54,7 +55,6 @@ jobs: - name: Build run: | cargo build - mkdir svelte && cd svelte && mkdir build && cd .. cd frontend && npm install && npm run build - name: Run Tests From 7aef741c6f44687c750a37a902d2a1cbe1ac1053 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 19:48:41 +0100 Subject: [PATCH 18/72] try --- .gitea/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 9ab476b..1972712 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -21,7 +21,7 @@ jobs: run: ./test_db.sh - name: Build run: - mkdir svelte && cd svelte && mkdir build && cd .. + cd svelte && mkdir build && cd .. cargo build cd frontend && npm install && npm run build - name: Install dependencies From bc1916ceab20decfd8bf84d40b7b19e1ec3d2952 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 19:54:24 +0100 Subject: [PATCH 19/72] try --- .gitea/workflows/action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 1972712..bfddc09 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -22,7 +22,9 @@ jobs: - name: Build run: cd svelte && mkdir build && cd .. + pwd cargo build + pwd cd frontend && npm install && npm run build - name: Install dependencies run: cd frontend && npm install From 2a025d7519788de30729dba8b37024abdd995431 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 20:00:41 +0100 Subject: [PATCH 20/72] try --- .gitea/workflows/action.yml | 3 --- svelte/.gitignore | 1 - 2 files changed, 4 deletions(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index bfddc09..6fa4dab 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -21,10 +21,7 @@ jobs: run: ./test_db.sh - name: Build run: - cd svelte && mkdir build && cd .. - pwd cargo build - pwd cd frontend && npm install && npm run build - name: Install dependencies run: cd frontend && npm install diff --git a/svelte/.gitignore b/svelte/.gitignore index 8f6c617..9280939 100644 --- a/svelte/.gitignore +++ b/svelte/.gitignore @@ -1,6 +1,5 @@ .DS_Store node_modules -/build /.svelte-kit /package .env From 6a1bde55a27f0284bf8edc7593c48f9dc462aa5c Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 20:04:34 +0100 Subject: [PATCH 21/72] try --- .gitea/workflows/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 6fa4dab..62422d6 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -20,7 +20,7 @@ jobs: - name: Run Test DB Script run: ./test_db.sh - name: Build - run: + run: | cargo build cd frontend && npm install && npm run build - name: Install dependencies From 7e1a0a2159eaa99935f08880cc1d10cf05b02680 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 20:24:02 +0100 Subject: [PATCH 22/72] try --- src/rest/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/mod.rs b/src/rest/mod.rs index b90bac0..ef69ad9 100644 --- a/src/rest/mod.rs +++ b/src/rest/mod.rs @@ -27,7 +27,7 @@ async fn login(login: Form>, db: &State) -> String { pub fn config(rocket: Rocket) -> Rocket { rocket - .mount("/", FileServer::from("svelte/build").rank(0)) + //.mount("/", FileServer::from("svelte/build").rank(0)) .mount("/api/login", routes![login]) } From c372051561b9a13440ff50a0200d441e18d8a963 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 16 Jan 2024 22:29:57 +0100 Subject: [PATCH 23/72] try --- .gitea/workflows/action.yml | 2 +- frontend/tests/cox.spec.ts | 114 ++++++++++++++++++++++++++++ frontend/tests/example.spec.ts | 22 ------ templates/includes/macros.html.tera | 2 +- 4 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 frontend/tests/cox.spec.ts delete mode 100644 frontend/tests/example.spec.ts diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 62422d6..cdf19ad 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -28,7 +28,7 @@ jobs: - name: Install Playwright Browsers run: cd frontend && npx playwright install --with-deps - name: Run Playwright tests - run: cd frontend && npx playwright test + run: cd frontend && npx playwright test --workers 1 - uses: actions/upload-artifact@v3 if: always() with: diff --git a/frontend/tests/cox.spec.ts b/frontend/tests/cox.spec.ts new file mode 100644 index 0000000..ae65a99 --- /dev/null +++ b/frontend/tests/cox.spec.ts @@ -0,0 +1,114 @@ +import { test, expect } from '@playwright/test'; + +test('cox can create and delete trip', async ({ page }) => { + await page.goto('http://localhost:8000/auth'); + await page.getByPlaceholder('Name').click(); + await page.getByPlaceholder('Name').fill('cox'); + await page.getByPlaceholder('Name').press('Tab'); + await page.getByPlaceholder('Passwort').fill('cox'); + await page.getByPlaceholder('Passwort').press('Enter'); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await page.locator('.relative').first().click(); + await page.locator('#sidebar #planned_starting_time').click(); + await page.locator('#sidebar #planned_starting_time').fill('18:00'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.getByRole('spinbutton').fill('5'); + await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await expect(page.locator('body')).toContainText('18:00 Uhr (cox) Details'); + + await page.goto('http://localhost:8000/planned'); + await page.getByRole('link', { name: 'Details' }).click(); + await page.getByRole('link', { name: 'Termin löschen' }).click(); + await expect(page.locator('body')).toContainText('Erfolgreich gelöscht!'); +}); + +test.describe('cox can edit trips', () => { + let sharedPage; + + test.beforeEach(async ({ browser }) => { + const page = await browser.newPage(); + + await page.goto('http://localhost:8000/auth'); + await page.getByPlaceholder('Name').click(); + await page.getByPlaceholder('Name').fill('cox'); + await page.getByPlaceholder('Name').press('Tab'); + await page.getByPlaceholder('Passwort').fill('cox'); + await page.getByPlaceholder('Passwort').press('Enter'); + await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await page.locator('.relative').first().click(); + await page.locator('#sidebar #planned_starting_time').click(); + await page.locator('#sidebar #planned_starting_time').fill('18:00'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.locator('#sidebar #planned_starting_time').press('Tab'); + await page.getByRole('spinbutton').fill('5'); + await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); + + sharedPage = page; + }); + + test('edit remarks', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.locator('#sidebar #notes').click(); + await sharedPage.locator('#sidebar #notes').fill('Meine Anmerkung'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Meine Anmerkung'); + await sharedPage.locator('.sidebar-overlay').click(); + }); + + test('add and remove guest', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.locator('#sidebar #user_note').click(); + await sharedPage.locator('#sidebar #user_note').fill('Mein Gast'); + await sharedPage.getByRole('button', { name: 'Gast hinzufügen' }).click(); + await expect(sharedPage.locator('body')).toContainText('Erfolgreich angemeldet!'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 4'); + await expect(sharedPage.locator('#sidebar')).toContainText('Mein Gast (Gast) Abmelden'); + await expect(sharedPage.getByRole('link', { name: 'Termin löschen' })).not.toBeVisible(); + + await sharedPage.getByRole('link', { name: 'Abmelden' }).click(); + await expect(sharedPage.locator('body')).toContainText('Erfolgreich abgemeldet!'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await expect(sharedPage.locator('#sidebar')).toContainText('Keine Ruderer angemeldet'); + await expect(sharedPage.getByRole('link', { name: 'Termin löschen' })).toBeVisible(); + + await sharedPage.locator('.sidebar-overlay').click(); + }); + + test('change amount rower', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await sharedPage.getByRole('spinbutton').click(); + await sharedPage.getByRole('spinbutton').fill('3'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await expect(sharedPage.locator('body')).toContainText('Ausfahrt erfolgreich aktualisiert.'); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + }); + + test('call off trip', async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await expect(sharedPage.locator('#sidebar')).toContainText('Freie Plätze: 5'); + await sharedPage.getByRole('spinbutton').click(); + await sharedPage.getByRole('spinbutton').fill('0'); + await sharedPage.getByRole('button', { name: 'Speichern' }).click(); + await expect(sharedPage.locator('body')).toContainText('Ausfahrt erfolgreich aktualisiert.'); + await sharedPage.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); + await expect(sharedPage.locator('body')).toContainText('(Absage cox )'); + }); + + test.afterEach(async () => { + await sharedPage.goto('http://localhost:8000/planned'); + await sharedPage.getByRole('link', { name: 'Details' }).click(); + await sharedPage.getByRole('link', { name: 'Termin löschen' }).click(); + await sharedPage.close(); + }); +}); diff --git a/frontend/tests/example.spec.ts b/frontend/tests/example.spec.ts deleted file mode 100644 index 18740ca..0000000 --- a/frontend/tests/example.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('cox can create trip', async ({ page }) => { - await page.goto('http://localhost:8000/auth'); - await page.getByPlaceholder('Name').click(); - await page.getByPlaceholder('Name').fill('cox'); - await page.getByPlaceholder('Name').press('Tab'); - await page.getByPlaceholder('Passwort').fill('cox'); - await page.getByRole('button', { name: 'Einloggen' }).click(); - await expect(page.locator('body')).toContainText('Login erfolgreich'); - await page.locator('li').filter({ hasText: 'Geplante Ausfahrten' }).click(); - await page.getByRole('link', { name: 'Geplante Ausfahrten' }).click(); - await page.locator('.relative').first().click(); - await page.locator('#sidebar #planned_starting_time').click(); - await page.locator('#sidebar #planned_starting_time').press('ArrowLeft'); - await page.locator('#sidebar #planned_starting_time').press('Tab'); - await page.locator('#sidebar #planned_starting_time').fill('14:00'); - await page.locator('#sidebar #planned_starting_time').press('Tab'); - await page.getByRole('spinbutton').fill('4'); - await page.getByRole('button', { name: 'Erstellen', exact: true }).click(); - await expect(page.locator('body')).toContainText('Ausfahrt erfolgreich erstellt.'); -}); diff --git a/templates/includes/macros.html.tera b/templates/includes/macros.html.tera index c2691b4..c1adb2d 100644 --- a/templates/includes/macros.html.tera +++ b/templates/includes/macros.html.tera @@ -209,7 +209,7 @@ {% if rower.is_real_guest %} (Gast) {% if allow_removing %} - Abmelden + Abmelden {% endif %} {% endif %}