use std::io::Write; 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"); calendar.push(Property::new( "X-WR-CALNAME", "Donau Linz - Deine Ausfahrten", )); 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, user).await); } let mut buf = Vec::new(); 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 } }