From bd4ee5954cf91669bc78e680c144056423d02a2c Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 9 Aug 2023 11:54:18 +0200 Subject: [PATCH] implement is_locked for trip_details --- migration.sql | 1 + src/model/planned_event.rs | 17 ++++++-- src/model/trip.rs | 70 ++++++++++++++++++++++++++------- src/model/tripdetails.rs | 3 +- src/model/usertrip.rs | 25 ++++++++++-- src/tera/admin/planned_event.rs | 2 + src/tera/cox.rs | 37 +++++++++++------ src/tera/mod.rs | 40 +++++++++++++------ templates/index.html.tera | 2 + 9 files changed, 150 insertions(+), 47 deletions(-) diff --git a/migration.sql b/migration.sql index 5e50687..a2b0eb7 100644 --- a/migration.sql +++ b/migration.sql @@ -26,6 +26,7 @@ CREATE TABLE IF NOT EXISTS "trip_details" ( "allow_guests" boolean NOT NULL default false, "notes" TEXT, "always_show" boolean NOT NULL default false, + "is_locked" boolean NOT NULL default false, "trip_type_id" INTEGER REFERENCES trip_type(id) ON DELETE CASCADE ); diff --git a/src/model/planned_event.rs b/src/model/planned_event.rs index 3028724..8444c2e 100644 --- a/src/model/planned_event.rs +++ b/src/model/planned_event.rs @@ -23,6 +23,7 @@ pub struct PlannedEvent { pub allow_guests: bool, trip_type_id: Option, always_show: bool, + is_locked: bool, } #[derive(Serialize)] @@ -49,7 +50,7 @@ impl PlannedEvent { Self, " SELECT - planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show + planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked FROM planned_event INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id WHERE planned_event.id like ? @@ -77,7 +78,7 @@ WHERE planned_event.id like ? let day = format!("{day}"); let events = sqlx::query_as!( PlannedEvent, - "SELECT planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, always_show, max_people, day, notes, allow_guests, trip_type_id + "SELECT planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, always_show, max_people, day, notes, allow_guests, trip_type_id, is_locked FROM planned_event INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id WHERE day=?", @@ -108,7 +109,7 @@ WHERE day=?", pub async fn all(db: &SqlitePool) -> Vec { sqlx::query_as!( PlannedEvent, - "SELECT planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, always_show, max_people, day, notes, allow_guests, trip_type_id + "SELECT planned_event.id, planned_event.name, planned_amount_cox, trip_details_id, planned_starting_time, always_show, max_people, day, notes, allow_guests, trip_type_id, is_locked FROM planned_event INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id", ) @@ -195,6 +196,7 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even max_people: i32, notes: Option<&str>, always_show: bool, + is_locked: bool, ) { sqlx::query!( "UPDATE planned_event SET planned_amount_cox = ? WHERE id = ?", @@ -206,10 +208,11 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even .unwrap(); //Okay, as planned_event can only be created with proper DB backing sqlx::query!( - "UPDATE trip_details SET max_people = ?, notes = ?, always_show=? WHERE id = ?", + "UPDATE trip_details SET max_people = ?, notes = ?, always_show = ?, is_locked = ? WHERE id = ?", max_people, notes, always_show, + is_locked, self.trip_details_id ) .execute(db) @@ -242,6 +245,12 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even write!(&mut buf, "{}", calendar).unwrap(); String::from_utf8(buf).unwrap() } + + pub async fn trip_details(&self, db: &SqlitePool) -> TripDetails { + TripDetails::find_by_id(db, self.trip_details_id) + .await + .unwrap() //ok, not null in db + } } #[cfg(test)] diff --git a/src/model/trip.rs b/src/model/trip.rs index bfb7564..491127d 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -22,6 +22,7 @@ pub struct Trip { pub allow_guests: bool, trip_type_id: Option, always_show: bool, + is_locked: bool, } #[derive(Serialize)] @@ -48,7 +49,7 @@ impl Trip { sqlx::query_as!( Self, " -SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked FROM trip INNER JOIN trip_details ON trip.trip_details_id = trip_details.id INNER JOIN user ON trip.cox_id = user.id @@ -71,6 +72,14 @@ WHERE trip.id=? return Err(CoxHelpError::AlreadyRegisteredAsRower); } + if planned_event.trip_details(db).await.is_locked { + return Err(CoxHelpError::DetailsLocked); + } + + if planned_event.is_rower_registered(db, cox).await { + return Err(CoxHelpError::AlreadyRegisteredAsRower); + } + match sqlx::query!( "INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)", cox.id, @@ -89,7 +98,7 @@ WHERE trip.id=? let trips = sqlx::query_as!( Trip, " -SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked FROM trip INNER JOIN trip_details ON trip.trip_details_id = trip_details.id INNER JOIN user ON trip.cox_id = user.id @@ -141,6 +150,7 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i notes: Option<&str>, trip_type: Option, //TODO: Move to `TripType` always_show: bool, + is_locked: bool, ) -> Result<(), TripUpdateError> { if !trip.is_trip_from_user(cox.id) { return Err(TripUpdateError::NotYourTrip); @@ -158,11 +168,12 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i }; sqlx::query!( - "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ? WHERE id = ?", + "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?", max_people, notes, trip_type, always_show, + is_locked, trip_details_id ) .execute(db) @@ -172,12 +183,23 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i Ok(()) } + pub async fn trip_details(&self, db: &SqlitePool) -> Option { + if let Some(trip_details_id) = self.trip_type_id { + return TripDetails::find_by_id(db, trip_details_id).await; + } + None + } + pub async fn delete_by_planned_event( db: &SqlitePool, cox: &CoxUser, planned_event: &PlannedEvent, - ) -> bool { - sqlx::query!( + ) -> Result<(), TripHelpDeleteError> { + if planned_event.trip_details(db).await.is_locked { + return Err(TripHelpDeleteError::DetailsLocked); + } + + let affected_rows = sqlx::query!( "DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?", cox.id, planned_event.id @@ -185,8 +207,13 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i .execute(db) .await .unwrap() - .rows_affected() - > 0 + .rows_affected(); + + if affected_rows == 0 { + return Err(TripHelpDeleteError::CoxNotHelping); + } + + Ok(()) } pub(crate) async fn delete( @@ -233,6 +260,13 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i pub enum CoxHelpError { AlreadyRegisteredAsRower, AlreadyRegisteredAsCox, + DetailsLocked, +} + +#[derive(Debug, PartialEq)] +pub enum TripHelpDeleteError { + DetailsLocked, + CoxNotHelping, } #[derive(Debug, PartialEq)] @@ -333,9 +367,11 @@ mod test { let trip = Trip::find_by_id(&pool, 1).await.unwrap(); - assert!(Trip::update_own(&pool, &cox, &trip, 10, None, None, false) - .await - .is_ok()); + assert!( + Trip::update_own(&pool, &cox, &trip, 10, None, None, false, false) + .await + .is_ok() + ); let trip = Trip::find_by_id(&pool, 1).await.unwrap(); assert_eq!(trip.max_people, 10); @@ -354,7 +390,7 @@ mod test { let trip = Trip::find_by_id(&pool, 1).await.unwrap(); assert!( - Trip::update_own(&pool, &cox, &trip, 10, None, Some(1), false) + Trip::update_own(&pool, &cox, &trip, 10, None, Some(1), false, false) .await .is_ok() ); @@ -376,9 +412,11 @@ mod test { let trip = Trip::find_by_id(&pool, 1).await.unwrap(); - assert!(Trip::update_own(&pool, &cox, &trip, 10, None, None, false) - .await - .is_err()); + assert!( + Trip::update_own(&pool, &cox, &trip, 10, None, None, false, false) + .await + .is_err() + ); assert_eq!(trip.max_people, 1); } @@ -398,7 +436,9 @@ mod test { //TODO: check why following assert fails //assert!(Trip::find_by_id(&pool, 2).await.is_some()); - Trip::delete_by_planned_event(&pool, &cox, &planned_event).await; + Trip::delete_by_planned_event(&pool, &cox, &planned_event) + .await + .unwrap(); assert!(Trip::find_by_id(&pool, 2).await.is_none()); } diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs index c4f2fff..3b6b91c 100644 --- a/src/model/tripdetails.rs +++ b/src/model/tripdetails.rs @@ -13,6 +13,7 @@ pub struct TripDetails { pub allow_guests: bool, pub trip_type_id: Option, pub always_show: bool, + pub is_locked: bool, } #[derive(FromForm, Serialize)] @@ -33,7 +34,7 @@ impl TripDetails { sqlx::query_as!( TripDetails, " -SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show +SELECT id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show, is_locked FROM trip_details WHERE id like ? ", diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index 10a27ca..8b8500d 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -14,6 +14,10 @@ impl UserTrip { return Err(UserTripError::EventAlreadyFull); } + if trip_details.is_locked { + return Err(UserTripError::DetailsLocked); + } + if user.is_guest && !trip_details.allow_guests { return Err(UserTripError::GuestNotAllowedForThisEvent); } @@ -68,8 +72,15 @@ impl UserTrip { } } - pub async fn delete(db: &SqlitePool, user: &User, trip_details: &TripDetails) { - //TODO: Check if > 2 hrs to event + pub async fn delete( + db: &SqlitePool, + user: &User, + trip_details: &TripDetails, + ) -> Result<(), UserTripDeleteError> { + if trip_details.is_locked { + return Err(UserTripDeleteError::DetailsLocked); + } + let _ = sqlx::query!( "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?", user.id, @@ -77,7 +88,9 @@ impl UserTrip { ) .execute(db) .await - .is_ok(); + .unwrap(); + + Ok(()) } } @@ -86,10 +99,16 @@ pub enum UserTripError { AlreadyRegistered, AlreadyRegisteredAsCox, EventAlreadyFull, + DetailsLocked, CantRegisterAtOwnEvent, GuestNotAllowedForThisEvent, } +#[derive(Debug, PartialEq)] +pub enum UserTripDeleteError { + DetailsLocked, +} + #[cfg(test)] mod test { use crate::{ diff --git a/src/tera/admin/planned_event.rs b/src/tera/admin/planned_event.rs index 87676a5..c732944 100644 --- a/src/tera/admin/planned_event.rs +++ b/src/tera/admin/planned_event.rs @@ -47,6 +47,7 @@ struct UpdatePlannedEventForm<'r> { max_people: i32, notes: Option<&'r str>, always_show: bool, + is_locked: bool, } #[put("/planned-event", data = "")] @@ -64,6 +65,7 @@ async fn update( data.max_people, data.notes, data.always_show, + data.is_locked, ) .await; Flash::success(Redirect::to("/"), "Successfully edited the event") diff --git a/src/tera/cox.rs b/src/tera/cox.rs index ad4a344..79bc5aa 100644 --- a/src/tera/cox.rs +++ b/src/tera/cox.rs @@ -9,7 +9,7 @@ use sqlx::SqlitePool; use crate::model::{ log::Log, planned_event::PlannedEvent, - trip::{CoxHelpError, Trip, TripDeleteError, TripUpdateError}, + trip::{CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError}, tripdetails::{TripDetails, TripDetailsToAdd}, user::CoxUser, }; @@ -43,6 +43,7 @@ struct EditTripForm<'r> { notes: Option<&'r str>, trip_type: Option, always_show: bool, + is_locked: bool, } #[post("/trip/", data = "")] @@ -61,6 +62,7 @@ async fn update( data.notes, data.trip_type, data.always_show, + data.is_locked, ) .await { @@ -99,6 +101,9 @@ async fn join(db: &State, planned_event_id: i64, cox: CoxUser) -> Fl Redirect::to("/"), "Du hast dich bereits als Ruderer angemeldet!", ), + Err(CoxHelpError::DetailsLocked) => { + Flash::error(Redirect::to("/"), "Boot ist bereits eingeteilt.") + } } } else { Flash::error(Redirect::to("/"), "Event gibt's nicht") @@ -129,19 +134,25 @@ async fn remove_trip(db: &State, trip_id: i64, cox: CoxUser) -> Flas #[get("/remove/")] async fn remove(db: &State, planned_event_id: i64, cox: CoxUser) -> Flash { if let Some(planned_event) = PlannedEvent::find_by_id(db, planned_event_id).await { - if Trip::delete_by_planned_event(db, &cox, &planned_event).await { - Log::create( - db, - format!( - "Cox {} deleted registration for planned_event.id={}", - cox.name, planned_event_id - ), - ) - .await; + match Trip::delete_by_planned_event(db, &cox, &planned_event).await { + Ok(_) => { + Log::create( + db, + format!( + "Cox {} deleted registration for planned_event.id={}", + cox.name, planned_event_id + ), + ) + .await; - Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") - } else { - Flash::error(Redirect::to("/"), "Steuermann hilft nicht aus...") + return Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!"); + } + Err(TripHelpDeleteError::DetailsLocked) => { + return Flash::error(Redirect::to("/"), "Boot bereits eingeteilt"); + } + Err(TripHelpDeleteError::CoxNotHelping) => { + return Flash::error(Redirect::to("/"), "Steuermann hilft nicht aus...") + } } } else { Flash::error(Redirect::to("/"), "Planned_event does not exist.") diff --git a/src/tera/mod.rs b/src/tera/mod.rs index bcbee30..04cb21e 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -16,7 +16,7 @@ use crate::model::{ tripdetails::TripDetails, triptype::TripType, user::User, - usertrip::{UserTrip, UserTripError}, + usertrip::{UserTrip, UserTripDeleteError, UserTripError}, }; mod admin; @@ -81,6 +81,10 @@ async fn join(db: &State, trip_details_id: i64, user: User) -> Flash Redirect::to("/"), "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", ), + 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.", + ), } } @@ -90,18 +94,32 @@ async fn remove(db: &State, trip_details_id: i64, user: User) -> Fla return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); }; - UserTrip::delete(db, &user, &trip_details).await; + match UserTrip::delete(db, &user, &trip_details).await { + Ok(_) => { + Log::create( + db, + format!( + "User {} unregistered for trip_details.id={}", + user.name, trip_details_id + ), + ) + .await; - 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::success(Redirect::to("/"), "Erfolgreich abgemeldet!") + Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") + } + } } #[catch(401)] //unauthorized diff --git a/templates/index.html.tera b/templates/index.html.tera index 39f6976..41aba07 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -134,6 +134,7 @@ {{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=planned_event.max_people, min='0') }} {{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', value=planned_event.planned_amount_cox, required=true, min='0') }} {{ macros::checkbox(label='Immer anzeigen', name='always_show', id=planned_event.id,checked=planned_event.always_show) }} + {{ macros::checkbox(label='Gesperrt', name='is_locked', id=planned_event.id,checked=planned_event.is_locked) }} {{ macros::input(label='Anmerkungen', name='notes', type='input', value=planned_event.notes) }} @@ -223,6 +224,7 @@ {{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min='0') }} {{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }} {{ macros::checkbox(label='Immer anzeigen', name='always_show', id=trip.id,checked=trip.always_show) }} + {{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }} {{ macros::select(select_name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }}