455 lines
12 KiB
Rust
455 lines
12 KiB
Rust
use chrono::NaiveDate;
|
|
use serde::Serialize;
|
|
use sqlx::SqlitePool;
|
|
|
|
use super::{
|
|
planned_event::{PlannedEvent, Registration},
|
|
tripdetails::TripDetails,
|
|
triptype::TripType,
|
|
user::CoxUser,
|
|
};
|
|
|
|
#[derive(Serialize, Clone, Debug)]
|
|
pub struct Trip {
|
|
id: i64,
|
|
cox_id: i64,
|
|
cox_name: String,
|
|
trip_details_id: Option<i64>,
|
|
planned_starting_time: String,
|
|
max_people: i64,
|
|
day: String,
|
|
notes: Option<String>,
|
|
pub allow_guests: bool,
|
|
trip_type_id: Option<i64>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct TripWithUserAndType {
|
|
#[serde(flatten)]
|
|
pub trip: Trip,
|
|
rower: Vec<Registration>,
|
|
trip_type: Option<TripType>,
|
|
}
|
|
|
|
impl Trip {
|
|
/// Cox decides to create own trip.
|
|
pub async fn new_own(db: &SqlitePool, cox: &CoxUser, trip_details: TripDetails) {
|
|
let _ = sqlx::query!(
|
|
"INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)",
|
|
cox.id,
|
|
trip_details.id
|
|
)
|
|
.execute(db)
|
|
.await;
|
|
}
|
|
|
|
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
|
|
sqlx::query_as!(
|
|
Self,
|
|
"
|
|
SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id
|
|
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> {
|
|
if planned_event.is_rower_registered(db, cox).await {
|
|
return Err(CoxHelpError::AlreadyRegisteredAsRower);
|
|
}
|
|
|
|
match sqlx::query!(
|
|
"INSERT INTO trip (cox_id, planned_event_id) VALUES(?, ?)",
|
|
cox.id,
|
|
planned_event.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
{
|
|
Ok(_) => Ok(()),
|
|
Err(_) => Err(CoxHelpError::AlreadyRegisteredAsCox),
|
|
}
|
|
}
|
|
|
|
pub async fn get_for_day(db: &SqlitePool, day: NaiveDate) -> Vec<TripWithUserAndType> {
|
|
let day = format!("{day}");
|
|
let trips = sqlx::query_as!(
|
|
Trip,
|
|
"
|
|
SELECT trip.id, cox_id, user.name as cox_name, trip_details_id, planned_starting_time, max_people, day, notes, allow_guests, trip_type_id
|
|
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
|
|
.unwrap(); //Okay, as Trip can only be created with proper DB backing
|
|
|
|
let mut ret = Vec::new();
|
|
for trip in trips {
|
|
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 {
|
|
rower: trip.get_all_rower(db).await,
|
|
trip,
|
|
trip_type,
|
|
});
|
|
}
|
|
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,
|
|
(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 = ?)",
|
|
self.id
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.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: &CoxUser,
|
|
trip: &Trip,
|
|
max_people: i32,
|
|
notes: Option<&str>,
|
|
trip_type: Option<i64>, //TODO: Move to `TripType`
|
|
) -> Result<(), TripUpdateError> {
|
|
if !trip.is_trip_from_user(cox.id) {
|
|
return Err(TripUpdateError::NotYourTrip);
|
|
}
|
|
|
|
let trip_details = sqlx::query!(
|
|
"SELECT trip_details_id as id FROM trip WHERE id = ?",
|
|
trip.id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.unwrap(); //Okay, as trip can only be created with proper DB backing
|
|
let Some(trip_details_id) = trip_details.id else {
|
|
return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove?
|
|
};
|
|
|
|
sqlx::query!(
|
|
"UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ? WHERE id = ?",
|
|
max_people,
|
|
notes,
|
|
trip_type,
|
|
trip_details_id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap(); //Okay, as trip_details can only be created with proper DB backing
|
|
|
|
Ok(())
|
|
}
|
|
|
|
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 = ?",
|
|
cox.id,
|
|
planned_event.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap(); //TODO: handle case where cox is not registered
|
|
}
|
|
|
|
pub(crate) async fn delete(
|
|
&self,
|
|
db: &SqlitePool,
|
|
user: &CoxUser,
|
|
) -> 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(user.id) {
|
|
return Err(TripDeleteError::NotYourTrip);
|
|
}
|
|
|
|
sqlx::query!(
|
|
"DELETE FROM trip WHERE cox_id = ? AND id = ?",
|
|
user.id,
|
|
self.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap(); //TODO: fixme
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn is_trip_from_user(&self, user_id: i64) -> bool {
|
|
self.cox_id == user_id
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum CoxHelpError {
|
|
AlreadyRegisteredAsRower,
|
|
AlreadyRegisteredAsCox,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum TripDeleteError {
|
|
SomebodyAlreadyRegistered,
|
|
NotYourTrip,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TripUpdateError {
|
|
NotYourTrip,
|
|
TripDetailsDoesNotExist,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::{
|
|
model::{
|
|
planned_event::PlannedEvent,
|
|
trip::TripDeleteError,
|
|
tripdetails::TripDetails,
|
|
user::{CoxUser, User},
|
|
usertrip::UserTrip,
|
|
},
|
|
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, None)
|
|
.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();
|
|
|
|
assert!(Trip::update_own(&pool, &cox, &trip, 10, None, Some(1))
|
|
.await
|
|
.is_ok());
|
|
|
|
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
|
|
assert_eq!(trip.max_people, 10);
|
|
assert_eq!(trip.trip_type_id, Some(1));
|
|
}
|
|
|
|
#[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, 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());
|
|
}
|
|
|
|
#[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());
|
|
}
|
|
|
|
#[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!();
|
|
|
|
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();
|
|
|
|
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();
|
|
|
|
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);
|
|
}
|
|
}
|