From d404636261ba53c8b8b603331bbb5776135a179e Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 9 Sep 2024 21:51:01 +0300 Subject: [PATCH] start working on cal --- TODO.md | 2 ++ src/model/event.rs | 66 ++++++++++++++++++++++++--------------- src/model/personal/cal.rs | 23 ++++++++++++++ src/model/personal/mod.rs | 1 + src/model/trip.rs | 61 ++++++++++++++++++++++++++++++++++++ src/tera/misc.rs | 10 ++++-- templates/index.html.tera | 17 +++++++++- 7 files changed, 151 insertions(+), 29 deletions(-) create mode 100644 TODO.md create mode 100644 src/model/personal/cal.rs diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..463e52e --- /dev/null +++ b/TODO.md @@ -0,0 +1,2 @@ +- create new field in user table -> user\_token +- change in misc.rs personal calendar function on not require User, but user\_token diff --git a/src/model/event.rs b/src/model/event.rs index b62811b..cf5917d 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -183,6 +183,17 @@ INNER JOIN trip_details ON planned_event.trip_details_id = trip_details.id", .unwrap() //TODO: fixme } + pub async fn all_with_user(db: &SqlitePool, user: &User) -> Vec { + let mut ret = Vec::new(); + let events = Self::all(db).await; + for event in events { + if event.is_rower_registered(db, user).await { + ret.push(event); + } + } + ret + } + //TODO: add tests pub async fn is_rower_registered(&self, db: &SqlitePool, user: &User) -> bool { let is_rower = sqlx::query!( @@ -394,38 +405,41 @@ WHERE trip_details.id=? let events = Event::all(db).await; for event in events { - let mut vevent = - ics::Event::new(format!("{}@rudernlinz.at", event.id), "19900101T180000"); - vevent.push(DtStart::new(format!( - "{}T{}00", - event.day.replace('-', ""), - event.planned_starting_time.replace(':', "") - ))); - let tripdetails = event.trip_details(db).await; - let mut name = String::new(); - if event.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!("{} ", event.name)); - - if let Some(triptype) = tripdetails.triptype(db).await { - name.push_str(&format!("• {} ", triptype.name)) - } - vevent.push(Summary::new(name)); - calendar.add_event(vevent); + calendar.add_event(event.get_vevent(db).await); } let mut buf = Vec::new(); write!(&mut buf, "{}", calendar).unwrap(); String::from_utf8(buf).unwrap() } + pub(crate) async fn get_vevent(self, db: &SqlitePool) -> ics::Event { + let mut vevent = ics::Event::new(format!("{}@rudernlinz.at", self.id), "19900101T180000"); + vevent.push(DtStart::new(format!( + "{}T{}00", + self.day.replace('-', ""), + self.planned_starting_time.replace(':', "") + ))); + 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 new file mode 100644 index 0000000..194be34 --- /dev/null +++ b/src/model/personal/cal.rs @@ -0,0 +1,23 @@ +use std::io::Write; + +use ics::ICalendar; +use sqlx::SqlitePool; + +use crate::model::{event::Event, trip::Trip, user::User}; + +pub(crate) async fn get_personal_cal(db: &SqlitePool, user: &User) -> String { + let mut calendar = ICalendar::new("2.0", "ics-rs"); + + let events = Event::all_with_user(db, user).await; + for event in events { + calendar.add_event(event.get_vevent(db).await); + } + + let trips = Trip::all_with_user(db, user).await; + for trip in trips { + calendar.add_event(trip.get_vevent(db).await); + } + let mut buf = Vec::new(); + write!(&mut buf, "{}", calendar).unwrap(); + String::from_utf8(buf).unwrap() +} diff --git a/src/model/personal/mod.rs b/src/model/personal/mod.rs index cc9c53c..40a93c9 100644 --- a/src/model/personal/mod.rs +++ b/src/model/personal/mod.rs @@ -5,6 +5,7 @@ use sqlx::SqlitePool; use super::{logbook::Logbook, stat::Stat, user::User}; +pub(crate) mod cal; pub(crate) mod equatorprice; pub(crate) mod rowingbadge; diff --git a/src/model/trip.rs b/src/model/trip.rs index fabc4de..46a97ca 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -1,4 +1,5 @@ use chrono::{Local, NaiveDate}; +use ics::properties::{DtStart, Summary}; use serde::Serialize; use sqlx::SqlitePool; @@ -9,6 +10,7 @@ use super::{ tripdetails::TripDetails, triptype::TripType, user::{CoxUser, User}, + usertrip::UserTrip, }; #[derive(Serialize, Clone, Debug)] @@ -123,6 +125,61 @@ WHERE trip_details.id=? .ok() } + pub(crate) async fn get_vevent(self, db: &SqlitePool) -> ics::Event { + let mut vevent = ics::Event::new(format!("{}@rudernlinz.at", self.id), "19900101T180000"); + vevent.push(DtStart::new(format!( + "{}T{}00", + self.day.replace('-', ""), + self.planned_starting_time.replace(':', "") + ))); + 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("! :-( "); + } + 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, + " +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 +", + ) + .fetch_all(db) + .await + .unwrap() //TODO: fixme + } + + pub async fn all_with_user(db: &SqlitePool, user: &User) -> Vec { + let mut ret = Vec::new(); + let trips = Self::all(db).await; + for trip in trips { + if let Some(trip_details_id) = trip.trip_details_id { + if UserTrip::find_by_userid_and_trip_detail_id(db, user.id, trip_details_id) + .await + .is_some() + { + ret.push(trip); + } + } + } + ret + } + pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { sqlx::query_as!( Self, @@ -370,6 +427,10 @@ WHERE day=? trips.retain(|e| e.trip.always_show); trips } + + fn is_cancelled(&self) -> bool { + self.max_people == 0 + } } #[derive(Debug)] diff --git a/src/tera/misc.rs b/src/tera/misc.rs index 5cf48ff..8f3e905 100644 --- a/src/tera/misc.rs +++ b/src/tera/misc.rs @@ -1,7 +1,7 @@ use rocket::{get, http::ContentType, routes, Route, State}; use sqlx::SqlitePool; -use crate::model::event::Event; +use crate::model::{event::Event, personal::cal::get_personal_cal, user::User}; #[get("/cal")] async fn cal(db: &State) -> (ContentType, String) { @@ -9,8 +9,14 @@ async fn cal(db: &State) -> (ContentType, String) { (ContentType::Calendar, Event::get_ics_feed(db).await) } +#[get("/cal/registered")] +async fn cal_registered(db: &State, user: User) -> (ContentType, String) { + //TODO: add unit test once proper functionality is there + (ContentType::Calendar, get_personal_cal(db, &user).await) +} + pub fn routes() -> Vec { - routes![cal] + routes![cal, cal_registered] } #[cfg(test)] diff --git a/templates/index.html.tera b/templates/index.html.tera index c717c89..5d86b6d 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -103,7 +103,7 @@ +
+

+

+  📅  Kalender +

+ Du möchtest immer up-to-date mit den Events und Ausfahrten bleiben? Wir bieten 3 verschiedene Arten von Kalender an:

+
    +
  1. Alle Events und Ausfahrten, zu denen du dich angemeldet hast: https://app.rudernlinz.at/cal/personal?my-secrect-key
  2. +
  3. Allgemeiner Kalender, zB save-the-dates (Wanderfahrten, ...): https://rudernlinz.at/cal
  4. +
  5. Alle Events: https://app.rudernlinz.at/cal
  6. +
+ Du kannst die Kalender einfach in deinen Kalender als "externen Kalender" synchronisieren. Die genauen Schritte hängen von deiner verwendeten Software ab. +
+

+