diff --git a/src/model/event.rs b/src/model/event.rs index 9b0832b..23fb96c 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -1,10 +1,7 @@ use std::io::Write; -use chrono::{Duration, NaiveDate, NaiveTime}; -use ics::{ - ICalendar, - properties::{DtEnd, DtStart, Summary}, -}; +use chrono::NaiveDate; +use ics::ICalendar; use serde::Serialize; use sqlx::{FromRow, Row, SqlitePool}; @@ -142,7 +139,7 @@ WHERE planned_event.id like ? .ok() } - async fn trip_type(&self, db: &SqlitePool) -> Option { + pub(crate) async fn trip_type(&self, db: &SqlitePool) -> Option { if let Some(trip_type_id) = self.trip_type_id { TripType::find_by_id(db, trip_type_id).await } else { @@ -474,66 +471,6 @@ WHERE trip_details.id=? String::from_utf8(buf).unwrap() } - pub(crate) async fn get_vevent(self, db: &SqlitePool) -> ics::Event { - let mut vevent = ics::Event::new( - format!("event-{}@rudernlinz.at", self.id), - "19900101T180000", - ); - let time_str = self.planned_starting_time.replace(':', ""); - let formatted_time = if time_str.len() == 3 { - format!("0{}", time_str) - } else { - time_str.clone() // TODO: remove again - }; - vevent.push(DtStart::new(format!( - "{}T{}00", - self.day.replace('-', ""), - formatted_time - ))); - - let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") - .expect("Failed to parse time"); - - let long_trip = match self.trip_type(db).await { - Some(a) if a.name == "Lange Ausfahrt" => true, - _ => false, - }; - let later_time = if long_trip { - original_time + Duration::hours(6) - } else { - original_time + Duration::hours(3) - }; - if later_time > original_time { - // Check if no day-overflow - let time_three_hours_later = later_time.format("%H%M").to_string(); - vevent.push(DtEnd::new(format!( - "{}T{}00", - self.day.replace('-', ""), - time_three_hours_later - ))); - } - - let tripdetails = self.trip_details(db).await; - let mut name = String::new(); - if self.is_cancelled() { - name.push_str("ABGESAGT"); - if let Some(notes) = &tripdetails.notes { - if !notes.is_empty() { - name.push_str(&format!(" (Grund: {notes})")) - } - } - - name.push_str("! :-( "); - } - name.push_str(&format!("{} ", self.name)); - - if let Some(triptype) = tripdetails.triptype(db).await { - name.push_str(&format!("• {} ", triptype.name)) - } - vevent.push(Summary::new(name)); - vevent - } - pub async fn trip_details(&self, db: &SqlitePool) -> TripDetails { TripDetails::find_by_id(db, self.trip_details_id) .await diff --git a/src/model/personal/cal.rs b/src/model/personal/cal.rs index 82a08bd..b4e8228 100644 --- a/src/model/personal/cal.rs +++ b/src/model/personal/cal.rs @@ -1,9 +1,14 @@ use std::io::Write; -use ics::{ICalendar, components::Property}; +use ics::{ + components::Property, + properties::{DtEnd, DtStart, Summary}, + ICalendar, +}; use sqlx::SqlitePool; use crate::model::{event::Event, trip::Trip, user::User}; +use chrono::{Duration, NaiveTime}; pub(crate) async fn get_personal_cal(db: &SqlitePool, user: &User) -> String { let mut calendar = ICalendar::new("2.0", "ics-rs"); @@ -25,3 +30,125 @@ pub(crate) async fn get_personal_cal(db: &SqlitePool, user: &User) -> String { write!(&mut buf, "{}", calendar).unwrap(); String::from_utf8(buf).unwrap() } + +impl Trip { + pub(crate) async fn get_vevent<'a>(self, db: &'a SqlitePool, user: &'a User) -> ics::Event<'a> { + let mut vevent = + ics::Event::new(format!("trip-{}@rudernlinz.at", self.id), "19900101T180000"); + let time_str = self.planned_starting_time.replace(':', ""); + let formatted_time = if time_str.len() == 3 { + format!("0{}", time_str) + } else { + time_str + }; + + vevent.push(DtStart::new(format!( + "{}T{}00", + self.day.replace('-', ""), + formatted_time + ))); + + let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") + .expect("Failed to parse time"); + let long_trip = match self.trip_type(db).await { + Some(a) if a.name == "Lange Ausfahrt" => true, + _ => false, + }; + let later_time = if long_trip { + original_time + Duration::hours(6) + } else { + original_time + Duration::hours(3) + }; + if later_time > original_time { + // Check if no day-overflow + let time_three_hours_later = later_time.format("%H%M").to_string(); + vevent.push(DtEnd::new(format!( + "{}T{}00", + self.day.replace('-', ""), + time_three_hours_later + ))); + } + + let mut name = String::new(); + if self.is_cancelled() { + name.push_str("ABGESAGT"); + if let Some(notes) = &self.notes { + if !notes.is_empty() { + name.push_str(&format!(" (Grund: {notes})")) + } + } + + name.push_str("! :-( "); + } + if self.cox_id == user.id { + name.push_str("Ruderausfahrt (selber ausgeschrieben)"); + } else { + name.push_str(&format!("Ruderausfahrt mit {} ", self.cox_name)); + } + + vevent.push(Summary::new(name)); + vevent + } +} + +impl Event { + pub(crate) async fn get_vevent(self, db: &SqlitePool) -> ics::Event { + let mut vevent = ics::Event::new( + format!("event-{}@rudernlinz.at", self.id), + "19900101T180000", + ); + let time_str = self.planned_starting_time.replace(':', ""); + let formatted_time = if time_str.len() == 3 { + format!("0{}", time_str) + } else { + time_str.clone() // TODO: remove again + }; + vevent.push(DtStart::new(format!( + "{}T{}00", + self.day.replace('-', ""), + formatted_time + ))); + + let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") + .expect("Failed to parse time"); + + let long_trip = match self.trip_type(db).await { + Some(a) if a.name == "Lange Ausfahrt" => true, + _ => false, + }; + let later_time = if long_trip { + original_time + Duration::hours(6) + } else { + original_time + Duration::hours(3) + }; + if later_time > original_time { + // Check if no day-overflow + let time_three_hours_later = later_time.format("%H%M").to_string(); + vevent.push(DtEnd::new(format!( + "{}T{}00", + self.day.replace('-', ""), + time_three_hours_later + ))); + } + + let tripdetails = self.trip_details(db).await; + let mut name = String::new(); + if self.is_cancelled() { + name.push_str("ABGESAGT"); + if let Some(notes) = &tripdetails.notes { + if !notes.is_empty() { + name.push_str(&format!(" (Grund: {notes})")) + } + } + + name.push_str("! :-( "); + } + name.push_str(&format!("{} ", self.name)); + + if let Some(triptype) = tripdetails.triptype(db).await { + name.push_str(&format!("• {} ", triptype.name)) + } + vevent.push(Summary::new(name)); + vevent + } +} diff --git a/src/model/trip/create.rs b/src/model/trip/create.rs new file mode 100644 index 0000000..7e5e034 --- /dev/null +++ b/src/model/trip/create.rs @@ -0,0 +1,77 @@ +use super::Trip; +use crate::model::{ + notification::Notification, + tripdetails::TripDetails, + triptype::TripType, + user::{ErgoUser, SteeringUser, User}, +}; +use sqlx::SqlitePool; + +impl Trip { + /// Cox decides to create own trip. + pub async fn new_own(db: &SqlitePool, cox: &SteeringUser, trip_details: TripDetails) { + Self::perform_new(db, &cox.user, trip_details).await + } + + /// ErgoUser decides to create ergo 'trip'. Returns false, if trip is not a ergo-session (and + /// thus User is not allowed to create such a trip) + pub async fn new_own_ergo(db: &SqlitePool, ergo: &ErgoUser, trip_details: TripDetails) -> bool { + if let Some(typ) = trip_details.triptype(db).await { + let allowed_type = TripType::find_by_id(db, 4).await.unwrap(); + if typ == allowed_type { + Self::perform_new(db, &ergo.user, trip_details).await; + return true; + } + } + false + } + + async fn perform_new(db: &SqlitePool, user: &User, trip_details: TripDetails) { + let _ = sqlx::query!( + "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)", + user.id, + trip_details.id + ) + .execute(db) + .await; + + Self::notify_trips_same_datetime(db, trip_details, user).await; + } + + async fn notify_trips_same_datetime(db: &SqlitePool, trip_details: TripDetails, user: &User) { + let same_starting_datetime = TripDetails::find_by_startingdatetime( + db, + trip_details.day, + trip_details.planned_starting_time, + ) + .await; + + for notify in same_starting_datetime { + // don't notify oneself + if notify.id == trip_details.id { + continue; + } + + // don't notify people who have cancelled their trip + if notify.cancelled() { + continue; + } + + if let Some(trip) = Trip::find_by_trip_details(db, notify.id).await { + let user_earlier_trip = User::find_by_id(db, trip.cox_id as i32).await.unwrap(); + Notification::create( + db, + &user_earlier_trip, + &format!( + "{user} hat eine Ausfahrt zur selben Zeit ({} um {}) wie du erstellt", + trip.day, trip.planned_starting_time + ), + "Neue Ausfahrt zur selben Zeit", + None, + None, + ) + .await; + } + } + } +} diff --git a/src/model/trip.rs b/src/model/trip/mod.rs similarity index 81% rename from src/model/trip.rs rename to src/model/trip/mod.rs index 6ba6906..f6baf1d 100644 --- a/src/model/trip.rs +++ b/src/model/trip/mod.rs @@ -1,25 +1,26 @@ -use chrono::{Duration, Local, NaiveDate, NaiveTime}; -use ics::properties::{DtEnd, DtStart, Summary}; +use chrono::{Local, NaiveDate}; use serde::Serialize; use sqlx::SqlitePool; +mod create; + use super::{ event::{Event, Registration}, log::Log, notification::Notification, tripdetails::TripDetails, triptype::TripType, - user::{ErgoUser, SteeringUser, User}, + user::{SteeringUser, User}, usertrip::UserTrip, }; #[derive(Serialize, Clone, Debug)] pub struct Trip { - id: i64, + pub id: i64, pub cox_id: i64, - cox_name: String, + pub cox_name: String, trip_details_id: Option, - planned_starting_time: String, + pub planned_starting_time: String, pub max_people: i64, pub day: String, pub notes: Option, @@ -69,65 +70,6 @@ impl TripWithDetails { } impl Trip { - /// Cox decides to create own trip. - pub async fn new_own(db: &SqlitePool, cox: &SteeringUser, trip_details: TripDetails) { - Self::perform_new(db, &cox.user, trip_details).await - } - - pub async fn new_own_ergo(db: &SqlitePool, ergo: &ErgoUser, trip_details: TripDetails) { - let typ = trip_details.triptype(db).await; - if let Some(typ) = typ { - let allowed_type = TripType::find_by_id(db, 4).await.unwrap(); - if typ == allowed_type { - Self::perform_new(db, &ergo.user, trip_details).await; - } - } - } - - async fn perform_new(db: &SqlitePool, user: &User, trip_details: TripDetails) { - let _ = sqlx::query!( - "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)", - user.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; - for notify in same_starting_datetime { - // don't notify oneself - if notify.id == trip_details.id { - continue; - } - - // don't notify people who have cancelled their trip - if notify.cancelled() { - continue; - } - - if let Some(trip) = Trip::find_by_trip_details(db, notify.id).await { - let user_earlier_trip = User::find_by_id(db, trip.cox_id as i32).await.unwrap(); - Notification::create( - db, - &user_earlier_trip, - &format!( - "{} hat eine Ausfahrt zur selben Zeit ({} um {}) wie du erstellt", - 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 { sqlx::query_as!( Self, @@ -145,7 +87,7 @@ WHERE trip_details.id=? .ok() } - async fn trip_type(&self, db: &SqlitePool) -> Option { + pub(crate) async fn trip_type(&self, db: &SqlitePool) -> Option { if let Some(trip_type_id) = self.trip_type_id { TripType::find_by_id(db, trip_type_id).await } else { @@ -153,64 +95,6 @@ WHERE trip_details.id=? } } - pub(crate) async fn get_vevent<'a>(self, db: &'a SqlitePool, user: &'a User) -> ics::Event<'a> { - let mut vevent = - ics::Event::new(format!("trip-{}@rudernlinz.at", self.id), "19900101T180000"); - let time_str = self.planned_starting_time.replace(':', ""); - let formatted_time = if time_str.len() == 3 { - format!("0{}", time_str) - } else { - time_str - }; - - vevent.push(DtStart::new(format!( - "{}T{}00", - self.day.replace('-', ""), - formatted_time - ))); - - let original_time = NaiveTime::parse_from_str(&self.planned_starting_time, "%H:%M") - .expect("Failed to parse time"); - let long_trip = match self.trip_type(db).await { - Some(a) if a.name == "Lange Ausfahrt" => true, - _ => false, - }; - let later_time = if long_trip { - original_time + Duration::hours(6) - } else { - original_time + Duration::hours(3) - }; - if later_time > original_time { - // Check if no day-overflow - let time_three_hours_later = later_time.format("%H%M").to_string(); - vevent.push(DtEnd::new(format!( - "{}T{}00", - self.day.replace('-', ""), - time_three_hours_later - ))); - } - - let mut name = String::new(); - if self.is_cancelled() { - name.push_str("ABGESAGT"); - if let Some(notes) = &self.notes { - if !notes.is_empty() { - name.push_str(&format!(" (Grund: {notes})")) - } - } - - name.push_str("! :-( "); - } - if self.cox_id == user.id { - name.push_str("Ruderausfahrt (selber ausgeschrieben)"); - } else { - name.push_str(&format!("Ruderausfahrt mit {} ", self.cox_name)); - } - - vevent.push(Summary::new(name)); - vevent - } - pub async fn all(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, @@ -491,7 +375,7 @@ WHERE day=? trips } - fn is_cancelled(&self) -> bool { + pub(crate) fn is_cancelled(&self) -> bool { self.max_people == -1 } } @@ -583,11 +467,9 @@ mod test { let last_notification = &Notification::for_user(&pool, &cox).await[0]; - assert!( - last_notification - .message - .starts_with("cox2 hat eine Ausfahrt zur selben Zeit") - ); + assert!(last_notification + .message + .starts_with("cox2 hat eine Ausfahrt zur selben Zeit")); } #[sqlx::test] diff --git a/src/tera/cox.rs b/src/tera/cox.rs index 84ca58a..6f03e280 100644 --- a/src/tera/cox.rs +++ b/src/tera/cox.rs @@ -1,9 +1,8 @@ use rocket::{ - FromForm, Route, State, form::Form, get, post, response::{Flash, Redirect}, - routes, + routes, FromForm, Route, State, }; use sqlx::SqlitePool; @@ -23,21 +22,13 @@ async fn create_ergo( ) -> Flash { let trip_details_id = TripDetails::create(db, data.into_inner()).await; let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just - //created + //created Trip::new_own_ergo(db, &cox, trip_details).await; //TODO: fix - //Log::create( - // db, - // format!( - // "Cox {} created trip on {} @ {} for {} rower", - // cox.name, trip_details.day, trip_details.planned_starting_time, trip_details.max_people, - // ), - //) - //.await; - Flash::success(Redirect::to("/planned"), "Ausfahrt erfolgreich erstellt.") } +/// SteeringUser created new trip #[post("/trip", data = "")] async fn create( db: &State, @@ -46,18 +37,9 @@ async fn create( ) -> Flash { let trip_details_id = TripDetails::create(db, data.into_inner()).await; let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just - //created + //created Trip::new_own(db, &cox, trip_details).await; //TODO: fix - //Log::create( - // db, - // format!( - // "Cox {} created trip on {} @ {} for {} rower", - // cox.name, trip_details.day, trip_details.planned_starting_time, trip_details.max_people, - // ), - //) - //.await; - Flash::success(Redirect::to("/planned"), "Ausfahrt erfolgreich erstellt.") }