diff --git a/Cargo.lock b/Cargo.lock index e9966dc..1a799af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,9 +263,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -335,7 +338,7 @@ dependencies = [ "rand", "sha2", "subtle", - "time 0.3.24", + "time 0.3.25", "version_check", ] @@ -420,15 +423,15 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8810e7e2cf385b1e9b50d68264908ec367ba642c96d02edfe61c39e88e2a3c01" +checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" [[package]] name = "deunicode" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" +checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43" [[package]] name = "devise" @@ -563,13 +566,13 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.21" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "windows-sys 0.48.0", ] @@ -754,9 +757,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aca8bbd8e0707c1887a8bbb7e6b40e228f251ff5d62c8220a4a7a53c73aff006" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", @@ -1090,9 +1093,9 @@ dependencies = [ [[package]] name = "kqueue" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" dependencies = [ "kqueue-sys", "libc", @@ -1100,9 +1103,9 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", @@ -1139,9 +1142,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "lock_api" @@ -1440,9 +1443,9 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2d1d55045829d65aad9d389139882ad623b33b904e7c9f1b10c5b8927298e5" +checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" dependencies = [ "thiserror", "ucd-trie", @@ -1450,9 +1453,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f94bca7e7a599d89dea5dfa309e217e7906c3c007fb9c3299c40b10d6a315d3" +checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" dependencies = [ "pest", "pest_generator", @@ -1460,9 +1463,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d490fe7e8556575ff6911e45567ab95e71617f43781e5c05490dc8d75c965c" +checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" dependencies = [ "pest", "pest_meta", @@ -1473,9 +1476,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674c66ebb4b4d9036012091b537aae5878970d6999f81a265034d85b136b341" +checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" dependencies = [ "once_cell", "pest", @@ -1523,18 +1526,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", @@ -1543,9 +1546,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c" [[package]] name = "pin-utils" @@ -1684,13 +1687,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.4", + "regex-automata 0.3.6", "regex-syntax 0.7.4", ] @@ -1705,9 +1708,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" dependencies = [ "aho-corasick", "memchr", @@ -1770,7 +1773,7 @@ dependencies = [ "serde", "state", "tempfile", - "time 0.3.24", + "time 0.3.25", "tokio", "tokio-stream", "tokio-util", @@ -1830,7 +1833,7 @@ dependencies = [ "smallvec", "stable-pattern", "state", - "time 0.3.24", + "time 0.3.25", "tokio", "uncased", ] @@ -1860,9 +1863,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" dependencies = [ "bitflags 2.3.3", "errno", @@ -1937,18 +1940,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.179" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.179" +version = "1.0.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" dependencies = [ "proc-macro2", "quote", @@ -2122,7 +2125,7 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", - "time 0.3.24", + "time 0.3.25", "tokio-stream", "url", "webpki-roots", @@ -2216,9 +2219,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.7.0" +version = "3.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", "fastrand", @@ -2301,9 +2304,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79eabcd964882a646b3584543ccabeae7869e9ac32a46f6f22b7a5bd405308b" +checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" dependencies = [ "deranged", "itoa", @@ -2969,9 +2972,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" dependencies = [ "memchr", ] diff --git a/migration.sql b/migration.sql index a2b0eb7..4c87b1a 100644 --- a/migration.sql +++ b/migration.sql @@ -48,10 +48,10 @@ CREATE TABLE IF NOT EXISTS "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), - "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 + "created_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS "log" ( diff --git a/src/model/planned_event.rs b/src/model/planned_event.rs index 8444c2e..ae7dfa4 100644 --- a/src/model/planned_event.rs +++ b/src/model/planned_event.rs @@ -37,11 +37,12 @@ pub struct PlannedEventWithUserAndTriptype { } //TODO: move to appropriate place -#[derive(Serialize)] +#[derive(Serialize, Debug)] pub struct Registration { pub name: String, pub registered_at: String, pub is_guest: bool, + pub is_real_guest: bool, } 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 { //TODO: switch to join - sqlx::query_as!( - Registration, + sqlx::query!( " SELECT (SELECT name FROM user WHERE cox_id = id) as name, (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 = ? ", self.id ) .fetch_all(db) .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 { //TODO: switch to join - sqlx::query_as!( - Registration, + sqlx::query!( " 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 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 = ?) @@ -151,7 +163,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM planned_even ) .fetch_all(db) .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 diff --git a/src/model/trip.rs b/src/model/trip.rs index 491127d..a7a4037 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -126,11 +126,14 @@ WHERE day=? } async fn get_all_rower(&self, db: &SqlitePool) -> Vec { - sqlx::query_as!( - Registration, + sqlx::query!( " 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 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 = ?)", @@ -138,7 +141,15 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i ) .fetch_all(db) .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. @@ -497,7 +508,9 @@ mod test { .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 .delete(&pool, &cox) diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs index 3b6b91c..b913810 100644 --- a/src/model/tripdetails.rs +++ b/src/model/tripdetails.rs @@ -1,3 +1,4 @@ +use crate::model::user::User; use chrono::NaiveDate; use rocket::FromForm; use serde::{Deserialize, Serialize}; @@ -89,6 +90,91 @@ ORDER BY day;", .map(|a| NaiveDate::parse_from_str(&a, "%Y-%m-%d").unwrap()) .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)] diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index 8b8500d..de133ed 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -1,6 +1,7 @@ use sqlx::SqlitePool; use super::{tripdetails::TripDetails, user::User}; +use crate::model::tripdetails::{Action, CoxAtTrip::Yes}; pub struct UserTrip {} @@ -9,6 +10,7 @@ impl UserTrip { db: &SqlitePool, user: &User, trip_details: &TripDetails, + user_note: Option, ) -> Result<(), UserTripError> { if trip_details.is_full(db).await { return Err(UserTripError::EventAlreadyFull); @@ -22,74 +24,84 @@ impl UserTrip { 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 = sqlx::query!( - "SELECT count(*) as amount - FROM trip - 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::CantRegisterAtOwnEvent); + let is_cox = trip_details.user_is_cox(db, user).await; + if user_note.is_none() { + if let Yes(action) = is_cox { + match action { + Action::Helping => return Err(UserTripError::AlreadyRegisteredAsCox), + Action::Own => return Err(UserTripError::CantRegisterAtOwnEvent), + }; + } + + if trip_details.user_is_rower(db, user).await { + return Err(UserTripError::AlreadyRegistered); + } + + sqlx::query!( + "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)", + user.id, + trip_details.id, + ) + .execute(db) + .await + .unwrap(); + } else { + 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(); } - //TODO: can probably move to trip.rs? - //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 = ?", - trip_details.id, - user.id - ) - .fetch_one(db) - .await - .unwrap(); - if is_cox.amount > 0 { - return Err(UserTripError::AlreadyRegisteredAsCox); - } - - match sqlx::query!( - "INSERT INTO user_trip (user_id, trip_details_id) VALUES(?, ?)", - user.id, - trip_details.id - ) - .execute(db) - .await - { - Ok(_) => Ok(()), - Err(_) => Err(UserTripError::AlreadyRegistered), - } + Ok(()) } pub async fn delete( db: &SqlitePool, user: &User, trip_details: &TripDetails, + name: Option, ) -> 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, - trip_details.id - ) - .execute(db) - .await - .unwrap(); + 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!( + "DELETE FROM user_trip WHERE user_id = ? AND trip_details_id = ?", + user.id, + trip_details.id + ) + .execute(db) + .await + .unwrap(); + } Ok(()) } } @@ -102,11 +114,14 @@ pub enum UserTripError { DetailsLocked, CantRegisterAtOwnEvent, GuestNotAllowedForThisEvent, + NotAllowedToAddGuest, } #[derive(Debug, PartialEq)] pub enum UserTripDeleteError { DetailsLocked, + GuestNotParticipating, + NotAllowedToDeleteGuest, } #[cfg(test)] @@ -130,7 +145,9 @@ mod test { 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] @@ -143,12 +160,14 @@ mod test { let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); - UserTrip::create(&pool, &user, &trip_details).await.unwrap(); - UserTrip::create(&pool, &user2, &trip_details) + UserTrip::create(&pool, &user, &trip_details, None) + .await + .unwrap(); + UserTrip::create(&pool, &user2, &trip_details, None) .await .unwrap(); - let result = UserTrip::create(&pool, &user3, &trip_details) + let result = UserTrip::create(&pool, &user3, &trip_details, None) .await .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 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 .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 result = UserTrip::create(&pool, &user, &trip_details) + let result = UserTrip::create(&pool, &user, &trip_details, None) .await .expect_err("Expect registration to fail because user is same as responsible cox"); @@ -196,12 +217,11 @@ mod test { .try_into() .unwrap(); - let trip_details = TripDetails::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(); - 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 .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 result = UserTrip::create(&pool, &user, &trip_details) + let result = UserTrip::create(&pool, &user, &trip_details, None) .await .expect_err("Not allowed for guests"); diff --git a/src/tera/mod.rs b/src/tera/mod.rs index 04cb21e..145b404 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -46,13 +46,18 @@ async fn index(db: &State, user: User, flash: Option")] -async fn join(db: &State, trip_details_id: i64, user: User) -> Flash { +#[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).await { + match UserTrip::create(db, &user, &trip_details, user_note).await { Ok(_) => { Log::create( db, @@ -81,6 +86,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::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.", @@ -88,13 +97,18 @@ async fn join(db: &State, trip_details_id: i64, user: User) -> Flash } } -#[get("/remove/")] -async fn remove(db: &State, trip_details_id: i64, user: User) -> Flash { +#[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).await { + match UserTrip::delete(db, &user, &trip_details, Some(name)).await { Ok(_) => { Log::create( db, @@ -119,6 +133,50 @@ async fn remove(db: &State, 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.") } + 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"); + } } } @@ -135,7 +193,7 @@ pub struct Config { pub fn config(rocket: Rocket) -> Rocket { rocket - .mount("/", routes![index, join, remove]) + .mount("/", routes![index, join, remove, remove_guest]) .mount("/auth", auth::routes()) .mount("/log", log::routes()) .mount("/stat", stat::routes()) diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 76d26f3..f82955c 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -20,7 +20,7 @@
+ Scheckbuch
@@ -49,7 +49,7 @@ {% endif %}
- {{ macros::checkbox(label='Gast', name='is_guest', id=loop.index , checked=user.is_guest) }} + {{ macros::checkbox(label='Scheckbuch', name='is_guest', id=loop.index , checked=user.is_guest) }} {{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }} {{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }} {{ macros::checkbox(label='Admin', name='is_admin', id=loop.index , checked=user.is_admin) }} diff --git a/templates/faq.html.tera b/templates/faq.html.tera index b32f242..66b2a4b 100644 --- a/templates/faq.html.tera +++ b/templates/faq.html.tera @@ -10,7 +10,7 @@ {% if loggedin_user.is_cox %} {{ macros::faq( question='Was bedeuted "gesperrt"?', - answer='Sobald du keine An- und Abmeldungen mehr erlauben willst (zB weil es bereits eine Bootseinteilung gibt), kannst du eine Ausfahrt sperren. Um deinen administrativen Aufwand zu minimieren, versuche die Ausfahrt möglichst spät zu sperren (ansonsten bekommst du potentiell private Nachrichten von Rudernde, die sich noch an- oder abmelden wollen.') + answer='Sobald du keine An- und Abmeldungen mehr erlauben willst (zB weil es bereits eine Bootseinteilung gibt), kannst du eine Ausfahrt sperren. Um deinen administrativen Aufwand zu minimieren, versuche die Ausfahrt möglichst spät zu sperren (ansonsten bekommst du potentiell private Nachrichten von Rudernde, die sich noch an- oder abmelden wollen).') }} {{ macros::faq( diff --git a/templates/includes/macros.html.tera b/templates/includes/macros.html.tera index 3a2567a..de64ad4 100644 --- a/templates/includes/macros.html.tera +++ b/templates/includes/macros.html.tera @@ -103,7 +103,7 @@
{% 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) %}
{{ header }} {{ empty_seats }}
@@ -111,7 +111,13 @@ {% for rower in participants %} {{ rower.name }} {% if rower.is_guest %} + (Scheckbuch) + {% endif %} + {% if rower.is_real_guest %} (Gast) + {% if allow_removing %} + Abmelden + {% endif %} {% endif %}
diff --git a/templates/index.html.tera b/templates/index.html.tera index 41aba07..5a5bd2c 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -115,10 +115,18 @@ {# --- START List Rowers --- #} {% if planned_event.max_people > 0 %} {% 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 %} {# --- END List Rowers --- #} + {% if loggedin_user.is_admin %} +
+ {{ macros::input(label='Gast', name='user_note', type='text', required=true) }} + +
+ {% endif %} + + {% if planned_event.allow_guests %}
Gäste willkommen!
{% endif %} @@ -213,7 +221,13 @@ {{ macros::box(participants=trip.rower,bg='[#f43f5e]',header='Absage') }} {% else %} {% 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 %} +
+ {{ macros::input(label='Gast', name='user_note', type='text', required=true) }} + +
+ {% endif %} {% endif %} {# --- START Edit Form --- #}