rowt/src/model/trip.rs

471 lines
13 KiB
Rust
Raw Normal View History

2023-04-04 15:16:21 +02:00
use chrono::NaiveDate;
use serde::Serialize;
use sqlx::SqlitePool;
2023-04-26 16:54:53 +02:00
use super::{
planned_event::{PlannedEvent, Registration},
tripdetails::TripDetails,
2023-04-28 21:19:51 +02:00
triptype::TripType,
2023-04-28 18:08:01 +02:00
user::CoxUser,
2023-04-26 16:54:53 +02:00
};
2023-04-05 17:25:22 +02:00
2023-04-26 16:54:53 +02:00
#[derive(Serialize, Clone, Debug)]
2023-04-04 15:16:21 +02:00
pub struct Trip {
id: i64,
cox_id: i64,
cox_name: String,
trip_details_id: Option<i64>,
planned_starting_time: String,
2023-07-23 14:21:27 +02:00
pub max_people: i64,
2023-04-04 15:16:21 +02:00
day: String,
2023-07-23 14:21:27 +02:00
pub notes: Option<String>,
pub allow_guests: bool,
2023-04-28 21:19:51 +02:00
trip_type_id: Option<i64>,
2023-07-23 19:45:48 +02:00
always_show: bool,
2023-04-04 15:16:21 +02:00
}
#[derive(Serialize)]
2023-04-28 21:19:51 +02:00
pub struct TripWithUserAndType {
2023-04-04 15:16:21 +02:00
#[serde(flatten)]
pub trip: Trip,
2023-04-05 17:25:22 +02:00
rower: Vec<Registration>,
2023-04-28 21:19:51 +02:00
trip_type: Option<TripType>,
2023-04-04 15:16:21 +02:00
}
impl Trip {
2023-04-26 16:54:53 +02:00
/// Cox decides to create own trip.
pub async fn new_own(db: &SqlitePool, cox: &CoxUser, trip_details: TripDetails) {
2023-05-24 13:11:47 +02:00
let _ = sqlx::query!(
2023-04-26 16:54:53 +02:00
"INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)",
cox.id,
trip_details.id
2023-04-04 15:16:21 +02:00
)
2023-04-26 16:54:53 +02:00
.execute(db)
2023-05-24 13:11:47 +02:00
.await;
2023-04-04 15:16:21 +02:00
}
2023-04-26 16:54:53 +02:00
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
2023-04-05 17:25:22 +02:00
sqlx::query_as!(
2023-04-26 16:54:53 +02:00
Self,
2023-04-04 15:16:21 +02:00
"
2023-07-23 19:45:48 +02:00
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
2023-04-26 16:54:53 +02:00
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
2023-04-04 15:16:21 +02:00
)
2023-04-26 16:54:53 +02:00
.fetch_one(db)
2023-04-04 15:16:21 +02:00
.await
2023-04-26 16:54:53 +02:00
.ok()
2023-04-04 15:16:21 +02:00
}
/// Cox decides to help in a planned event.
pub async fn new_join(
db: &SqlitePool,
2023-04-26 16:54:53 +02:00
cox: &CoxUser,
planned_event: &PlannedEvent,
) -> Result<(), CoxHelpError> {
2023-05-30 14:36:23 +02:00
if planned_event.is_rower_registered(db, cox).await {
return Err(CoxHelpError::AlreadyRegisteredAsRower);
}
match sqlx::query!(
2023-04-04 15:16:21 +02:00
"INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)",
2023-04-26 16:54:53 +02:00
cox.id,
planned_event.id
2023-04-04 15:16:21 +02:00
)
.execute(db)
.await
{
Ok(_) => Ok(()),
Err(_) => Err(CoxHelpError::AlreadyRegisteredAsCox),
}
2023-04-04 15:16:21 +02:00
}
2023-04-28 21:19:51 +02:00
pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<TripWithUserAndType> {
2023-04-26 16:54:53 +02:00
let day = format!("{day}");
let trips = sqlx::query_as!(
Trip,
"
2023-07-23 19:45:48 +02:00
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
2023-04-26 16:54:53 +02:00
FROM trip
INNER JOIN trip_details ON trip.trip_details_id = trip_details.id
INNER JOIN user ON trip.cox_id = user.id
WHERE day=?
",
day
)
.fetch_all(db)
.await
2023-05-24 13:11:47 +02:00
.unwrap(); //Okay, as Trip can only be created with proper DB backing
2023-05-03 13:32:23 +02:00
2023-04-26 16:54:53 +02:00
let mut ret = Vec::new();
for trip in trips {
2023-04-28 21:19:51 +02:00
let mut trip_type = None;
if let Some(trip_type_id) = trip.trip_type_id {
trip_type = TripType::find_by_id(db, trip_type_id).await;
}
ret.push(TripWithUserAndType {
2023-04-26 16:54:53 +02:00
rower: trip.get_all_rower(db).await,
2023-05-24 12:16:13 +02:00
trip,
trip_type,
2023-04-26 16:54:53 +02:00
});
}
ret
}
async fn get_all_rower(&self, db: &SqlitePool) -> Vec<Registration> {
sqlx::query_as!(
Registration,
"
SELECT
(SELECT name FROM user WHERE user_trip.user_id = user.id) as name,
2023-04-28 19:08:17 +02:00
(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
2023-04-26 16:54:53 +02:00
FROM user_trip WHERE trip_details_id = (SELECT trip_details_id FROM trip WHERE id = ?)",
self.id
)
.fetch_all(db)
.await
.unwrap() //Okay, as Trip can only be created with proper DB backing
}
2023-04-07 11:54:56 +02:00
/// Cox decides to update own trip.
pub async fn update_own(
db: &SqlitePool,
2023-04-26 16:54:53 +02:00
cox: &CoxUser,
trip: &Trip,
2023-04-07 11:54:56 +02:00
max_people: i32,
2023-05-24 12:11:55 +02:00
notes: Option<&str>,
2023-05-03 16:51:37 +02:00
trip_type: Option<i64>, //TODO: Move to `TripType`
2023-07-23 19:45:48 +02:00
always_show: bool,
2023-04-07 11:54:56 +02:00
) -> Result<(), TripUpdateError> {
2023-05-30 14:47:44 +02:00
if !trip.is_trip_from_user(cox.id) {
2023-04-07 11:54:56 +02:00
return Err(TripUpdateError::NotYourTrip);
}
let trip_details = sqlx::query!(
"SELECT trip_details_id as id FROM trip WHERE id = ?",
2023-04-26 16:54:53 +02:00
trip.id
2023-04-07 11:54:56 +02:00
)
.fetch_one(db)
.await
2023-04-26 16:54:53 +02:00
.unwrap(); //Okay, as trip can only be created with proper DB backing
2023-04-26 12:52:19 +02:00
let Some(trip_details_id) = trip_details.id else {
return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove?
2023-04-07 11:54:56 +02:00
};
sqlx::query!(
2023-07-23 19:45:48 +02:00
"UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ? WHERE id = ?",
2023-04-07 11:54:56 +02:00
max_people,
notes,
2023-05-03 16:51:37 +02:00
trip_type,
2023-07-23 19:45:48 +02:00
always_show,
2023-04-07 11:54:56 +02:00
trip_details_id
)
.execute(db)
.await
2023-04-26 16:54:53 +02:00
.unwrap(); //Okay, as trip_details can only be created with proper DB backing
2023-04-07 11:54:56 +02:00
Ok(())
}
2023-04-26 16:54:53 +02:00
pub async fn delete_by_planned_event(
db: &SqlitePool,
cox: &CoxUser,
planned_event: &PlannedEvent,
) -> bool {
2023-04-26 12:52:19 +02:00
sqlx::query!(
2023-04-04 15:16:21 +02:00
"DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?",
2023-04-26 16:54:53 +02:00
cox.id,
planned_event.id
2023-04-04 15:16:21 +02:00
)
.execute(db)
.await
.unwrap()
.rows_affected()
> 0
2023-04-04 15:16:21 +02:00
}
2023-04-28 18:08:01 +02:00
pub(crate) async fn delete(
&self,
db: &SqlitePool,
user: &CoxUser,
) -> Result<(), TripDeleteError> {
2023-04-26 16:54:53 +02:00
let registered_rower = self.get_all_rower(db).await;
2023-04-28 18:08:01 +02:00
if !registered_rower.is_empty() {
return Err(TripDeleteError::SomebodyAlreadyRegistered);
}
2023-05-30 14:47:44 +02:00
if !self.is_trip_from_user(user.id) {
return Err(TripDeleteError::NotYourTrip);
}
sqlx::query!(
"DELETE FROM trip WHERE cox_id = ? AND id = ?",
2023-04-26 16:54:53 +02:00
user.id,
self.id
)
.execute(db)
.await
.unwrap(); //TODO: fixme
Ok(())
}
2023-04-07 11:54:56 +02:00
2023-05-30 14:47:44 +02:00
fn is_trip_from_user(&self, user_id: i64) -> bool {
2023-04-26 16:54:53 +02:00
self.cox_id == user_id
2023-04-07 11:54:56 +02:00
}
2023-07-23 19:45:48 +02:00
pub(crate) async fn get_pinned_for_day(
db: &sqlx::Pool<sqlx::Sqlite>,
day: NaiveDate,
) -> Vec<TripWithUserAndType> {
let mut trips = Self::get_for_day(db, day).await;
trips.retain(|e| e.trip.always_show);
trips
}
}
2023-04-04 15:16:21 +02:00
2023-04-26 16:54:53 +02:00
#[derive(Debug)]
pub enum CoxHelpError {
AlreadyRegisteredAsRower,
AlreadyRegisteredAsCox,
2023-04-04 15:16:21 +02:00
}
2023-04-28 18:18:00 +02:00
#[derive(Debug, PartialEq)]
pub enum TripDeleteError {
SomebodyAlreadyRegistered,
NotYourTrip,
}
2023-04-07 11:54:56 +02:00
2023-04-26 16:54:53 +02:00
#[derive(Debug)]
2023-04-07 11:54:56 +02:00
pub enum TripUpdateError {
NotYourTrip,
2023-04-26 16:54:53 +02:00
TripDetailsDoesNotExist,
}
#[cfg(test)]
mod test {
use crate::{
model::{
planned_event::PlannedEvent,
2023-04-28 18:18:00 +02:00
trip::TripDeleteError,
2023-04-26 16:54:53 +02:00
tripdetails::TripDetails,
user::{CoxUser, User},
2023-04-28 18:18:00 +02:00
usertrip::UserTrip,
2023-04-26 16:54:53 +02:00
},
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();
2023-07-23 19:45:48 +02:00
assert!(Trip::update_own(&pool, &cox, &trip, 10, None, None, false)
2023-05-03 16:53:36 +02:00
.await
.is_ok());
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
assert_eq!(trip.max_people, 10);
}
#[sqlx::test]
fn test_succ_update_own_with_triptype() {
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();
2023-07-23 19:45:48 +02:00
assert!(
Trip::update_own(&pool, &cox, &trip, 10, None, Some(1), false)
.await
.is_ok()
);
2023-04-26 16:54:53 +02:00
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
assert_eq!(trip.max_people, 10);
2023-05-03 16:53:36 +02:00
assert_eq!(trip.trip_type_id, Some(1));
2023-04-26 16:54:53 +02:00
}
#[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();
2023-07-23 19:45:48 +02:00
assert!(Trip::update_own(&pool, &cox, &trip, 10, None, None, false)
2023-04-26 16:54:53 +02:00
.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());
}
2023-04-28 18:08:01 +02:00
#[sqlx::test]
fn test_succ_delete() {
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();
trip.delete(&pool, &cox).await.unwrap();
assert!(Trip::find_by_id(&pool, 1).await.is_none());
}
2023-04-28 18:18:00 +02:00
#[sqlx::test]
fn test_fail_delete_diff_cox() {
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();
let result = trip
.delete(&pool, &cox)
.await
.expect_err("It should not be possible to delete trips from others");
let expected = TripDeleteError::NotYourTrip;
assert_eq!(result, expected);
}
#[sqlx::test]
fn test_fail_delete_someone_registered() {
let pool = testdb!();
2023-04-28 19:08:17 +02:00
let cox: CoxUser = User::find_by_name(&pool, "cox".into())
2023-04-28 18:18:00 +02:00
.await
.unwrap()
.try_into()
.unwrap();
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
2023-04-28 19:08:17 +02:00
let trip_details = TripDetails::find_by_id(&pool, trip.trip_details_id.unwrap())
.await
.unwrap();
let user = User::find_by_name(&pool, "rower".into()).await.unwrap();
UserTrip::create(&pool, &user, &trip_details).await.unwrap();
2023-04-28 18:18:00 +02:00
let result = trip
.delete(&pool, &cox)
.await
.expect_err("It should not be possible to delete trips if somebody already registered");
let expected = TripDeleteError::SomebodyAlreadyRegistered;
assert_eq!(result, expected);
}
2023-04-07 11:54:56 +02:00
}