diff --git a/README.md b/README.md index 710086d..fc42db6 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,6 @@ - [x] model/user.rs - [x] model/tripdetails.rs - [x] model/planned_event.rs -- [ ] model/trip.rs +- [.] model/trip.rs - [ ] model/usertrip.rs - [ ] Rest? diff --git a/seeds.sql b/seeds.sql index b10d7f5..8333172 100644 --- a/seeds.sql +++ b/seeds.sql @@ -3,6 +3,11 @@ INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('rower', false, INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('guest', false, false, true, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$GF6gizbI79Bh0zA9its8S0gram956v+YIV8w8VpwJnQ'); INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('cox', true, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); INSERT INTO "user" (name) VALUES('new'); +INSERT INTO "user" (name, is_cox, is_admin, is_guest, pw) VALUES('cox2', true, false, false, '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); -INSERT INTO "trip_details" (planned_starting_time, max_people, day) VALUES("10:00", 1, "1970-01-01"); +INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES("10:00", 1, "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); + +INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES("11:00", 1, "1970-01-02", "trip_details for trip from cox"); +INSERT INTO "trip" (cox_id, trip_details_id) VALUES(4, 2); + diff --git a/src/model/planned_event.rs b/src/model/planned_event.rs index 3fc1bf9..b1d1712 100644 --- a/src/model/planned_event.rs +++ b/src/model/planned_event.rs @@ -4,7 +4,7 @@ use sqlx::SqlitePool; #[derive(Serialize, Clone)] pub struct PlannedEvent { - id: i64, + pub id: i64, name: String, planned_amount_cox: i64, allow_guests: bool, @@ -47,6 +47,7 @@ WHERE planned_event.id like ? .await .ok() } + pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec { let day = format!("{day}"); let events = sqlx::query_as!( diff --git a/src/model/trip.rs b/src/model/trip.rs index 8adb67f..137b45e 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -2,9 +2,13 @@ use chrono::NaiveDate; use serde::Serialize; use sqlx::SqlitePool; -use super::planned_event::Registration; +use super::{ + planned_event::{PlannedEvent, Registration}, + tripdetails::TripDetails, + user::{CoxUser, User}, +}; -#[derive(Serialize, Clone)] +#[derive(Serialize, Clone, Debug)] pub struct Trip { id: i64, cox_id: i64, @@ -24,6 +28,70 @@ pub struct TripWithUser { } impl Trip { + /// Cox decides to create own trip. + pub async fn new_own(db: &SqlitePool, cox: &CoxUser, trip_details: TripDetails) { + sqlx::query!( + "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)", + cox.id, + trip_details.id + ) + .execute(db) + .await + .unwrap(); //Okay, bc. CoxUser + TripDetails can only be created with proper DB backing + } + + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { + sqlx::query_as!( + Self, + " +SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes +FROM trip +INNER JOIN trip_details ON trip.trip_details_id = trip_details.id +INNER JOIN user ON trip.cox_id = user.id +WHERE trip.id=? + ", + id + ) + .fetch_one(db) + .await + .ok() + } + + /// Cox decides to help in a planned event. + pub async fn new_join( + db: &SqlitePool, + cox: &CoxUser, + planned_event: &PlannedEvent, + ) -> Result<(), CoxHelpError> { + let is_rower = sqlx::query!( + "SELECT count(*) as amount + FROM user_trip + WHERE trip_details_id = + (SELECT trip_details_id FROM planned_event WHERE id = ?) + AND user_id = ?", + planned_event.id, + cox.id + ) + .fetch_one(db) + .await + .unwrap(); //Okay, bc planned_event can only be created with proper DB backing + if is_rower.amount > 0 { + return Err(CoxHelpError::AlreadyRegisteredAsRower); + } + + match sqlx::query!( + "INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)", + cox.id, + planned_event.id + ) + .execute(db) + .await + { + Ok(_) => Ok(()), + Err(_) => Err(CoxHelpError::AlreadyRegisteredAsCox), + } + } + pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec { let day = format!("{day}"); let trips = sqlx::query_as!( @@ -44,13 +112,13 @@ WHERE day=? for trip in trips { ret.push(TripWithUser { trip: trip.clone(), - rower: Self::get_all_rower_for_id(db, trip.id).await, + rower: trip.get_all_rower(db).await, }); } ret } - async fn get_all_rower_for_id(db: &SqlitePool, trip_id: i64) -> Vec { + async fn get_all_rower(&self, db: &SqlitePool) -> Vec { sqlx::query_as!( Registration, " @@ -58,81 +126,34 @@ SELECT (SELECT name FROM user WHERE user_trip.user_id = user.id) as name, (SELECT created_at FROM user WHERE user_trip.user_id = user.id) as registered_at FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)", - trip_id + self.id ) .fetch_all(db) .await - .unwrap() //TODO: fixme - } - - /// Cox decides to create own trip. - pub async fn new_own(db: &SqlitePool, cox_id: i64, trip_details_id: i64) { - sqlx::query!( - "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)", - cox_id, - trip_details_id - ) - .execute(db) - .await - .unwrap(); //TODO: fixme - } - - /// Cox decides to help in a planned event. - pub async fn new_join( - db: &SqlitePool, - cox_id: i64, - planned_event_id: i64, - ) -> Result<(), CoxHelpError> { - let is_rower = sqlx::query!( - "SELECT count(*) as amount - FROM user_trip - WHERE trip_details_id = - (SELECT trip_details_id FROM planned_event WHERE id = ?) - AND user_id = ?", - planned_event_id, - cox_id - ) - .fetch_one(db) - .await - .unwrap(); - if is_rower.amount > 0 { - return Err(CoxHelpError::AlreadyRegisteredAsRower); - } - - match sqlx::query!( - "INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)", - cox_id, - planned_event_id - ) - .execute(db) - .await - { - Ok(_) => Ok(()), - Err(_) => Err(CoxHelpError::AlreadyRegisteredAsCox), - } + .unwrap() //Okay, as Trip can only be created with proper DB backing } /// Cox decides to update own trip. pub async fn update_own( db: &SqlitePool, - cox_id: i64, - trip_id: i64, + cox: &CoxUser, + trip: &Trip, max_people: i32, notes: Option, ) -> Result<(), TripUpdateError> { - if !Self::is_trip_from_user(db, cox_id, trip_id).await { + if !trip.is_trip_from_user(cox.id).await { return Err(TripUpdateError::NotYourTrip); } let trip_details = sqlx::query!( "SELECT trip_details_id as id FROM trip WHERE id = ?", - trip_id + trip.id ) .fetch_one(db) .await - .unwrap(); //TODO: fixme + .unwrap(); //Okay, as trip can only be created with proper DB backing let Some(trip_details_id) = trip_details.id else { - return Err(TripUpdateError::TripDoesNotExist); + return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove? }; sqlx::query!( @@ -143,40 +164,40 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i ) .execute(db) .await - .unwrap(); //TODO: fixme + .unwrap(); //Okay, as trip_details can only be created with proper DB backing Ok(()) } - pub async fn delete_by_planned_event_id(db: &SqlitePool, user_id: i64, planned_event_id: i64) { + pub async fn delete_by_planned_event( + db: &SqlitePool, + cox: &CoxUser, + planned_event: &PlannedEvent, + ) { sqlx::query!( "DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?", - user_id, - planned_event_id + cox.id, + planned_event.id ) .execute(db) .await - .unwrap(); //TODO: fixme + .unwrap(); //TODO: handle case where cox is not registered } - pub(crate) async fn delete( - db: &SqlitePool, - user_id: i64, - trip_id: i64, - ) -> Result<(), TripDeleteError> { - let registered_rower = Self::get_all_rower_for_id(db, trip_id).await; + pub(crate) async fn delete(&self, db: &SqlitePool, user: &User) -> Result<(), TripDeleteError> { + let registered_rower = self.get_all_rower(db).await; if registered_rower.is_empty() { return Err(TripDeleteError::SomebodyAlreadyRegistered); } - if !Self::is_trip_from_user(db, user_id, trip_id).await { + if !self.is_trip_from_user(user.id).await { return Err(TripDeleteError::NotYourTrip); } sqlx::query!( "DELETE FROM trip WHERE cox_id = ? AND id = ?", - user_id, - trip_id + user.id, + self.id ) .execute(db) .await @@ -185,26 +206,156 @@ FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE i Ok(()) } - async fn is_trip_from_user(db: &SqlitePool, user_id: i64, trip_id: i64) -> bool { - let trip_cox = sqlx::query!("SELECT cox_id FROM trip WHERE id = ?", trip_id) - .fetch_one(db) - .await - .unwrap(); //TODO: fixme - trip_cox.cox_id == user_id + async fn is_trip_from_user(&self, user_id: i64) -> bool { + self.cox_id == user_id } } +#[derive(Debug)] pub enum CoxHelpError { AlreadyRegisteredAsRower, AlreadyRegisteredAsCox, } +#[derive(Debug)] pub enum TripDeleteError { SomebodyAlreadyRegistered, NotYourTrip, } +#[derive(Debug)] pub enum TripUpdateError { NotYourTrip, - TripDoesNotExist, + TripDetailsDoesNotExist, +} + +#[cfg(test)] +mod test { + use crate::{ + model::{ + planned_event::PlannedEvent, + tripdetails::TripDetails, + user::{CoxUser, User}, + }, + testdb, + }; + + use chrono::NaiveDate; + use sqlx::SqlitePool; + + use super::Trip; + + #[sqlx::test] + fn test_new_own() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); + + Trip::new_own(&pool, &cox, trip_details).await; + + assert!(Trip::find_by_id(&pool, 1).await.is_some()); + } + + #[sqlx::test] + fn test_get_day_cox_trip() { + let pool = testdb!(); + + let res = Trip::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()).await; + assert_eq!(res.len(), 1); + } + + #[sqlx::test] + fn test_new_succ_join() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); + + assert!(Trip::new_join(&pool, &cox, &planned_event).await.is_ok()); + } + + #[sqlx::test] + fn test_new_failed_join_already_cox() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); + + Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); + assert!(Trip::new_join(&pool, &cox, &planned_event).await.is_err()); + } + + #[sqlx::test] + fn test_succ_update_own() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let trip = Trip::find_by_id(&pool, 1).await.unwrap(); + + assert!(Trip::update_own(&pool, &cox, &trip, 10, None).await.is_ok()); + + let trip = Trip::find_by_id(&pool, 1).await.unwrap(); + assert_eq!(trip.max_people, 10); + } + + #[sqlx::test] + fn test_fail_update_own_not_your_trip() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox2".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let trip = Trip::find_by_id(&pool, 1).await.unwrap(); + + assert!(Trip::update_own(&pool, &cox, &trip, 10, None) + .await + .is_err()); + assert_eq!(trip.max_people, 1); + } + + #[sqlx::test] + fn test_succ_delete_by_planned_event() { + let pool = testdb!(); + + let cox: CoxUser = User::find_by_name(&pool, "cox".into()) + .await + .unwrap() + .try_into() + .unwrap(); + + let planned_event = PlannedEvent::find_by_id(&pool, 1).await.unwrap(); + + Trip::new_join(&pool, &cox, &planned_event).await.unwrap(); + + //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; + assert!(Trip::find_by_id(&pool, 2).await.is_none()); + } + + //TODO: create tests for delete() function } diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs index 912e195..663620d 100644 --- a/src/model/tripdetails.rs +++ b/src/model/tripdetails.rs @@ -95,11 +95,11 @@ mod test { assert_eq!( TripDetails::create(&pool, "10:00".into(), 2, "1970-01-01".into(), None).await, - 2, + 3, ); assert_eq!( TripDetails::create(&pool, "10:00".into(), 2, "1970-01-01".into(), None).await, - 3, + 4, ); } diff --git a/src/model/user.rs b/src/model/user.rs index 2f0bfeb..90253cb 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -45,7 +45,7 @@ WHERE id like ? .ok() } - async fn find_by_name(db: &SqlitePool, name: String) -> Option { + pub async fn find_by_name(db: &SqlitePool, name: String) -> Option { sqlx::query_as!( User, " diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index cefd669..d575b27 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -84,3 +84,18 @@ pub enum UserTripError { EventAlreadyFull, TripDetailsNotFound, } + +#[cfg(test)] +mod test { + use crate::testdb; + + //use super::User; + //use sqlx::SqlitePool; + + //#[sqlx::test] + //fn test_find_correct_id() { + // let pool = testdb!(); + // let user = User::find_by_id(&pool, 1).await.unwrap(); + // assert_eq!(user.id, 1); + //} +} diff --git a/src/rest/cox.rs b/src/rest/cox.rs index 24f584e..5e379ea 100644 --- a/src/rest/cox.rs +++ b/src/rest/cox.rs @@ -8,6 +8,7 @@ use sqlx::SqlitePool; use crate::model::{ log::Log, + planned_event::PlannedEvent, trip::{CoxHelpError, Trip, TripDeleteError, TripUpdateError}, tripdetails::TripDetails, user::CoxUser, @@ -33,10 +34,11 @@ async fn create(db: &State, data: Form, cox: CoxUser) - data.notes.clone(), ) .await; + let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just + //created + Trip::new_own(db, &cox, trip_details).await; //TODO: fix clone() - Trip::new_own(db, cox.id, trip_details_id).await; - Log::create( db, format!( @@ -65,72 +67,88 @@ async fn update( trip_id: i64, cox: CoxUser, ) -> Flash { - match Trip::update_own(db, cox.id, trip_id, data.max_people, data.notes.clone()).await { - Ok(_) => Flash::success(Redirect::to("/"), "Ausfahrt erfolgreich aktualisiert."), - Err(TripUpdateError::NotYourTrip) => { - Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") - } - Err(TripUpdateError::TripDoesNotExist) => { - Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") + if let Some(trip) = Trip::find_by_id(db, trip_id).await { + match Trip::update_own(db, &cox, &trip, data.max_people, data.notes.clone()).await { + Ok(_) => Flash::success(Redirect::to("/"), "Ausfahrt erfolgreich aktualisiert."), + Err(TripUpdateError::NotYourTrip) => { + Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") + } + Err(TripUpdateError::TripDetailsDoesNotExist) => { + Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") + } } + } else { + Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") } } #[get("/join/")] async fn join(db: &State, planned_event_id: i64, cox: CoxUser) -> Flash { - match Trip::new_join(db, cox.id, planned_event_id).await { - Ok(_) => { - Log::create( - db, - format!( - "Cox {} helps at planned_event.id={}", - cox.name, planned_event_id, - ), - ) - .await; - Flash::success(Redirect::to("/"), "Danke für's helfen!") + if let Some(planned_event) = PlannedEvent::find_by_id(db, planned_event_id).await { + match Trip::new_join(db, &cox, &planned_event).await { + Ok(_) => { + Log::create( + db, + format!( + "Cox {} helps at planned_event.id={}", + cox.name, planned_event_id, + ), + ) + .await; + Flash::success(Redirect::to("/"), "Danke für's helfen!") + } + Err(CoxHelpError::AlreadyRegisteredAsCox) => { + Flash::error(Redirect::to("/"), "Du hilfst bereits aus!") + } + Err(CoxHelpError::AlreadyRegisteredAsRower) => Flash::error( + Redirect::to("/"), + "Du hast dich bereits als Ruderer angemeldet!", + ), } - Err(CoxHelpError::AlreadyRegisteredAsCox) => { - Flash::error(Redirect::to("/"), "Du hilfst bereits aus!") - } - Err(CoxHelpError::AlreadyRegisteredAsRower) => Flash::error( - Redirect::to("/"), - "Du hast dich bereits als Ruderer angemeldet!", - ), + } else { + Flash::error(Redirect::to("/"), "Event gibt's nicht") } } #[get("/remove/trip/")] async fn remove_trip(db: &State, trip_id: i64, cox: CoxUser) -> Flash { - match Trip::delete(db, cox.id, trip_id).await { - Ok(_) => { - Log::create(db, format!("Cox {} deleted trip.id={}", cox.name, trip_id)).await; - Flash::success(Redirect::to("/"), "Erfolgreich gelöscht!") - } - Err(TripDeleteError::SomebodyAlreadyRegistered) => Flash::error( - Redirect::to("/"), - "Ausfahrt kann nicht gelöscht werden, da bereits jemand registriert ist!", - ), - Err(TripDeleteError::NotYourTrip) => { - Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") - } + let trip = Trip::find_by_id(db, trip_id).await; + match trip { + None => Flash::error(Redirect::to("/"), "Trip gibt's nicht!"), + Some(trip) => match trip.delete(db, &cox).await { + Ok(_) => { + Log::create(db, format!("Cox {} deleted trip.id={}", cox.name, trip_id)).await; + Flash::success(Redirect::to("/"), "Erfolgreich gelöscht!") + } + Err(TripDeleteError::SomebodyAlreadyRegistered) => Flash::error( + Redirect::to("/"), + "Ausfahrt kann nicht gelöscht werden, da bereits jemand registriert ist!", + ), + Err(TripDeleteError::NotYourTrip) => { + Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") + } + }, } } #[get("/remove/")] async fn remove(db: &State, planned_event_id: i64, cox: CoxUser) -> Flash { - Trip::delete_by_planned_event_id(db, cox.id, planned_event_id).await; + if let Some(planned_event) = PlannedEvent::find_by_id(db, planned_event_id).await { + 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; + Log::create( + db, + format!( + "Cox {} deleted registration for planned_event.id={}", + cox.name, planned_event_id + ), + ) + .await; - Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") + } else { + Flash::error(Redirect::to("/"), "Planned_event does not exist.") + } } pub fn routes() -> Vec {