use super::methods::mkcalendar::route_mkcalendar; use super::methods::report::route_report_calendar; use super::prop::{ Resourcetype, SupportedCalendarComponent, SupportedCalendarComponentSet, SupportedCalendarData, SupportedReportSet, UserPrivilegeSet, }; use crate::calendar_object::resource::CalendarObjectResource; use crate::principal::PrincipalResource; use crate::Error; use actix_web::dev::ResourceMap; use actix_web::http::Method; use actix_web::web; use actix_web::{web::Data, HttpRequest}; use async_trait::async_trait; use derive_more::derive::{From, Into}; use rustical_dav::resource::{InvalidProperty, Resource, ResourceService}; use rustical_dav::xml::HrefElement; use rustical_store::{Calendar, CalendarStore}; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; use strum::{EnumString, VariantNames}; pub struct CalendarResourceService { pub cal_store: Arc, pub path: String, pub principal: String, pub calendar_id: String, } #[derive(EnumString, Debug, VariantNames, Clone)] #[strum(serialize_all = "kebab-case")] pub enum CalendarPropName { Resourcetype, CurrentUserPrincipal, Owner, Displayname, CalendarColor, CalendarDescription, CalendarTimezone, CalendarOrder, SupportedCalendarComponentSet, SupportedCalendarData, Getcontenttype, CurrentUserPrivilegeSet, MaxResourceSize, SupportedReportSet, SyncToken, Getctag, } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum CalendarProp { // WebDAV (RFC 2518) Resourcetype(Resourcetype), Displayname(Option), Getcontenttype(String), // WebDAV Current Principal Extension (RFC 5397) CurrentUserPrincipal(HrefElement), // WebDAV Access Control (RFC 3744) Owner(HrefElement), CurrentUserPrivilegeSet(UserPrivilegeSet), // CalDAV (RFC 4791) #[serde(rename = "IC:calendar-color", alias = "calendar-color")] CalendarColor(Option), #[serde(rename = "C:calendar-description", alias = "calendar-description")] CalendarDescription(Option), #[serde(rename = "C:calendar-timezone", alias = "calendar-timezone")] CalendarTimezone(Option), #[serde(rename = "IC:calendar-order", alias = "calendar-order")] CalendarOrder(Option), #[serde( rename = "C:supported-calendar-component-set", alias = "supported-calendar-component-set" )] SupportedCalendarComponentSet(SupportedCalendarComponentSet), #[serde( rename = "C:supported-calendar-data", alias = "supported-calendar-data" )] SupportedCalendarData(SupportedCalendarData), MaxResourceSize(i64), SupportedReportSet(SupportedReportSet), // Collection Synchronization (RFC 6578) SyncToken(String), // Didn't find the spec Getctag(String), #[serde(other)] Invalid, } impl InvalidProperty for CalendarProp { fn invalid_property(&self) -> bool { matches!(self, Self::Invalid) } } #[derive(Clone, Debug, From, Into)] pub struct CalendarResource(Calendar); impl Resource for CalendarResource { type PropName = CalendarPropName; type Prop = CalendarProp; type Error = Error; fn get_prop( &self, rmap: &ResourceMap, prop: Self::PropName, ) -> Result { Ok(match prop { CalendarPropName::Resourcetype => CalendarProp::Resourcetype(Resourcetype::default()), CalendarPropName::CurrentUserPrincipal => { CalendarProp::CurrentUserPrincipal(HrefElement::new( PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(), )) } CalendarPropName::Owner => CalendarProp::Owner(HrefElement::new( PrincipalResource::get_url(rmap, vec![&self.0.principal]).unwrap(), )), CalendarPropName::Displayname => CalendarProp::Displayname(self.0.displayname.clone()), CalendarPropName::CalendarColor => CalendarProp::CalendarColor(self.0.color.clone()), CalendarPropName::CalendarDescription => { CalendarProp::CalendarDescription(self.0.description.clone()) } CalendarPropName::CalendarTimezone => { CalendarProp::CalendarTimezone(self.0.timezone.clone()) } CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)), CalendarPropName::SupportedCalendarComponentSet => { CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet { comp: vec![ SupportedCalendarComponent { name: "VEVENT".to_owned(), }, SupportedCalendarComponent { name: "VTODO".to_owned(), }, SupportedCalendarComponent { name: "VJOURNAL".to_owned(), }, ], }) } CalendarPropName::SupportedCalendarData => { CalendarProp::SupportedCalendarData(SupportedCalendarData::default()) } CalendarPropName::Getcontenttype => { CalendarProp::Getcontenttype("text/calendar;charset=utf-8".to_owned()) } CalendarPropName::MaxResourceSize => CalendarProp::MaxResourceSize(10000000), CalendarPropName::CurrentUserPrivilegeSet => { CalendarProp::CurrentUserPrivilegeSet(UserPrivilegeSet::default()) } CalendarPropName::SupportedReportSet => { CalendarProp::SupportedReportSet(SupportedReportSet::default()) } CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()), CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()), }) } fn set_prop(&mut self, prop: Self::Prop) -> Result<(), rustical_dav::Error> { match prop { CalendarProp::Resourcetype(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrincipal(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Owner(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Displayname(displayname) => { self.0.displayname = displayname; Ok(()) } CalendarProp::CalendarColor(color) => { self.0.color = color; Ok(()) } CalendarProp::CalendarDescription(description) => { self.0.description = description; Ok(()) } CalendarProp::CalendarTimezone(timezone) => { self.0.timezone = timezone; Ok(()) } CalendarProp::CalendarOrder(order) => { self.0.order = order.unwrap_or_default(); Ok(()) } CalendarProp::SupportedCalendarComponentSet(_) => { Err(rustical_dav::Error::PropReadOnly) } CalendarProp::SupportedCalendarData(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Getcontenttype(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::MaxResourceSize(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::CurrentUserPrivilegeSet(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly), CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly), } } fn remove_prop(&mut self, prop: Self::PropName) -> Result<(), rustical_dav::Error> { match prop { CalendarPropName::Resourcetype => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::CurrentUserPrincipal => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Owner => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Displayname => { self.0.displayname = None; Ok(()) } CalendarPropName::CalendarColor => { self.0.color = None; Ok(()) } CalendarPropName::CalendarDescription => { self.0.description = None; Ok(()) } CalendarPropName::CalendarTimezone => { self.0.timezone = None; Ok(()) } CalendarPropName::CalendarOrder => { self.0.order = 0; Ok(()) } CalendarPropName::SupportedCalendarComponentSet => { Err(rustical_dav::Error::PropReadOnly) } CalendarPropName::SupportedCalendarData => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Getcontenttype => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::MaxResourceSize => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::CurrentUserPrivilegeSet => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly), CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly), } } #[inline] fn resource_name() -> &'static str { "caldav_calendar" } } #[async_trait(?Send)] impl ResourceService for CalendarResourceService { type MemberType = CalendarObjectResource; type PathComponents = (String, String); // principal, calendar_id type Resource = CalendarResource; type Error = Error; async fn get_resource(&self, principal: String) -> Result { if self.principal != principal { return Err(Error::Unauthorized); } let calendar = self .cal_store .get_calendar(&self.principal, &self.calendar_id) .await .map_err(|_e| Error::NotFound)?; Ok(calendar.into()) } async fn get_members( &self, rmap: &ResourceMap, ) -> Result, Self::Error> { Ok(self .cal_store .get_objects(&self.principal, &self.calendar_id) .await? .into_iter() .map(|object| { ( CalendarObjectResource::get_url( rmap, vec![&self.principal, &self.calendar_id, object.get_id()], ) .unwrap(), object.into(), ) }) .collect()) } async fn new( req: &HttpRequest, path_components: Self::PathComponents, ) -> Result { let cal_store = req .app_data::>() .expect("no calendar store in app_data!") .clone() .into_inner(); Ok(Self { path: req.path().to_owned(), principal: path_components.0, calendar_id: path_components.1, cal_store, }) } async fn save_resource(&self, file: Self::Resource) -> Result<(), Self::Error> { self.cal_store .update_calendar( self.principal.to_owned(), self.calendar_id.to_owned(), file.into(), ) .await?; Ok(()) } async fn delete_resource(&self, use_trashbin: bool) -> Result<(), Self::Error> { self.cal_store .delete_calendar(&self.principal, &self.calendar_id, use_trashbin) .await?; Ok(()) } #[inline] fn actix_additional_routes(res: actix_web::Resource) -> actix_web::Resource { let report_method = web::method(Method::from_str("REPORT").unwrap()); let mkcalendar_method = web::method(Method::from_str("MKCALENDAR").unwrap()); res.route(report_method.to(route_report_calendar::)) .route(mkcalendar_method.to(route_mkcalendar::)) } }