diff --git a/crates/caldav/Cargo.toml b/crates/caldav/Cargo.toml index 968b389..7f0853e 100644 --- a/crates/caldav/Cargo.toml +++ b/crates/caldav/Cargo.toml @@ -24,3 +24,6 @@ serde_json = "1.0.105" tokio = { version = "1.32.0", features = ["sync", "full"] } async-trait = "0.1.73" thiserror = "1.0.48" +ical = { git = "https://github.com/Peltoche/ical-rs.git", rev = "4f7aeb0", features = [ + "generator", +] } diff --git a/crates/caldav/src/resources/event.rs b/crates/caldav/src/resources/event.rs index 3997e57..4127fb3 100644 --- a/crates/caldav/src/resources/event.rs +++ b/crates/caldav/src/resources/event.rs @@ -60,7 +60,7 @@ impl Resource for EventResource { write_string_prop(writer, "getetag", &self.event.get_etag())?; } "calendar-data" => { - write_string_prop(writer, "C:calendar-data", self.event.as_ics())?; + write_string_prop(writer, "C:calendar-data", &self.event.get_ics())?; } "getcontenttype" => { write_string_prop(writer, "getcontenttype", "text/calendar;charset=utf-8")?; diff --git a/crates/caldav/src/routes/event.rs b/crates/caldav/src/routes/event.rs index 26cedb1..c0d1b46 100644 --- a/crates/caldav/src/routes/event.rs +++ b/crates/caldav/src/routes/event.rs @@ -53,7 +53,7 @@ pub async fn get_event( Ok(HttpResponse::Ok() .insert_header(("ETag", event.get_etag())) - .body(event.as_ics().to_string())) + .body(event.get_ics())) } pub async fn put_event( diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 7a18e83..68276f9 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -22,3 +22,6 @@ sqlx = { version = "0.7.1", features = [ ] } tokio = { version = "1.32.0", features = ["sync", "full"] } toml = "0.7.6" +ical = { git = "https://github.com/Peltoche/ical-rs.git", rev = "4f7aeb0", features = [ + "generator", +] } diff --git a/crates/store/src/calendar.rs b/crates/store/src/calendar.rs index ab588ea..7877dd4 100644 --- a/crates/store/src/calendar.rs +++ b/crates/store/src/calendar.rs @@ -1,17 +1,63 @@ -use anyhow::Result; +use std::io::BufReader; + +use anyhow::{anyhow, Result}; use async_trait::async_trait; +use ical::generator::{Emitter, IcalCalendar}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone)] pub struct Event { uid: String, - ics: String, + cal: IcalCalendar, +} + +// Custom implementation for Event (de)serialization +impl<'de> Deserialize<'de> for Event { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Inner { + uid: String, + ics: String, + } + let Inner { uid, ics } = Inner::deserialize(deserializer)?; + Self::from_ics(uid, ics).map_err(serde::de::Error::custom) + } +} +impl Serialize for Event { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct Inner { + uid: String, + ics: String, + } + Inner::serialize( + &Inner { + uid: self.get_uid().to_string(), + ics: self.get_ics().to_string(), + }, + serializer, + ) + } } impl Event { - pub fn from_ics(uid: String, ics: String) -> Self { - Self { uid, ics } + pub fn from_ics(uid: String, ics: String) -> Result { + let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes())); + let cal = parser.next().ok_or(anyhow!("no calendar :("))??; + if parser.next().is_some() { + return Err(anyhow!("multiple calendars!")); + } + if cal.events.len() == 2 { + return Err(anyhow!("multiple events")); + } + Ok(Self { uid, cal }) } pub fn get_uid(&self) -> &str { &self.uid @@ -19,12 +65,12 @@ impl Event { pub fn get_etag(&self) -> String { let mut hasher = Sha256::new(); hasher.update(&self.uid); - hasher.update(self.as_ics()); + hasher.update(self.get_ics()); format!("{:x}", hasher.finalize()) } - pub fn as_ics(&self) -> &str { - &self.ics + pub fn get_ics(&self) -> String { + self.cal.generate() } } @@ -38,8 +84,6 @@ pub struct Calendar { pub timezone: Option, } -impl Calendar {} - #[async_trait] pub trait CalendarStore: Send + Sync + 'static { async fn get_calendar(&self, id: &str) -> Result; diff --git a/crates/store/src/toml_store.rs b/crates/store/src/toml_store.rs index 176af50..061a748 100644 --- a/crates/store/src/toml_store.rs +++ b/crates/store/src/toml_store.rs @@ -70,7 +70,7 @@ impl CalendarStore for TomlCalendarStore { async fn upsert_event(&mut self, cid: String, uid: String, ics: String) -> Result<()> { let events = self.events.entry(cid).or_insert(HashMap::new()); - events.insert(uid.clone(), Event::from_ics(uid, ics)); + events.insert(uid.clone(), Event::from_ics(uid, ics)?); self.save().await.unwrap(); Ok(()) }