627 lines
17 KiB
Rust
627 lines
17 KiB
Rust
use chrono::NaiveDate;
|
|
use serde::Serialize;
|
|
use sqlx::SqlitePool;
|
|
|
|
use super::{
|
|
notification::Notification,
|
|
planned_event::{PlannedEvent, Registration},
|
|
tripdetails::TripDetails,
|
|
triptype::TripType,
|
|
user::{CoxUser, User},
|
|
};
|
|
|
|
#[derive(Serialize, Clone, Debug)]
|
|
pub struct Trip {
|
|
id: i64,
|
|
pub cox_id: i64,
|
|
cox_name: String,
|
|
trip_details_id: Option<i64>,
|
|
planned_starting_time: String,
|
|
pub max_people: i64,
|
|
pub day: String,
|
|
pub notes: Option<String>,
|
|
pub allow_guests: bool,
|
|
trip_type_id: Option<i64>,
|
|
always_show: bool,
|
|
is_locked: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct TripWithUserAndType {
|
|
#[serde(flatten)]
|
|
pub trip: Trip,
|
|
pub rower: Vec<Registration>,
|
|
trip_type: Option<TripType>,
|
|
}
|
|
|
|
pub struct TripUpdate<'a> {
|
|
pub cox: &'a CoxUser,
|
|
pub trip: &'a Trip,
|
|
pub max_people: i32,
|
|
pub notes: Option<&'a str>,
|
|
pub trip_type: Option<i64>, //TODO: Move to `TripType`
|
|
pub always_show: bool,
|
|
pub is_locked: bool,
|
|
}
|
|
|
|
impl TripWithUserAndType {
|
|
pub async fn from(db: &SqlitePool, trip: Trip) -> Self {
|
|
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;
|
|
}
|
|
Self {
|
|
rower: Registration::all_rower(db, trip.trip_details_id.unwrap()).await,
|
|
trip,
|
|
trip_type,
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
let same_starting_datetime = TripDetails::find_by_startingdatetime(
|
|
db,
|
|
trip_details.day,
|
|
trip_details.planned_starting_time,
|
|
)
|
|
.await;
|
|
if same_starting_datetime.len() > 1 {
|
|
for notify in same_starting_datetime {
|
|
if notify.id != trip_details.id {
|
|
// notify everyone except oneself
|
|
if let Some(trip) = Trip::find_by_trip_details(db, notify.id).await {
|
|
let user = User::find_by_id(db, trip.cox_id as i32).await.unwrap();
|
|
Notification::create(
|
|
db,
|
|
&user,
|
|
&format!(
|
|
"{} hat eine Ausfahrt zur selben Zeit ({} um {}) wie du erstellt",
|
|
cox.user.name, trip.day, trip.planned_starting_time
|
|
),
|
|
"Neue Ausfahrt zur selben Zeit",
|
|
None,
|
|
None,
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn find_by_trip_details(db: &SqlitePool, tripdetails_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, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
|
|
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_details.id=?
|
|
",
|
|
tripdetails_id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.ok()
|
|
}
|
|
|
|
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, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
|
|
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);
|
|
}
|
|
|
|
if planned_event.trip_details(db).await.is_locked {
|
|
return Err(CoxHelpError::DetailsLocked);
|
|
}
|
|
|
|
if planned_event.max_people == 0 {
|
|
return Err(CoxHelpError::CanceledEvent);
|
|
}
|
|
|
|
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, trip_details.notes, allow_guests, trip_type_id, always_show, is_locked
|
|
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 {
|
|
ret.push(TripWithUserAndType::from(db, trip).await);
|
|
}
|
|
ret
|
|
}
|
|
|
|
/// Cox decides to update own trip.
|
|
pub async fn update_own(
|
|
db: &SqlitePool,
|
|
update: &TripUpdate<'_>,
|
|
) -> Result<(), TripUpdateError> {
|
|
if !update.trip.is_trip_from_user(update.cox.id) {
|
|
return Err(TripUpdateError::NotYourTrip);
|
|
}
|
|
|
|
let Some(trip_details_id) = update.trip.trip_details_id else {
|
|
return Err(TripUpdateError::TripDetailsDoesNotExist); //TODO: Remove?
|
|
};
|
|
|
|
let tripdetails = TripDetails::find_by_id(db, trip_details_id).await.unwrap();
|
|
let was_already_cancelled = tripdetails.max_people == 0;
|
|
|
|
sqlx::query!(
|
|
"UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?",
|
|
update.max_people,
|
|
update.notes,
|
|
update.trip_type,
|
|
update.always_show,
|
|
update.is_locked,
|
|
trip_details_id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap(); //Okay, as trip_details can only be created with proper DB backing
|
|
|
|
if update.max_people == 0 && !was_already_cancelled {
|
|
let rowers = TripWithUserAndType::from(db, update.trip.clone())
|
|
.await
|
|
.rower;
|
|
for user in rowers {
|
|
if let Some(user) = User::find_by_name(db, &user.name).await {
|
|
let notes = match update.notes {
|
|
Some(n) if !n.is_empty() => n,
|
|
_ => ".",
|
|
};
|
|
|
|
Notification::create(
|
|
db,
|
|
&user,
|
|
&format!(
|
|
"Die Ausfahrt von {} am {} um {} wurde abgesagt{}",
|
|
update.cox.user.name,
|
|
update.trip.day,
|
|
update.trip.planned_starting_time,
|
|
notes
|
|
),
|
|
"Absage Ausfahrt",
|
|
None,
|
|
Some(&format!(
|
|
"remove_user_trip_with_trip_details_id:{}",
|
|
trip_details_id
|
|
)),
|
|
)
|
|
.await;
|
|
}
|
|
}
|
|
} else {
|
|
Notification::delete_by_action(
|
|
db,
|
|
&format!("remove_user_trip_with_trip_details_id:{}", trip_details_id),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
if update.max_people > 0 && was_already_cancelled {
|
|
Notification::delete_by_action(
|
|
db,
|
|
&format!("remove_user_trip_with_trip_details_id:{}", trip_details_id),
|
|
)
|
|
.await;
|
|
}
|
|
|
|
let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap();
|
|
trip_details.check_free_spaces(db).await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn trip_details(&self, db: &SqlitePool) -> Option<TripDetails> {
|
|
if let Some(trip_details_id) = self.trip_type_id {
|
|
return TripDetails::find_by_id(db, trip_details_id).await;
|
|
}
|
|
None
|
|
}
|
|
|
|
pub async fn delete_by_planned_event(
|
|
db: &SqlitePool,
|
|
cox: &CoxUser,
|
|
planned_event: &PlannedEvent,
|
|
) -> Result<(), TripHelpDeleteError> {
|
|
if planned_event.trip_details(db).await.is_locked {
|
|
return Err(TripHelpDeleteError::DetailsLocked);
|
|
}
|
|
|
|
let affected_rows = sqlx::query!(
|
|
"DELETE FROM trip WHERE cox_id = ? AND planned_event_id = ?",
|
|
cox.id,
|
|
planned_event.id
|
|
)
|
|
.execute(db)
|
|
.await
|
|
.unwrap()
|
|
.rows_affected();
|
|
|
|
if affected_rows == 0 {
|
|
return Err(TripHelpDeleteError::CoxNotHelping);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) async fn delete(
|
|
&self,
|
|
db: &SqlitePool,
|
|
user: &CoxUser,
|
|
) -> Result<(), TripDeleteError> {
|
|
let registered_rower = Registration::all_rower(db, self.trip_details_id.unwrap()).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
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum CoxHelpError {
|
|
AlreadyRegisteredAsRower,
|
|
AlreadyRegisteredAsCox,
|
|
DetailsLocked,
|
|
CanceledEvent,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum TripHelpDeleteError {
|
|
DetailsLocked,
|
|
CoxNotHelping,
|
|
}
|
|
|
|
#[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::{self, 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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox2".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox2".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
|
|
|
|
let update = trip::TripUpdate {
|
|
cox: &cox,
|
|
trip: &trip,
|
|
max_people: 10,
|
|
notes: None,
|
|
trip_type: None,
|
|
always_show: false,
|
|
is_locked: false,
|
|
};
|
|
|
|
assert!(Trip::update_own(&pool, &update).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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
|
|
|
|
let update = trip::TripUpdate {
|
|
cox: &cox,
|
|
trip: &trip,
|
|
max_people: 10,
|
|
notes: None,
|
|
trip_type: Some(1),
|
|
always_show: false,
|
|
is_locked: false,
|
|
};
|
|
assert!(Trip::update_own(&pool, &update).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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox2".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
let trip = Trip::find_by_id(&pool, 1).await.unwrap();
|
|
|
|
let update = trip::TripUpdate {
|
|
cox: &cox,
|
|
trip: &trip,
|
|
max_people: 10,
|
|
notes: None,
|
|
trip_type: None,
|
|
always_show: false,
|
|
is_locked: false,
|
|
};
|
|
assert!(Trip::update_own(&pool, &update).await.is_err());
|
|
assert_eq!(trip.max_people, 1);
|
|
}
|
|
|
|
#[sqlx::test]
|
|
fn test_succ_delete_by_planned_event() {
|
|
let pool = testdb!();
|
|
|
|
let cox = CoxUser::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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
|
|
.unwrap();
|
|
assert!(Trip::find_by_id(&pool, 2).await.is_none());
|
|
}
|
|
|
|
#[sqlx::test]
|
|
fn test_succ_delete() {
|
|
let pool = testdb!();
|
|
|
|
let cox = CoxUser::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox2".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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::new(
|
|
&pool,
|
|
User::find_by_name(&pool, "cox".into()).await.unwrap(),
|
|
)
|
|
.await
|
|
.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, None)
|
|
.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);
|
|
}
|
|
}
|