Allow to add real guests
This commit is contained in:
		| @@ -48,10 +48,10 @@ CREATE TABLE IF NOT EXISTS "trip" ( | |||||||
| ); | ); | ||||||
|  |  | ||||||
| CREATE TABLE IF NOT EXISTS "user_trip" ( | CREATE TABLE IF NOT EXISTS "user_trip" ( | ||||||
| 	"user_id" INTEGER NOT NULL REFERENCES user(id), | 	"user_id" INTEGER REFERENCES user(id), | ||||||
|  | 	"user_note" text, -- only shown if user_id = none | ||||||
| 	"trip_details_id" INTEGER NOT NULL REFERENCES trip_details(id), | 	"trip_details_id" INTEGER NOT NULL REFERENCES trip_details(id), | ||||||
| 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP, | 	"created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP | ||||||
| 	CONSTRAINT unq UNIQUE (user_id, trip_details_id) -- allow user to participate only once for each trip |  | ||||||
| ); | ); | ||||||
|  |  | ||||||
| CREATE TABLE IF NOT EXISTS "log" ( | CREATE TABLE IF NOT EXISTS "log" ( | ||||||
|   | |||||||
| @@ -37,11 +37,12 @@ pub struct PlannedEventWithUserAndTriptype { | |||||||
| } | } | ||||||
|  |  | ||||||
| //TODO: move to appropriate place | //TODO: move to appropriate place | ||||||
| #[derive(Serialize)] | #[derive(Serialize, Debug)] | ||||||
| pub struct Registration { | pub struct Registration { | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub registered_at: String, |     pub registered_at: String, | ||||||
|     pub is_guest: bool, |     pub is_guest: bool, | ||||||
|  |     pub is_real_guest: bool, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl PlannedEvent { | impl PlannedEvent { | ||||||
| @@ -120,29 +121,40 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id", | |||||||
|  |  | ||||||
|     async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> { |     async fn get_all_cox(&self, db: &SqlitePool) -> Vec<Registration> { | ||||||
|         //TODO: switch to join |         //TODO: switch to join | ||||||
|         sqlx::query_as!( |         sqlx::query!( | ||||||
|             Registration, |  | ||||||
|             " |             " | ||||||
| SELECT | SELECT | ||||||
|     (SELECT name FROM user WHERE cox_id = id) as name, |     (SELECT name FROM user WHERE cox_id = id) as name, | ||||||
|     (SELECT created_at FROM user WHERE cox_id = id) as registered_at, |     (SELECT created_at FROM user WHERE cox_id = id) as registered_at, | ||||||
|     (SELECT is_guest FROM user WHERE cox_id = id) as is_guest |     (SELECT is_guest FROM user WHERE cox_id = id) as is_guest, | ||||||
|  |     0 as is_real_guest | ||||||
| FROM trip WHERE planned_event_id = ? | FROM trip WHERE planned_event_id = ? | ||||||
|         ", |         ", | ||||||
|             self.id |             self.id | ||||||
|         ) |         ) | ||||||
|         .fetch_all(db) |         .fetch_all(db) | ||||||
|         .await |         .await | ||||||
|         .unwrap() //Okay, as PlannedEvent can only be created with proper DB backing |         .unwrap() | ||||||
|  |         .into_iter() | ||||||
|  |         .map(|r| Registration { | ||||||
|  |             name: r.name, | ||||||
|  |             registered_at: r.registered_at, | ||||||
|  |             is_guest: r.is_guest, | ||||||
|  |             is_real_guest: r.is_real_guest == 1, | ||||||
|  |         }) | ||||||
|  |         .collect() //Okay, as PlannedEvent can only be created with proper DB backing | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { |     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { | ||||||
|         //TODO: switch to join |         //TODO: switch to join | ||||||
|         sqlx::query_as!( |         sqlx::query!( | ||||||
|             Registration, |  | ||||||
|             " |             " | ||||||
| SELECT | SELECT | ||||||
|     (SELECT name FROM user WHERE user_trip.user_id = user.id) as name,  |     CASE  | ||||||
|  |         WHEN user_id IS NOT NULL THEN (SELECT name FROM user WHERE user_trip.user_id = user.id) | ||||||
|  |         ELSE user_note | ||||||
|  |     END as name, | ||||||
|  |     user_id IS NULL as is_real_guest, | ||||||
|     (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, |     (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, | ||||||
|     (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest |     (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest | ||||||
| FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_event WHERE id = ?) | FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_event WHERE id = ?) | ||||||
| @@ -151,7 +163,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even | |||||||
|         ) |         ) | ||||||
|         .fetch_all(db) |         .fetch_all(db) | ||||||
|         .await |         .await | ||||||
|         .unwrap() //Okay, as PlannedEvent can only be created with proper DB backing |         .unwrap() | ||||||
|  |         .into_iter() | ||||||
|  |         .map(|r| Registration { | ||||||
|  |             name: r.name.unwrap(), | ||||||
|  |             registered_at: r.registered_at, | ||||||
|  |             is_guest: r.is_guest, | ||||||
|  |             is_real_guest: r.is_real_guest == 1, | ||||||
|  |         }) | ||||||
|  |         .collect() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //TODO: add tests |     //TODO: add tests | ||||||
|   | |||||||
| @@ -126,11 +126,14 @@ WHERE day=? | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { |     async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> { | ||||||
|         sqlx::query_as!( |         sqlx::query!( | ||||||
|             Registration, |  | ||||||
|             " |             " | ||||||
| SELECT | SELECT | ||||||
|     (SELECT name FROM user WHERE user_trip.user_id = user.id) as name, |     CASE  | ||||||
|  |         WHEN user_id IS NOT NULL THEN (SELECT name FROM user WHERE user_trip.user_id = user.id) | ||||||
|  |         ELSE user_note | ||||||
|  |     END as name, | ||||||
|  |     user_id IS NULL as is_real_guest, | ||||||
|     (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, |     (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at, | ||||||
|     (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest |     (SELECT is_guest FROM user WHERE user_trip.user_id = user.id) as is_guest | ||||||
| FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)", | FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)", | ||||||
| @@ -138,7 +141,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i | |||||||
|         ) |         ) | ||||||
|         .fetch_all(db) |         .fetch_all(db) | ||||||
|         .await |         .await | ||||||
|         .unwrap() //Okay, as Trip can only be created with proper DB backing |         .unwrap() | ||||||
|  |         .into_iter() | ||||||
|  |         .map(|r| Registration { | ||||||
|  |             name: r.name.unwrap(), | ||||||
|  |             registered_at: r.registered_at, | ||||||
|  |             is_guest: r.is_guest, | ||||||
|  |             is_real_guest: r.is_real_guest == 1, | ||||||
|  |         }) | ||||||
|  |         .collect() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Cox decides to update own trip. |     /// Cox decides to update own trip. | ||||||
| @@ -497,7 +508,9 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         let user = User::find_by_name(&pool, "rower".into()).await.unwrap(); |         let user = User::find_by_name(&pool, "rower".into()).await.unwrap(); | ||||||
|  |  | ||||||
|         UserTrip::create(&pool, &user, &trip_details).await.unwrap(); |         UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|  |             .await | ||||||
|  |             .unwrap(); | ||||||
|  |  | ||||||
|         let result = trip |         let result = trip | ||||||
|             .delete(&pool, &cox) |             .delete(&pool, &cox) | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | use crate::model::user::User; | ||||||
| use chrono::NaiveDate; | use chrono::NaiveDate; | ||||||
| use rocket::FromForm; | use rocket::FromForm; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::{Deserialize, Serialize}; | ||||||
| @@ -89,6 +90,91 @@ ORDER BY day;", | |||||||
|             .map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap()) |             .map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap()) | ||||||
|             .collect() |             .collect() | ||||||
|     } |     } | ||||||
|  |     pub(crate) async fn user_is_rower(&self, db: &SqlitePool, user: &User) -> bool { | ||||||
|  |         //check if cox if planned_event | ||||||
|  |         let is_rower = sqlx::query!( | ||||||
|  |             "SELECT count(*) as amount | ||||||
|  |             FROM user_trip  | ||||||
|  |             WHERE trip_details_id = ? AND user_id = ?", | ||||||
|  |             self.id, | ||||||
|  |             user.id | ||||||
|  |         ) | ||||||
|  |         .fetch_one(db) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |         is_rower.amount > 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async fn belongs_to_event(&self, db: &SqlitePool) -> bool { | ||||||
|  |         let amount = sqlx::query!( | ||||||
|  |             "SELECT count(*) as amount | ||||||
|  |             FROM planned_event | ||||||
|  |             WHERE trip_details_id = ?", | ||||||
|  |             self.id, | ||||||
|  |         ) | ||||||
|  |         .fetch_one(db) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |         amount.amount > 0 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) async fn user_allowed_to_change(&self, db: &SqlitePool, user: &User) -> bool { | ||||||
|  |         if self.belongs_to_event(db).await { | ||||||
|  |             return user.is_admin; | ||||||
|  |         } else { | ||||||
|  |             return self.user_is_cox(db, user).await != CoxAtTrip::No; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub(crate) async fn user_is_cox(&self, db: &SqlitePool, user: &User) -> CoxAtTrip { | ||||||
|  |         //check if cox if planned_event | ||||||
|  |         let is_cox = sqlx::query!( | ||||||
|  |             "SELECT count(*) as amount | ||||||
|  |             FROM trip  | ||||||
|  |             WHERE planned_event_id = ( | ||||||
|  |                 SELECT id FROM planned_event WHERE trip_details_id = ? | ||||||
|  |             )  | ||||||
|  |             AND cox_id = ?", | ||||||
|  |             self.id, | ||||||
|  |             user.id | ||||||
|  |         ) | ||||||
|  |         .fetch_one(db) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |         if is_cox.amount > 0 { | ||||||
|  |             return CoxAtTrip::Yes(Action::Helping); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //check if cox if own event | ||||||
|  |         let is_cox = sqlx::query!( | ||||||
|  |             "SELECT count(*) as amount | ||||||
|  |             FROM trip  | ||||||
|  |             WHERE trip_details_id = ?  | ||||||
|  |             AND cox_id = ?", | ||||||
|  |             self.id, | ||||||
|  |             user.id | ||||||
|  |         ) | ||||||
|  |         .fetch_one(db) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  |         if is_cox.amount > 0 { | ||||||
|  |             return CoxAtTrip::Yes(Action::Own); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         CoxAtTrip::No | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(PartialEq, Debug)] | ||||||
|  | pub(crate) enum CoxAtTrip { | ||||||
|  |     No, | ||||||
|  |     Yes(Action), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(PartialEq, Debug)] | ||||||
|  | pub(crate) enum Action { | ||||||
|  |     Helping, | ||||||
|  |     Own, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| use sqlx::SqlitePool; | use sqlx::SqlitePool; | ||||||
|  |  | ||||||
| use super::{tripdetails::TripDetails, user::User}; | use super::{tripdetails::TripDetails, user::User}; | ||||||
|  | use crate::model::tripdetails::{Action, CoxAtTrip::Yes}; | ||||||
|  |  | ||||||
| pub struct UserTrip {} | pub struct UserTrip {} | ||||||
|  |  | ||||||
| @@ -9,6 +10,7 @@ impl UserTrip { | |||||||
|         db: &SqlitePool, |         db: &SqlitePool, | ||||||
|         user: &User, |         user: &User, | ||||||
|         trip_details: &TripDetails, |         trip_details: &TripDetails, | ||||||
|  |         user_note: Option<String>, | ||||||
|     ) -> Result<(), UserTripError> { |     ) -> Result<(), UserTripError> { | ||||||
|         if trip_details.is_full(db).await { |         if trip_details.is_full(db).await { | ||||||
|             return Err(UserTripError::EventAlreadyFull); |             return Err(UserTripError::EventAlreadyFull); | ||||||
| @@ -22,65 +24,75 @@ impl UserTrip { | |||||||
|             return Err(UserTripError::GuestNotAllowedForThisEvent); |             return Err(UserTripError::GuestNotAllowedForThisEvent); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //TODO: Check if user sees the event (otherwise she could forge trip_details_id |         //TODO: Check if user sees the event (otherwise she could forge trip_details_id) | ||||||
|  |  | ||||||
|         //check if cox if own event |         let is_cox = trip_details.user_is_cox(db, user).await; | ||||||
|         let is_cox = sqlx::query!( |         if user_note.is_none() { | ||||||
|             "SELECT count(*) as amount |             if let Yes(action) = is_cox { | ||||||
|             FROM trip  |                 match action { | ||||||
|             WHERE trip_details_id = ?  |                     Action::Helping => return Err(UserTripError::AlreadyRegisteredAsCox), | ||||||
|             AND cox_id = ?", |                     Action::Own => return Err(UserTripError::CantRegisterAtOwnEvent), | ||||||
|             trip_details.id, |                 }; | ||||||
|             user.id |  | ||||||
|         ) |  | ||||||
|         .fetch_one(db) |  | ||||||
|         .await |  | ||||||
|         .unwrap(); |  | ||||||
|         if is_cox.amount > 0 { |  | ||||||
|             return Err(UserTripError::CantRegisterAtOwnEvent); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         //TODO: can probably move to trip.rs? |             if trip_details.user_is_rower(db, user).await { | ||||||
|         //check if cox if planned_event |                 return Err(UserTripError::AlreadyRegistered); | ||||||
|         let is_cox = sqlx::query!( |  | ||||||
|             "SELECT count(*) as amount |  | ||||||
|             FROM trip  |  | ||||||
|             WHERE planned_event_id = ( |  | ||||||
|                 SELECT id FROM planned_event WHERE trip_details_id = ? |  | ||||||
|             )  |  | ||||||
|             AND cox_id = ?", |  | ||||||
|             trip_details.id, |  | ||||||
|             user.id |  | ||||||
|         ) |  | ||||||
|         .fetch_one(db) |  | ||||||
|         .await |  | ||||||
|         .unwrap(); |  | ||||||
|         if is_cox.amount > 0 { |  | ||||||
|             return Err(UserTripError::AlreadyRegisteredAsCox); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|         match sqlx::query!( |             sqlx::query!( | ||||||
|                 "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)", |                 "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)", | ||||||
|                 user.id, |                 user.id, | ||||||
|             trip_details.id |                 trip_details.id, | ||||||
|             ) |             ) | ||||||
|             .execute(db) |             .execute(db) | ||||||
|             .await |             .await | ||||||
|         { |             .unwrap(); | ||||||
|             Ok(_) => Ok(()), |         } else { | ||||||
|             Err(_) => Err(UserTripError::AlreadyRegistered), |             if !trip_details.user_allowed_to_change(db, user).await { | ||||||
|  |                 return Err(UserTripError::NotAllowedToAddGuest); | ||||||
|             } |             } | ||||||
|  |             sqlx::query!( | ||||||
|  |                 "INSERT INTO user_trip (user_note, trip_details_id) VALUES(?, ?)", | ||||||
|  |                 user_note, | ||||||
|  |                 trip_details.id, | ||||||
|  |             ) | ||||||
|  |             .execute(db) | ||||||
|  |             .await | ||||||
|  |             .unwrap(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub async fn delete( |     pub async fn delete( | ||||||
|         db: &SqlitePool, |         db: &SqlitePool, | ||||||
|         user: &User, |         user: &User, | ||||||
|         trip_details: &TripDetails, |         trip_details: &TripDetails, | ||||||
|  |         name: Option<String>, | ||||||
|     ) -> Result<(), UserTripDeleteError> { |     ) -> Result<(), UserTripDeleteError> { | ||||||
|         if trip_details.is_locked { |         if trip_details.is_locked { | ||||||
|             return Err(UserTripDeleteError::DetailsLocked); |             return Err(UserTripDeleteError::DetailsLocked); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if let Some(name) = name { | ||||||
|  |             if !trip_details.user_allowed_to_change(db, user).await { | ||||||
|  |                 return Err(UserTripDeleteError::NotAllowedToDeleteGuest); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if sqlx::query!( | ||||||
|  |                 "DELETE FROM user_trip WHERE user_note = ? AND trip_details_id = ?", | ||||||
|  |                 name, | ||||||
|  |                 trip_details.id | ||||||
|  |             ) | ||||||
|  |             .execute(db) | ||||||
|  |             .await | ||||||
|  |             .unwrap() | ||||||
|  |             .rows_affected() | ||||||
|  |                 == 0 | ||||||
|  |             { | ||||||
|  |                 return Err(UserTripDeleteError::GuestNotParticipating); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|             let _ = sqlx::query!( |             let _ = sqlx::query!( | ||||||
|                 "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?", |                 "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?", | ||||||
|                 user.id, |                 user.id, | ||||||
| @@ -89,7 +101,7 @@ impl UserTrip { | |||||||
|             .execute(db) |             .execute(db) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -102,11 +114,14 @@ pub enum UserTripError { | |||||||
|     DetailsLocked, |     DetailsLocked, | ||||||
|     CantRegisterAtOwnEvent, |     CantRegisterAtOwnEvent, | ||||||
|     GuestNotAllowedForThisEvent, |     GuestNotAllowedForThisEvent, | ||||||
|  |     NotAllowedToAddGuest, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug, PartialEq)] | #[derive(Debug, PartialEq)] | ||||||
| pub enum UserTripDeleteError { | pub enum UserTripDeleteError { | ||||||
|     DetailsLocked, |     DetailsLocked, | ||||||
|  |     GuestNotParticipating, | ||||||
|  |     NotAllowedToDeleteGuest, | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| @@ -130,7 +145,9 @@ mod test { | |||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |  | ||||||
|         UserTrip::create(&pool, &user, &trip_details).await.unwrap(); |         UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|  |             .await | ||||||
|  |             .unwrap(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[sqlx::test] |     #[sqlx::test] | ||||||
| @@ -143,12 +160,14 @@ mod test { | |||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |  | ||||||
|         UserTrip::create(&pool, &user, &trip_details).await.unwrap(); |         UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|         UserTrip::create(&pool, &user2, &trip_details) |             .await | ||||||
|  |             .unwrap(); | ||||||
|  |         UserTrip::create(&pool, &user2, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|         let result = UserTrip::create(&pool, &user3, &trip_details) |         let result = UserTrip::create(&pool, &user3, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .expect_err("Expect registration to fail because trip is already full"); |             .expect_err("Expect registration to fail because trip is already full"); | ||||||
|  |  | ||||||
| @@ -162,9 +181,11 @@ mod test { | |||||||
|         let user = User::find_by_name(&pool, "cox".into()).await.unwrap(); |         let user = User::find_by_name(&pool, "cox".into()).await.unwrap(); | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |  | ||||||
|         UserTrip::create(&pool, &user, &trip_details).await.unwrap(); |         UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|  |             .await | ||||||
|  |             .unwrap(); | ||||||
|  |  | ||||||
|         let result = UserTrip::create(&pool, &user, &trip_details) |         let result = UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .expect_err("Expect registration to fail because user is same as responsible cox"); |             .expect_err("Expect registration to fail because user is same as responsible cox"); | ||||||
|  |  | ||||||
| @@ -179,7 +200,7 @@ mod test { | |||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 2).await.unwrap(); |         let trip_details = TripDetails::find_by_id(&pool, 2).await.unwrap(); | ||||||
|  |  | ||||||
|         let result = UserTrip::create(&pool, &user, &trip_details) |         let result = UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .expect_err("Expect registration to fail because user is same as responsible cox"); |             .expect_err("Expect registration to fail because user is same as responsible cox"); | ||||||
|  |  | ||||||
| @@ -196,12 +217,11 @@ mod test { | |||||||
|             .try_into() |             .try_into() | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |  | ||||||
|         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); |         let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |  | ||||||
|         Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); |         Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); | ||||||
|  |  | ||||||
|         let result = UserTrip::create(&pool, &cox, &trip_details) |         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |         let result = UserTrip::create(&pool, &cox, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .expect_err("Expect registration to fail because user is already registered as cox"); |             .expect_err("Expect registration to fail because user is already registered as cox"); | ||||||
|  |  | ||||||
| @@ -216,7 +236,7 @@ mod test { | |||||||
|  |  | ||||||
|         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); |         let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); | ||||||
|  |  | ||||||
|         let result = UserTrip::create(&pool, &user, &trip_details) |         let result = UserTrip::create(&pool, &user, &trip_details, None) | ||||||
|             .await |             .await | ||||||
|             .expect_err("Not allowed for guests"); |             .expect_err("Not allowed for guests"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,13 +46,18 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | |||||||
|     Template::render("index", context.into_json()) |     Template::render("index", context.into_json()) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/join/<trip_details_id>")] | #[get("/join/<trip_details_id>?<user_note>")] | ||||||
| async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> { | async fn join( | ||||||
|  |     db: &State<SqlitePool>, | ||||||
|  |     trip_details_id: i64, | ||||||
|  |     user: User, | ||||||
|  |     user_note: Option<String>, | ||||||
|  | ) -> Flash<Redirect> { | ||||||
|     let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { |     let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { | ||||||
|         return Flash::error(Redirect::to("/"), "Trip_details do not exist."); |         return Flash::error(Redirect::to("/"), "Trip_details do not exist."); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     match UserTrip::create(db, &user, &trip_details).await { |     match UserTrip::create(db, &user, &trip_details, user_note).await { | ||||||
|         Ok(_) => { |         Ok(_) => { | ||||||
|             Log::create( |             Log::create( | ||||||
|                 db, |                 db, | ||||||
| @@ -81,6 +86,10 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash | |||||||
|             Redirect::to("/"), |             Redirect::to("/"), | ||||||
|             "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", |             "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( |         Err(UserTripError::DetailsLocked) => Flash::error( | ||||||
|             Redirect::to("/"), |             Redirect::to("/"), | ||||||
|             "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.", |             "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.", | ||||||
| @@ -88,13 +97,18 @@ async fn join(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[get("/remove/<trip_details_id>")] | #[get("/remove/<trip_details_id>/<name>")] | ||||||
| async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> { | async fn remove_guest( | ||||||
|  |     db: &State<SqlitePool>, | ||||||
|  |     trip_details_id: i64, | ||||||
|  |     user: User, | ||||||
|  |     name: String, | ||||||
|  | ) -> Flash<Redirect> { | ||||||
|     let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { |     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("/"), "TripDetailsId does not exist"); | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     match UserTrip::delete(db, &user, &trip_details).await { |     match UserTrip::delete(db, &user, &trip_details, Some(name)).await { | ||||||
|         Ok(_) => { |         Ok(_) => { | ||||||
|             Log::create( |             Log::create( | ||||||
|                 db, |                 db, | ||||||
| @@ -119,6 +133,50 @@ async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Fla | |||||||
|  |  | ||||||
|             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("/"), "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/<trip_details_id>")] | ||||||
|  | async fn remove(db: &State<SqlitePool>, trip_details_id: i64, user: User) -> Flash<Redirect> { | ||||||
|  |     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"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -135,7 +193,7 @@ pub struct Config { | |||||||
|  |  | ||||||
| pub fn config(rocket: Rocket<Build>) -> Rocket<Build> { | pub fn config(rocket: Rocket<Build>) -> Rocket<Build> { | ||||||
|     rocket |     rocket | ||||||
|         .mount("/", routes![index, join, remove]) |         .mount("/", routes![index, join, remove, remove_guest]) | ||||||
|         .mount("/auth", auth::routes()) |         .mount("/auth", auth::routes()) | ||||||
|         .mount("/log", log::routes()) |         .mount("/log", log::routes()) | ||||||
|         .mount("/stat", stat::routes()) |         .mount("/stat", stat::routes()) | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ | |||||||
| 	</div> | 	</div> | ||||||
| {% endmacro alert %} | {% endmacro alert %} | ||||||
|  |  | ||||||
| {% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white') %} | {% macro box(participants, empty_seats='', header='Freie Plätze:', text='Keine Ruderer angemeldet', bg='primary-600', color='white', trip_details_id='', allow_removing=false) %} | ||||||
| 	<div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }} | 	<div class="text-{{ color }} bg-{{ bg }} text-center p-1 mt-1 rounded-t-md">{{ header }} | ||||||
| 		{{ empty_seats }}</div> | 		{{ empty_seats }}</div> | ||||||
| 	<div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> | 	<div class="p-2 border border-t-0 border-{{ bg }} mb-4 rounded-b-md"> | ||||||
| @@ -111,7 +111,13 @@ | |||||||
| 			{% for rower in participants %} | 			{% for rower in participants %} | ||||||
| 				{{ rower.name }} | 				{{ rower.name }} | ||||||
| 				{% if rower.is_guest %} | 				{% if rower.is_guest %} | ||||||
|  | 					<small class="text-gray-600">(Scheckbuch)</small> | ||||||
|  | 				{% endif %} | ||||||
|  | 				{% if rower.is_real_guest %} | ||||||
| 					<small class="text-gray-600">(Gast)</small> | 					<small class="text-gray-600">(Gast)</small> | ||||||
|  | 					{% if allow_removing %} | ||||||
|  | 				  	<a href="/remove/{{ trip_details_id }}/{{ rower.name }}" class="btn btn-attention btn-fw">Abmelden</a> | ||||||
|  | 					{% endif %} | ||||||
| 				{% endif %} | 				{% endif %} | ||||||
| 				<span class="hidden">(angemeldet seit | 				<span class="hidden">(angemeldet seit | ||||||
| 					{{ rower.registered_at }})</span><br/> | 					{{ rower.registered_at }})</span><br/> | ||||||
|   | |||||||
| @@ -115,10 +115,18 @@ | |||||||
| 												{# --- START List Rowers --- #} | 												{# --- START List Rowers --- #} | ||||||
| 												{% if planned_event.max_people > 0 %} | 												{% if planned_event.max_people > 0 %} | ||||||
| 													{% set amount_cur_rower = planned_event.rower | length %} | 													{% set amount_cur_rower = planned_event.rower | length %} | ||||||
| 													{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black') }} | 													{{ macros::box(participants=planned_event.rower, empty_seats=planned_event.max_people - amount_cur_rower, bg='primary-100', color='black', trip_details_id=planned_event.trip_details_id, allow_removing=loggedin_user.is_admin) }} | ||||||
| 												{% endif %} | 												{% endif %} | ||||||
| 												{# --- END List Rowers --- #} | 												{# --- END List Rowers --- #} | ||||||
|  |  | ||||||
|  | 													{% if loggedin_user.is_admin %} | ||||||
|  | 														<form action="/join/{{ planned_event.trip_details_id }}" method="get" /> | ||||||
|  | 															{{ macros::input(label='Gast', name='user_note', type='text', required=true) }} | ||||||
|  | 															<input value="Gast hinzufügen" class="btn btn-primary" type="submit"/> | ||||||
|  | 														</form> | ||||||
|  | 													{% endif %} | ||||||
|  |  | ||||||
|  |  | ||||||
| 												{% if planned_event.allow_guests %} | 												{% if planned_event.allow_guests %} | ||||||
| 													<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div> | 													<div class="text-primary-900 bg-primary-50 text-center p-1 mb-4">Gäste willkommen!</div> | ||||||
| 												{% endif %} | 												{% endif %} | ||||||
| @@ -213,7 +221,13 @@ | |||||||
| 													{{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }} | 													{{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }} | ||||||
| 												{% else %} | 												{% else %} | ||||||
| 													{% set amount_cur_rower = trip.rower | length %} | 													{% 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') }} | 													{{ 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 %} | ||||||
|  | 														<form action="/join/{{ trip.trip_details_id }}" method="get" /> | ||||||
|  | 															{{ macros::input(label='Gast', name='user_note', type='text', required=true) }} | ||||||
|  | 															<input value="Gast hinzufügen" class="btn btn-primary" type="submit"/> | ||||||
|  | 														</form> | ||||||
|  | 													{% endif %} | ||||||
| 												{% endif %} | 												{% endif %} | ||||||
|  |  | ||||||
| 												{# --- START Edit Form --- #} | 												{# --- START Edit Form --- #} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user