From 71c2f8c019fe3ffefe7fe8a31d06490856c27bb6 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:09:46 +0200 Subject: [PATCH] Move properties into separate files --- .../caldav/src/calendar/methods/mkcalendar.rs | 2 +- .../methods/report/calendar_multiget.rs | 2 +- .../calendar/methods/report/calendar_query.rs | 2 +- .../caldav/src/calendar/methods/report/mod.rs | 8 +- .../methods/report/sync_collection.rs | 4 +- crates/caldav/src/calendar/mod.rs | 3 + crates/caldav/src/calendar/resource.rs | 128 +------------- crates/caldav/src/calendar/service.rs | 128 ++++++++++++++ crates/caldav/src/calendar_object/methods.rs | 3 +- crates/caldav/src/calendar_object/mod.rs | 4 + crates/caldav/src/calendar_object/prop.rs | 45 +++++ crates/caldav/src/calendar_object/resource.rs | 162 +---------------- crates/caldav/src/calendar_object/service.rs | 113 ++++++++++++ crates/caldav/src/calendar_set/mod.rs | 97 +--------- crates/caldav/src/calendar_set/prop.rs | 8 + crates/caldav/src/calendar_set/service.rs | 84 +++++++++ crates/caldav/src/principal/mod.rs | 152 +--------------- crates/caldav/src/principal/prop.rs | 34 ++++ crates/caldav/src/principal/service.rs | 110 ++++++++++++ crates/carddav/src/address_object/methods.rs | 7 +- crates/carddav/src/address_object/mod.rs | 4 + crates/carddav/src/address_object/prop.rs | 23 +++ crates/carddav/src/address_object/resource.rs | 140 ++------------- crates/carddav/src/address_object/service.rs | 105 +++++++++++ .../carddav/src/addressbook/methods/mkcol.rs | 2 +- .../methods/report/addressbook_multiget.rs | 4 +- .../src/addressbook/methods/report/mod.rs | 6 +- .../methods/report/sync_collection.rs | 4 +- crates/carddav/src/addressbook/mod.rs | 2 + crates/carddav/src/addressbook/prop.rs | 31 +++- crates/carddav/src/addressbook/resource.rs | 167 +----------------- crates/carddav/src/addressbook/service.rs | 130 ++++++++++++++ crates/carddav/src/principal/mod.rs | 131 +------------- crates/carddav/src/principal/prop.rs | 30 ++++ crates/carddav/src/principal/service.rs | 96 ++++++++++ 35 files changed, 1023 insertions(+), 948 deletions(-) create mode 100644 crates/caldav/src/calendar/service.rs create mode 100644 crates/caldav/src/calendar_object/prop.rs create mode 100644 crates/caldav/src/calendar_object/service.rs create mode 100644 crates/caldav/src/calendar_set/prop.rs create mode 100644 crates/caldav/src/calendar_set/service.rs create mode 100644 crates/caldav/src/principal/prop.rs create mode 100644 crates/caldav/src/principal/service.rs create mode 100644 crates/carddav/src/address_object/prop.rs create mode 100644 crates/carddav/src/address_object/service.rs create mode 100644 crates/carddav/src/addressbook/service.rs create mode 100644 crates/carddav/src/principal/prop.rs create mode 100644 crates/carddav/src/principal/service.rs diff --git a/crates/caldav/src/calendar/methods/mkcalendar.rs b/crates/caldav/src/calendar/methods/mkcalendar.rs index fca3e00..c3cd9aa 100644 --- a/crates/caldav/src/calendar/methods/mkcalendar.rs +++ b/crates/caldav/src/calendar/methods/mkcalendar.rs @@ -1,6 +1,6 @@ use crate::Error; +use crate::calendar::CalendarResourceService; use crate::calendar::prop::SupportedCalendarComponentSet; -use crate::calendar::resource::CalendarResourceService; use axum::extract::{Path, State}; use axum::response::{IntoResponse, Response}; use http::StatusCode; diff --git a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs index e106add..4ee7237 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_multiget.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_multiget.rs @@ -1,4 +1,4 @@ -use crate::{Error, calendar_object::resource::CalendarObjectPropWrapperName}; +use crate::{Error, calendar_object::CalendarObjectPropWrapperName}; use rustical_dav::xml::PropfindType; use rustical_ical::CalendarObject; use rustical_store::CalendarStore; diff --git a/crates/caldav/src/calendar/methods/report/calendar_query.rs b/crates/caldav/src/calendar/methods/report/calendar_query.rs index 2f12183..1d3a945 100644 --- a/crates/caldav/src/calendar/methods/report/calendar_query.rs +++ b/crates/caldav/src/calendar/methods/report/calendar_query.rs @@ -1,4 +1,4 @@ -use crate::{Error, calendar_object::resource::CalendarObjectPropWrapperName}; +use crate::{Error, calendar_object::CalendarObjectPropWrapperName}; use rustical_dav::xml::PropfindType; use rustical_ical::{CalendarObject, UtcDateTime}; use rustical_store::{CalendarStore, calendar_store::CalendarQuery}; diff --git a/crates/caldav/src/calendar/methods/report/mod.rs b/crates/caldav/src/calendar/methods/report/mod.rs index 32a770d..9e1ec56 100644 --- a/crates/caldav/src/calendar/methods/report/mod.rs +++ b/crates/caldav/src/calendar/methods/report/mod.rs @@ -1,8 +1,8 @@ use crate::{ CalDavPrincipalUri, Error, - calendar::resource::CalendarResourceService, - calendar_object::resource::{ - CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource, + calendar::CalendarResourceService, + calendar_object::{ + CalendarObjectPropWrapper, CalendarObjectPropWrapperName, resource::CalendarObjectResource, }, }; use axum::{ @@ -147,7 +147,7 @@ pub async fn route_report_calendar( #[cfg(test)] mod tests { use super::*; - use crate::calendar_object::resource::{CalendarData, CalendarObjectPropName, ExpandElement}; + use crate::calendar_object::{CalendarData, CalendarObjectPropName, ExpandElement}; use calendar_query::{CompFilterElement, FilterElement, TimeRangeElement}; use rustical_dav::xml::PropElement; use rustical_ical::UtcDateTime; diff --git a/crates/caldav/src/calendar/methods/report/sync_collection.rs b/crates/caldav/src/calendar/methods/report/sync_collection.rs index b1a2b50..7686c75 100644 --- a/crates/caldav/src/calendar/methods/report/sync_collection.rs +++ b/crates/caldav/src/calendar/methods/report/sync_collection.rs @@ -1,7 +1,7 @@ use crate::{ Error, - calendar_object::resource::{ - CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource, + calendar_object::{ + CalendarObjectPropWrapper, CalendarObjectPropWrapperName, resource::CalendarObjectResource, }, }; use http::StatusCode; diff --git a/crates/caldav/src/calendar/mod.rs b/crates/caldav/src/calendar/mod.rs index 0cb72f5..2d092d8 100644 --- a/crates/caldav/src/calendar/mod.rs +++ b/crates/caldav/src/calendar/mod.rs @@ -1,3 +1,6 @@ pub mod methods; pub mod prop; pub mod resource; +mod service; + +pub use service::CalendarResourceService; diff --git a/crates/caldav/src/calendar/resource.rs b/crates/caldav/src/calendar/resource.rs index 15c0842..89f883d 100644 --- a/crates/caldav/src/calendar/resource.rs +++ b/crates/caldav/src/calendar/resource.rs @@ -1,32 +1,20 @@ use super::prop::{SupportedCalendarComponentSet, SupportedCalendarData, SupportedReportSet}; -use crate::calendar::methods::mkcalendar::route_mkcalendar; -use crate::calendar::methods::report::route_report_calendar; -use crate::calendar_object::resource::{CalendarObjectResource, CalendarObjectResourceService}; -use crate::{CalDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::Router; -use axum::extract::Request; -use axum::handler::Handler; -use axum::response::Response; +use crate::Error; use chrono::{DateTime, Utc}; use derive_more::derive::{From, Into}; -use futures_util::future::BoxFuture; use rustical_dav::extensions::{ CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, }; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceName}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; use rustical_dav_push::DavPushExtension; use rustical_ical::CalDateTime; +use rustical_store::Calendar; use rustical_store::auth::User; -use rustical_store::{Calendar, CalendarStore, SubscriptionStore}; use rustical_xml::{EnumVariants, PropName}; use rustical_xml::{XmlDeserialize, XmlSerialize}; -use std::convert::Infallible; use std::str::FromStr; -use std::sync::Arc; -use tower::Service; #[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] #[xml(unit_variants_ident = "CalendarPropName")] @@ -317,113 +305,3 @@ impl Resource for CalendarResource { )) } } - -pub struct CalendarResourceService { - pub(crate) cal_store: Arc, - pub(crate) sub_store: Arc, -} - -impl Clone for CalendarResourceService { - fn clone(&self) -> Self { - Self { - cal_store: self.cal_store.clone(), - sub_store: self.sub_store.clone(), - } - } -} - -impl CalendarResourceService { - pub fn new(cal_store: Arc, sub_store: Arc) -> Self { - Self { - cal_store, - sub_store, - } - } -} - -#[async_trait] -impl ResourceService for CalendarResourceService { - type MemberType = CalendarObjectResource; - type PathComponents = (String, String); // principal, calendar_id - type Resource = CalendarResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CalDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; - - async fn get_resource( - &self, - (principal, cal_id): &Self::PathComponents, - ) -> Result { - let calendar = self.cal_store.get_calendar(principal, cal_id).await?; - Ok(CalendarResource { - cal: calendar, - read_only: self.cal_store.is_read_only(), - }) - } - - async fn get_members( - &self, - (principal, cal_id): &Self::PathComponents, - ) -> Result, Self::Error> { - Ok(self - .cal_store - .get_objects(principal, cal_id) - .await? - .into_iter() - .map(|object| CalendarObjectResource { - object, - principal: principal.to_owned(), - }) - .collect()) - } - - async fn save_resource( - &self, - (principal, cal_id): &Self::PathComponents, - file: Self::Resource, - ) -> Result<(), Self::Error> { - self.cal_store - .update_calendar(principal.to_owned(), cal_id.to_owned(), file.into()) - .await?; - Ok(()) - } - - async fn delete_resource( - &self, - (principal, cal_id): &Self::PathComponents, - use_trashbin: bool, - ) -> Result<(), Self::Error> { - self.cal_store - .delete_calendar(principal, cal_id, use_trashbin) - .await?; - Ok(()) - } - - fn axum_router(self) -> axum::Router { - Router::new() - .nest( - "/{object_id}", - CalendarObjectResourceService::new(self.cal_store.clone()).axum_router(), - ) - .route_service("/", self.axum_service()) - } -} - -impl AxumMethods for CalendarResourceService { - fn report() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(route_report_calendar::, state); - Box::pin(Service::call(&mut service, req)) - }) - } - - fn mkcalendar() -> Option BoxFuture<'static, Result>> - { - Some(|state, req| { - let mut service = Handler::with_state(route_mkcalendar::, state); - Box::pin(Service::call(&mut service, req)) - }) - } -} diff --git a/crates/caldav/src/calendar/service.rs b/crates/caldav/src/calendar/service.rs new file mode 100644 index 0000000..62244e6 --- /dev/null +++ b/crates/caldav/src/calendar/service.rs @@ -0,0 +1,128 @@ +use crate::calendar::methods::mkcalendar::route_mkcalendar; +use crate::calendar::methods::report::route_report_calendar; +use crate::calendar::resource::CalendarResource; +use crate::calendar_object::CalendarObjectResourceService; +use crate::calendar_object::resource::CalendarObjectResource; +use crate::{CalDavPrincipalUri, Error}; +use async_trait::async_trait; +use axum::Router; +use axum::extract::Request; +use axum::handler::Handler; +use axum::response::Response; +use futures_util::future::BoxFuture; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::auth::User; +use rustical_store::{CalendarStore, SubscriptionStore}; +use std::convert::Infallible; +use std::sync::Arc; +use tower::Service; + +pub struct CalendarResourceService { + pub(crate) cal_store: Arc, + pub(crate) sub_store: Arc, +} + +impl Clone for CalendarResourceService { + fn clone(&self) -> Self { + Self { + cal_store: self.cal_store.clone(), + sub_store: self.sub_store.clone(), + } + } +} + +impl CalendarResourceService { + pub fn new(cal_store: Arc, sub_store: Arc) -> Self { + Self { + cal_store, + sub_store, + } + } +} + +#[async_trait] +impl ResourceService for CalendarResourceService { + type MemberType = CalendarObjectResource; + type PathComponents = (String, String); // principal, calendar_id + type Resource = CalendarResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CalDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; + + async fn get_resource( + &self, + (principal, cal_id): &Self::PathComponents, + ) -> Result { + let calendar = self.cal_store.get_calendar(principal, cal_id).await?; + Ok(CalendarResource { + cal: calendar, + read_only: self.cal_store.is_read_only(), + }) + } + + async fn get_members( + &self, + (principal, cal_id): &Self::PathComponents, + ) -> Result, Self::Error> { + Ok(self + .cal_store + .get_objects(principal, cal_id) + .await? + .into_iter() + .map(|object| CalendarObjectResource { + object, + principal: principal.to_owned(), + }) + .collect()) + } + + async fn save_resource( + &self, + (principal, cal_id): &Self::PathComponents, + file: Self::Resource, + ) -> Result<(), Self::Error> { + self.cal_store + .update_calendar(principal.to_owned(), cal_id.to_owned(), file.into()) + .await?; + Ok(()) + } + + async fn delete_resource( + &self, + (principal, cal_id): &Self::PathComponents, + use_trashbin: bool, + ) -> Result<(), Self::Error> { + self.cal_store + .delete_calendar(principal, cal_id, use_trashbin) + .await?; + Ok(()) + } + + fn axum_router(self) -> axum::Router { + Router::new() + .nest( + "/{object_id}", + CalendarObjectResourceService::new(self.cal_store.clone()).axum_router(), + ) + .route_service("/", self.axum_service()) + } +} + +impl AxumMethods for CalendarResourceService { + fn report() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(route_report_calendar::, state); + Box::pin(Service::call(&mut service, req)) + }) + } + + fn mkcalendar() -> Option BoxFuture<'static, Result>> + { + Some(|state, req| { + let mut service = Handler::with_state(route_mkcalendar::, state); + Box::pin(Service::call(&mut service, req)) + }) + } +} diff --git a/crates/caldav/src/calendar_object/methods.rs b/crates/caldav/src/calendar_object/methods.rs index a647e44..8ec8f79 100644 --- a/crates/caldav/src/calendar_object/methods.rs +++ b/crates/caldav/src/calendar_object/methods.rs @@ -1,6 +1,5 @@ -use super::resource::CalendarObjectPathComponents; use crate::Error; -use crate::calendar_object::resource::CalendarObjectResourceService; +use crate::calendar_object::{CalendarObjectPathComponents, CalendarObjectResourceService}; use crate::error::Precondition; use axum::body::Body; use axum::extract::{Path, State}; diff --git a/crates/caldav/src/calendar_object/mod.rs b/crates/caldav/src/calendar_object/mod.rs index 20447fe..93cd7f7 100644 --- a/crates/caldav/src/calendar_object/mod.rs +++ b/crates/caldav/src/calendar_object/mod.rs @@ -1,2 +1,6 @@ pub mod methods; pub mod resource; +mod service; +pub use service::*; +mod prop; +pub use prop::*; diff --git a/crates/caldav/src/calendar_object/prop.rs b/crates/caldav/src/calendar_object/prop.rs new file mode 100644 index 0000000..ebd181d --- /dev/null +++ b/crates/caldav/src/calendar_object/prop.rs @@ -0,0 +1,45 @@ +use rustical_dav::extensions::CommonPropertiesProp; +use rustical_ical::UtcDateTime; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "CalendarObjectPropName")] +pub enum CalendarObjectProp { + // WebDAV (RFC 2518) + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + Getetag(String), + #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] + Getcontenttype(&'static str), + + // CalDAV (RFC 4791) + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + #[xml(prop = "CalendarData")] + CalendarData(String), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "CalendarObjectPropWrapperName", untagged)] +pub enum CalendarObjectPropWrapper { + CalendarObject(CalendarObjectProp), + Common(CommonPropertiesProp), +} + +#[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) struct ExpandElement { + #[xml(ty = "attr")] + pub(crate) start: UtcDateTime, + #[xml(ty = "attr")] + pub(crate) end: UtcDateTime, +} + +#[derive(XmlDeserialize, Clone, Debug, PartialEq, Default, Eq, Hash)] +pub struct CalendarData { + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) comp: Option<()>, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) expand: Option, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) limit_recurrence_set: Option<()>, + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + pub(crate) limit_freebusy_set: Option<()>, +} diff --git a/crates/caldav/src/calendar_object/resource.rs b/crates/caldav/src/calendar_object/resource.rs index 67cd361..489d4de 100644 --- a/crates/caldav/src/calendar_object/resource.rs +++ b/crates/caldav/src/calendar_object/resource.rs @@ -1,66 +1,14 @@ -// use super::methods::{get_event, put_event}; -use crate::{ - CalDavPrincipalUri, Error, - calendar_object::methods::{get_event, put_event}, -}; -use async_trait::async_trait; -use axum::{extract::Request, handler::Handler, response::Response}; +use super::prop::*; +use crate::Error; use derive_more::derive::{From, Into}; -use futures_util::future::BoxFuture; use rustical_dav::{ - extensions::{CommonPropertiesExtension, CommonPropertiesProp}, + extensions::CommonPropertiesExtension, privileges::UserPrivilegeSet, - resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}, + resource::{PrincipalUri, Resource, ResourceName}, xml::Resourcetype, }; -use rustical_ical::{CalendarObject, UtcDateTime}; -use rustical_store::{CalendarStore, auth::User}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use serde::{Deserialize, Deserializer}; -use std::{convert::Infallible, sync::Arc}; -use tower::Service; - -#[derive(XmlDeserialize, Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) struct ExpandElement { - #[xml(ty = "attr")] - pub(crate) start: UtcDateTime, - #[xml(ty = "attr")] - pub(crate) end: UtcDateTime, -} - -#[derive(XmlDeserialize, Clone, Debug, PartialEq, Default, Eq, Hash)] -pub struct CalendarData { - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - pub(crate) comp: Option<()>, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - pub(crate) expand: Option, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - pub(crate) limit_recurrence_set: Option<()>, - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - pub(crate) limit_freebusy_set: Option<()>, -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "CalendarObjectPropName")] -pub enum CalendarObjectProp { - // WebDAV (RFC 2518) - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - Getetag(String), - #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] - Getcontenttype(&'static str), - - // CalDAV (RFC 4791) - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - #[xml(prop = "CalendarData")] - CalendarData(String), -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "CalendarObjectPropWrapperName", untagged)] -pub enum CalendarObjectPropWrapper { - CalendarObject(CalendarObjectProp), - Common(CommonPropertiesProp), -} +use rustical_ical::CalendarObject; +use rustical_store::auth::User; #[derive(Clone, From, Into)] pub struct CalendarObjectResource { @@ -130,101 +78,3 @@ impl Resource for CalendarObjectResource { )) } } - -fn deserialize_ics_name<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let name: String = Deserialize::deserialize(deserializer)?; - if let Some(object_id) = name.strip_suffix(".ics") { - Ok(object_id.to_owned()) - } else { - Err(serde::de::Error::custom("Missing .ics extension")) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct CalendarObjectPathComponents { - pub principal: String, - pub calendar_id: String, - #[serde(deserialize_with = "deserialize_ics_name")] - pub object_id: String, -} - -pub struct CalendarObjectResourceService { - pub(crate) cal_store: Arc, -} - -impl Clone for CalendarObjectResourceService { - fn clone(&self) -> Self { - Self { - cal_store: self.cal_store.clone(), - } - } -} - -impl CalendarObjectResourceService { - pub fn new(cal_store: Arc) -> Self { - Self { cal_store } - } -} - -#[async_trait] -impl ResourceService for CalendarObjectResourceService { - type PathComponents = CalendarObjectPathComponents; - type Resource = CalendarObjectResource; - type MemberType = CalendarObjectResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CalDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; - - async fn get_resource( - &self, - CalendarObjectPathComponents { - principal, - calendar_id, - object_id, - }: &Self::PathComponents, - ) -> Result { - let object = self - .cal_store - .get_object(principal, calendar_id, object_id) - .await?; - Ok(CalendarObjectResource { - object, - principal: principal.to_owned(), - }) - } - - async fn delete_resource( - &self, - CalendarObjectPathComponents { - principal, - calendar_id, - object_id, - }: &Self::PathComponents, - use_trashbin: bool, - ) -> Result<(), Self::Error> { - self.cal_store - .delete_object(principal, calendar_id, object_id, use_trashbin) - .await?; - Ok(()) - } -} - -impl AxumMethods for CalendarObjectResourceService { - fn get() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(get_event::, state); - Box::pin(Service::call(&mut service, req)) - }) - } - fn put() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(put_event::, state); - Box::pin(Service::call(&mut service, req)) - }) - } -} diff --git a/crates/caldav/src/calendar_object/service.rs b/crates/caldav/src/calendar_object/service.rs new file mode 100644 index 0000000..bd8106b --- /dev/null +++ b/crates/caldav/src/calendar_object/service.rs @@ -0,0 +1,113 @@ +use crate::{ + CalDavPrincipalUri, Error, + calendar_object::{ + methods::{get_event, put_event}, + resource::CalendarObjectResource, + }, +}; +use async_trait::async_trait; +use axum::{extract::Request, handler::Handler, response::Response}; +use futures_util::future::BoxFuture; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::{CalendarStore, auth::User}; +use serde::{Deserialize, Deserializer}; +use std::{convert::Infallible, sync::Arc}; +use tower::Service; + +#[derive(Debug, Clone, Deserialize)] +pub struct CalendarObjectPathComponents { + pub principal: String, + pub calendar_id: String, + #[serde(deserialize_with = "deserialize_ics_name")] + pub object_id: String, +} + +pub struct CalendarObjectResourceService { + pub(crate) cal_store: Arc, +} + +impl Clone for CalendarObjectResourceService { + fn clone(&self) -> Self { + Self { + cal_store: self.cal_store.clone(), + } + } +} + +impl CalendarObjectResourceService { + pub fn new(cal_store: Arc) -> Self { + Self { cal_store } + } +} + +#[async_trait] +impl ResourceService for CalendarObjectResourceService { + type PathComponents = CalendarObjectPathComponents; + type Resource = CalendarObjectResource; + type MemberType = CalendarObjectResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CalDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; + + async fn get_resource( + &self, + CalendarObjectPathComponents { + principal, + calendar_id, + object_id, + }: &Self::PathComponents, + ) -> Result { + let object = self + .cal_store + .get_object(principal, calendar_id, object_id) + .await?; + Ok(CalendarObjectResource { + object, + principal: principal.to_owned(), + }) + } + + async fn delete_resource( + &self, + CalendarObjectPathComponents { + principal, + calendar_id, + object_id, + }: &Self::PathComponents, + use_trashbin: bool, + ) -> Result<(), Self::Error> { + self.cal_store + .delete_object(principal, calendar_id, object_id, use_trashbin) + .await?; + Ok(()) + } +} + +impl AxumMethods for CalendarObjectResourceService { + fn get() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(get_event::, state); + Box::pin(Service::call(&mut service, req)) + }) + } + fn put() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(put_event::, state); + Box::pin(Service::call(&mut service, req)) + }) + } +} + +fn deserialize_ics_name<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let name: String = Deserialize::deserialize(deserializer)?; + if let Some(object_id) = name.strip_suffix(".ics") { + Ok(object_id.to_owned()) + } else { + Err(serde::de::Error::custom("Missing .ics extension")) + } +} diff --git a/crates/caldav/src/calendar_set/mod.rs b/crates/caldav/src/calendar_set/mod.rs index 22a6d9a..5b702d7 100644 --- a/crates/caldav/src/calendar_set/mod.rs +++ b/crates/caldav/src/calendar_set/mod.rs @@ -1,15 +1,14 @@ -use crate::calendar::resource::{CalendarResource, CalendarResourceService}; -use crate::{CalDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::Router; -use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; +use crate::Error; +use rustical_dav::extensions::CommonPropertiesExtension; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceName}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; use rustical_store::auth::User; -use rustical_store::{CalendarStore, SubscriptionStore}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use std::sync::Arc; + +mod service; +pub use service::*; +mod prop; +pub use prop::*; #[derive(Clone)] pub struct CalendarSetResource { @@ -24,12 +23,6 @@ impl ResourceName for CalendarSetResource { } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] -pub enum PrincipalPropWrapper { - Common(CommonPropertiesProp), -} - impl Resource for CalendarSetResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -67,77 +60,3 @@ impl Resource for CalendarSetResource { }) } } - -pub struct CalendarSetResourceService { - name: &'static str, - cal_store: Arc, - sub_store: Arc, -} - -impl Clone for CalendarSetResourceService { - fn clone(&self) -> Self { - Self { - name: self.name, - cal_store: self.cal_store.clone(), - sub_store: self.sub_store.clone(), - } - } -} - -impl CalendarSetResourceService { - pub fn new(name: &'static str, cal_store: Arc, sub_store: Arc) -> Self { - Self { - name, - cal_store, - sub_store, - } - } -} - -#[async_trait] -impl ResourceService for CalendarSetResourceService { - type PathComponents = (String,); - type MemberType = CalendarResource; - type Resource = CalendarSetResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CalDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol, calendar-access"; - - async fn get_resource( - &self, - (principal,): &Self::PathComponents, - ) -> Result { - Ok(CalendarSetResource { - principal: principal.to_owned(), - read_only: self.cal_store.is_read_only(), - name: self.name, - }) - } - - async fn get_members( - &self, - (principal,): &Self::PathComponents, - ) -> Result, Self::Error> { - let calendars = self.cal_store.get_calendars(principal).await?; - Ok(calendars - .into_iter() - .map(|cal| CalendarResource { - cal, - read_only: self.cal_store.is_read_only(), - }) - .collect()) - } - - fn axum_router(self) -> axum::Router { - Router::new() - .nest( - "/{calendar_id}", - CalendarResourceService::new(self.cal_store.clone(), self.sub_store.clone()) - .axum_router(), - ) - .route_service("/", self.axum_service()) - } -} -impl AxumMethods for CalendarSetResourceService {} diff --git a/crates/caldav/src/calendar_set/prop.rs b/crates/caldav/src/calendar_set/prop.rs new file mode 100644 index 0000000..589c2a4 --- /dev/null +++ b/crates/caldav/src/calendar_set/prop.rs @@ -0,0 +1,8 @@ +use rustical_dav::extensions::CommonPropertiesProp; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] +pub enum PrincipalPropWrapper { + Common(CommonPropertiesProp), +} diff --git a/crates/caldav/src/calendar_set/service.rs b/crates/caldav/src/calendar_set/service.rs new file mode 100644 index 0000000..68d15da --- /dev/null +++ b/crates/caldav/src/calendar_set/service.rs @@ -0,0 +1,84 @@ +use crate::calendar::CalendarResourceService; +use crate::calendar::resource::CalendarResource; +use crate::calendar_set::CalendarSetResource; +use crate::{CalDavPrincipalUri, Error}; +use async_trait::async_trait; +use axum::Router; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::auth::User; +use rustical_store::{CalendarStore, SubscriptionStore}; +use std::sync::Arc; + +pub struct CalendarSetResourceService { + name: &'static str, + cal_store: Arc, + sub_store: Arc, +} + +impl Clone for CalendarSetResourceService { + fn clone(&self) -> Self { + Self { + name: self.name, + cal_store: self.cal_store.clone(), + sub_store: self.sub_store.clone(), + } + } +} + +impl CalendarSetResourceService { + pub fn new(name: &'static str, cal_store: Arc, sub_store: Arc) -> Self { + Self { + name, + cal_store, + sub_store, + } + } +} + +#[async_trait] +impl ResourceService for CalendarSetResourceService { + type PathComponents = (String,); + type MemberType = CalendarResource; + type Resource = CalendarSetResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CalDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, extended-mkcol, calendar-access"; + + async fn get_resource( + &self, + (principal,): &Self::PathComponents, + ) -> Result { + Ok(CalendarSetResource { + principal: principal.to_owned(), + read_only: self.cal_store.is_read_only(), + name: self.name, + }) + } + + async fn get_members( + &self, + (principal,): &Self::PathComponents, + ) -> Result, Self::Error> { + let calendars = self.cal_store.get_calendars(principal).await?; + Ok(calendars + .into_iter() + .map(|cal| CalendarResource { + cal, + read_only: self.cal_store.is_read_only(), + }) + .collect()) + } + + fn axum_router(self) -> axum::Router { + Router::new() + .nest( + "/{calendar_id}", + CalendarResourceService::new(self.cal_store.clone(), self.sub_store.clone()) + .axum_router(), + ) + .route_service("/", self.axum_service()) + } +} +impl AxumMethods for CalendarSetResourceService {} diff --git a/crates/caldav/src/principal/mod.rs b/crates/caldav/src/principal/mod.rs index 22f8e26..87c340b 100644 --- a/crates/caldav/src/principal/mod.rs +++ b/crates/caldav/src/principal/mod.rs @@ -1,16 +1,14 @@ -use crate::calendar_set::{CalendarSetResource, CalendarSetResourceService}; -use crate::{CalDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::Router; -use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; +use crate::Error; +use rustical_dav::extensions::CommonPropertiesExtension; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceName}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; -use rustical_store::auth::user::PrincipalType; -use rustical_store::auth::{AuthenticationProvider, User}; -use rustical_store::{CalendarStore, SubscriptionStore}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use std::sync::Arc; +use rustical_store::auth::User; + +mod service; +pub use service::*; +mod prop; +pub use prop::*; #[derive(Clone)] pub struct PrincipalResource { @@ -24,37 +22,6 @@ impl ResourceName for PrincipalResource { } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] -pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] Vec); - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "PrincipalPropName")] -pub enum PrincipalProp { - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - Displayname(String), - - // Scheduling Extensions to CalDAV (RFC 6638) - #[xml(ns = "rustical_dav::namespace::NS_CALDAV", skip_deserializing)] - CalendarUserType(PrincipalType), - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - CalendarUserAddressSet(HrefElement), - - // WebDAV Access Control (RFC 3744) - #[xml(ns = "rustical_dav::namespace::NS_DAV", rename = b"principal-URL")] - PrincipalUrl(HrefElement), - - // CalDAV (RFC 4791) - #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] - CalendarHomeSet(CalendarHomeSet), -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] -pub enum PrincipalPropWrapper { - Principal(PrincipalProp), - Common(CommonPropertiesProp), -} - impl Resource for PrincipalResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -124,104 +91,3 @@ impl Resource for PrincipalResource { )) } } - -#[derive(Debug)] -pub struct PrincipalResourceService< - AP: AuthenticationProvider, - S: SubscriptionStore, - CS: CalendarStore, - BS: CalendarStore, -> { - pub(crate) auth_provider: Arc, - pub(crate) sub_store: Arc, - pub(crate) cal_store: Arc, - pub(crate) birthday_store: Arc, -} - -impl Clone - for PrincipalResourceService -{ - fn clone(&self) -> Self { - Self { - auth_provider: self.auth_provider.clone(), - sub_store: self.sub_store.clone(), - cal_store: self.cal_store.clone(), - birthday_store: self.birthday_store.clone(), - } - } -} - -#[async_trait] -impl - ResourceService for PrincipalResourceService -{ - type PathComponents = (String,); - type MemberType = CalendarSetResource; - type Resource = PrincipalResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CalDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; - - async fn get_resource( - &self, - (principal,): &Self::PathComponents, - ) -> Result { - let user = self - .auth_provider - .get_principal(principal) - .await? - .ok_or(crate::Error::NotFound)?; - Ok(PrincipalResource { - principal: user, - home_set: &["calendar", "birthdays"], - }) - } - - async fn get_members( - &self, - (principal,): &Self::PathComponents, - ) -> Result, Self::Error> { - Ok(vec![ - CalendarSetResource { - name: "calendar", - principal: principal.to_owned(), - read_only: false, - }, - CalendarSetResource { - name: "birthdays", - principal: principal.to_owned(), - read_only: true, - }, - ]) - } - - fn axum_router(self) -> axum::Router { - Router::new() - .nest( - "/calendar", - CalendarSetResourceService::new( - "calendar", - self.cal_store.clone(), - self.sub_store.clone(), - ) - .axum_router(), - ) - .nest( - "/birthdays", - CalendarSetResourceService::new( - "birthdays", - self.birthday_store.clone(), - self.sub_store.clone(), - ) - .axum_router(), - ) - .route_service("/", self.axum_service()) - } -} - -impl - AxumMethods for PrincipalResourceService -{ -} diff --git a/crates/caldav/src/principal/prop.rs b/crates/caldav/src/principal/prop.rs new file mode 100644 index 0000000..15dd133 --- /dev/null +++ b/crates/caldav/src/principal/prop.rs @@ -0,0 +1,34 @@ +use rustical_dav::{extensions::CommonPropertiesProp, xml::HrefElement}; +use rustical_store::auth::user::PrincipalType; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "PrincipalPropName")] +pub enum PrincipalProp { + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + Displayname(String), + + // Scheduling Extensions to CalDAV (RFC 6638) + #[xml(ns = "rustical_dav::namespace::NS_CALDAV", skip_deserializing)] + CalendarUserType(PrincipalType), + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + CalendarUserAddressSet(HrefElement), + + // WebDAV Access Control (RFC 3744) + #[xml(ns = "rustical_dav::namespace::NS_DAV", rename = b"principal-URL")] + PrincipalUrl(HrefElement), + + // CalDAV (RFC 4791) + #[xml(ns = "rustical_dav::namespace::NS_CALDAV")] + CalendarHomeSet(CalendarHomeSet), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] +pub enum PrincipalPropWrapper { + Principal(PrincipalProp), + Common(CommonPropertiesProp), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] +pub struct CalendarHomeSet(#[xml(ty = "untagged", flatten)] pub(super) Vec); diff --git a/crates/caldav/src/principal/service.rs b/crates/caldav/src/principal/service.rs new file mode 100644 index 0000000..ee6099a --- /dev/null +++ b/crates/caldav/src/principal/service.rs @@ -0,0 +1,110 @@ +use crate::calendar_set::{CalendarSetResource, CalendarSetResourceService}; +use crate::principal::PrincipalResource; +use crate::{CalDavPrincipalUri, Error}; +use async_trait::async_trait; +use axum::Router; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::auth::{AuthenticationProvider, User}; +use rustical_store::{CalendarStore, SubscriptionStore}; +use std::sync::Arc; + +#[derive(Debug)] +pub struct PrincipalResourceService< + AP: AuthenticationProvider, + S: SubscriptionStore, + CS: CalendarStore, + BS: CalendarStore, +> { + pub(crate) auth_provider: Arc, + pub(crate) sub_store: Arc, + pub(crate) cal_store: Arc, + pub(crate) birthday_store: Arc, +} + +impl Clone + for PrincipalResourceService +{ + fn clone(&self) -> Self { + Self { + auth_provider: self.auth_provider.clone(), + sub_store: self.sub_store.clone(), + cal_store: self.cal_store.clone(), + birthday_store: self.birthday_store.clone(), + } + } +} + +#[async_trait] +impl + ResourceService for PrincipalResourceService +{ + type PathComponents = (String,); + type MemberType = CalendarSetResource; + type Resource = PrincipalResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CalDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, calendar-access"; + + async fn get_resource( + &self, + (principal,): &Self::PathComponents, + ) -> Result { + let user = self + .auth_provider + .get_principal(principal) + .await? + .ok_or(crate::Error::NotFound)?; + Ok(PrincipalResource { + principal: user, + home_set: &["calendar", "birthdays"], + }) + } + + async fn get_members( + &self, + (principal,): &Self::PathComponents, + ) -> Result, Self::Error> { + Ok(vec![ + CalendarSetResource { + name: "calendar", + principal: principal.to_owned(), + read_only: false, + }, + CalendarSetResource { + name: "birthdays", + principal: principal.to_owned(), + read_only: true, + }, + ]) + } + + fn axum_router(self) -> axum::Router { + Router::new() + .nest( + "/calendar", + CalendarSetResourceService::new( + "calendar", + self.cal_store.clone(), + self.sub_store.clone(), + ) + .axum_router(), + ) + .nest( + "/birthdays", + CalendarSetResourceService::new( + "birthdays", + self.birthday_store.clone(), + self.sub_store.clone(), + ) + .axum_router(), + ) + .route_service("/", self.axum_service()) + } +} + +impl + AxumMethods for PrincipalResourceService +{ +} diff --git a/crates/carddav/src/address_object/methods.rs b/crates/carddav/src/address_object/methods.rs index faedbc4..5d76e3e 100644 --- a/crates/carddav/src/address_object/methods.rs +++ b/crates/carddav/src/address_object/methods.rs @@ -1,8 +1,6 @@ -use std::str::FromStr; - -use super::resource::AddressObjectPathComponents; +use super::AddressObjectPathComponents; +use super::AddressObjectResourceService; use crate::Error; -use crate::address_object::resource::AddressObjectResourceService; use crate::addressbook::resource::AddressbookResource; use axum::body::Body; use axum::extract::{Path, State}; @@ -15,6 +13,7 @@ use rustical_dav::resource::Resource; use rustical_ical::AddressObject; use rustical_store::AddressbookStore; use rustical_store::auth::User; +use std::str::FromStr; use tracing::instrument; #[instrument(skip(addr_store))] diff --git a/crates/carddav/src/address_object/mod.rs b/crates/carddav/src/address_object/mod.rs index 20447fe..93cd7f7 100644 --- a/crates/carddav/src/address_object/mod.rs +++ b/crates/carddav/src/address_object/mod.rs @@ -1,2 +1,6 @@ pub mod methods; pub mod resource; +mod service; +pub use service::*; +mod prop; +pub use prop::*; diff --git a/crates/carddav/src/address_object/prop.rs b/crates/carddav/src/address_object/prop.rs new file mode 100644 index 0000000..b2068c9 --- /dev/null +++ b/crates/carddav/src/address_object/prop.rs @@ -0,0 +1,23 @@ +use rustical_dav::extensions::CommonPropertiesProp; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "AddressObjectPropName")] +pub enum AddressObjectProp { + // WebDAV (RFC 2518) + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + Getetag(String), + #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] + Getcontenttype(&'static str), + + // CalDAV (RFC 4791) + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] + AddressData(String), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "AddressObjectPropWrapperName", untagged)] +pub enum AddressObjectPropWrapper { + AddressObject(AddressObjectProp), + Common(CommonPropertiesProp), +} diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index 91284d9..000733f 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -1,56 +1,19 @@ -use crate::{CardDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::{extract::Request, handler::Handler, response::Response}; -use derive_more::derive::{Constructor, From, Into}; -use futures_util::future::BoxFuture; +use crate::{ + Error, + address_object::{ + AddressObjectProp, AddressObjectPropName, AddressObjectPropWrapper, + AddressObjectPropWrapperName, + }, +}; +use derive_more::derive::{From, Into}; use rustical_dav::{ - extensions::{CommonPropertiesExtension, CommonPropertiesProp}, + extensions::CommonPropertiesExtension, privileges::UserPrivilegeSet, - resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}, + resource::{PrincipalUri, Resource, ResourceName}, xml::Resourcetype, }; use rustical_ical::AddressObject; -use rustical_store::{AddressbookStore, auth::User}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use serde::{Deserialize, Deserializer}; -use std::{convert::Infallible, sync::Arc}; -use tower::Service; - -use super::methods::{get_object, put_object}; - -#[derive(Constructor)] -pub struct AddressObjectResourceService { - pub(crate) addr_store: Arc, -} - -impl Clone for AddressObjectResourceService { - fn clone(&self) -> Self { - Self { - addr_store: self.addr_store.clone(), - } - } -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "AddressObjectPropName")] -pub enum AddressObjectProp { - // WebDAV (RFC 2518) - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - Getetag(String), - #[xml(ns = "rustical_dav::namespace::NS_DAV", skip_deserializing)] - Getcontenttype(&'static str), - - // CalDAV (RFC 4791) - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] - AddressData(String), -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "AddressObjectPropWrapperName", untagged)] -pub enum AddressObjectPropWrapper { - AddressObject(AddressObjectProp), - Common(CommonPropertiesProp), -} +use rustical_store::auth::User; #[derive(Clone, From, Into)] pub struct AddressObjectResource { @@ -113,84 +76,3 @@ impl Resource for AddressObjectResource { )) } } - -fn deserialize_vcf_name<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let name: String = Deserialize::deserialize(deserializer)?; - if let Some(object_id) = name.strip_suffix(".vcf") { - Ok(object_id.to_owned()) - } else { - Err(serde::de::Error::custom("Missing .vcf extension")) - } -} - -#[derive(Debug, Clone, Deserialize)] -pub struct AddressObjectPathComponents { - pub principal: String, - pub addressbook_id: String, - #[serde(deserialize_with = "deserialize_vcf_name")] - pub object_id: String, -} - -#[async_trait] -impl ResourceService for AddressObjectResourceService { - type PathComponents = AddressObjectPathComponents; - type Resource = AddressObjectResource; - type MemberType = AddressObjectResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CardDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, addressbook"; - - async fn get_resource( - &self, - AddressObjectPathComponents { - principal, - addressbook_id, - object_id, - }: &Self::PathComponents, - ) -> Result { - let object = self - .addr_store - .get_object(principal, addressbook_id, object_id, false) - .await?; - Ok(AddressObjectResource { - object, - principal: principal.to_owned(), - }) - } - - async fn delete_resource( - &self, - AddressObjectPathComponents { - principal, - addressbook_id, - object_id, - }: &Self::PathComponents, - use_trashbin: bool, - ) -> Result<(), Self::Error> { - self.addr_store - .delete_object(principal, addressbook_id, object_id, use_trashbin) - .await?; - Ok(()) - } -} - -impl AxumMethods for AddressObjectResourceService { - fn get() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(get_object::, state); - Box::pin(Service::call(&mut service, req)) - }) - } - - fn put() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(put_object::, state); - Box::pin(Service::call(&mut service, req)) - }) - } -} diff --git a/crates/carddav/src/address_object/service.rs b/crates/carddav/src/address_object/service.rs new file mode 100644 index 0000000..f81754f --- /dev/null +++ b/crates/carddav/src/address_object/service.rs @@ -0,0 +1,105 @@ +use super::methods::{get_object, put_object}; +use crate::{CardDavPrincipalUri, Error, address_object::resource::AddressObjectResource}; +use async_trait::async_trait; +use axum::{extract::Request, handler::Handler, response::Response}; +use derive_more::derive::Constructor; +use futures_util::future::BoxFuture; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::{AddressbookStore, auth::User}; +use serde::{Deserialize, Deserializer}; +use std::{convert::Infallible, sync::Arc}; +use tower::Service; + +#[derive(Constructor)] +pub struct AddressObjectResourceService { + pub(crate) addr_store: Arc, +} + +impl Clone for AddressObjectResourceService { + fn clone(&self) -> Self { + Self { + addr_store: self.addr_store.clone(), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct AddressObjectPathComponents { + pub principal: String, + pub addressbook_id: String, + #[serde(deserialize_with = "deserialize_vcf_name")] + pub object_id: String, +} + +#[async_trait] +impl ResourceService for AddressObjectResourceService { + type PathComponents = AddressObjectPathComponents; + type Resource = AddressObjectResource; + type MemberType = AddressObjectResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CardDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + + async fn get_resource( + &self, + AddressObjectPathComponents { + principal, + addressbook_id, + object_id, + }: &Self::PathComponents, + ) -> Result { + let object = self + .addr_store + .get_object(principal, addressbook_id, object_id, false) + .await?; + Ok(AddressObjectResource { + object, + principal: principal.to_owned(), + }) + } + + async fn delete_resource( + &self, + AddressObjectPathComponents { + principal, + addressbook_id, + object_id, + }: &Self::PathComponents, + use_trashbin: bool, + ) -> Result<(), Self::Error> { + self.addr_store + .delete_object(principal, addressbook_id, object_id, use_trashbin) + .await?; + Ok(()) + } +} + +impl AxumMethods for AddressObjectResourceService { + fn get() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(get_object::, state); + Box::pin(Service::call(&mut service, req)) + }) + } + + fn put() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(put_object::, state); + Box::pin(Service::call(&mut service, req)) + }) + } +} + +fn deserialize_vcf_name<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let name: String = Deserialize::deserialize(deserializer)?; + if let Some(object_id) = name.strip_suffix(".vcf") { + Ok(object_id.to_owned()) + } else { + Err(serde::de::Error::custom("Missing .vcf extension")) + } +} diff --git a/crates/carddav/src/addressbook/methods/mkcol.rs b/crates/carddav/src/addressbook/methods/mkcol.rs index fbf8e4e..e640b65 100644 --- a/crates/carddav/src/addressbook/methods/mkcol.rs +++ b/crates/carddav/src/addressbook/methods/mkcol.rs @@ -1,4 +1,4 @@ -use crate::{Error, addressbook::resource::AddressbookResourceService}; +use crate::{Error, addressbook::AddressbookResourceService}; use axum::{ extract::{Path, State}, response::{IntoResponse, Response}, diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 222bd68..c7c0760 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -1,7 +1,7 @@ use crate::{ Error, - address_object::resource::{ - AddressObjectPropWrapper, AddressObjectPropWrapperName, AddressObjectResource, + address_object::{ + AddressObjectPropWrapper, AddressObjectPropWrapperName, resource::AddressObjectResource, }, }; use http::StatusCode; diff --git a/crates/carddav/src/addressbook/methods/report/mod.rs b/crates/carddav/src/addressbook/methods/report/mod.rs index 82594a1..3ace036 100644 --- a/crates/carddav/src/addressbook/methods/report/mod.rs +++ b/crates/carddav/src/addressbook/methods/report/mod.rs @@ -1,6 +1,6 @@ use crate::{ - CardDavPrincipalUri, Error, address_object::resource::AddressObjectPropWrapperName, - addressbook::resource::AddressbookResourceService, + CardDavPrincipalUri, Error, address_object::AddressObjectPropWrapperName, + addressbook::AddressbookResourceService, }; use addressbook_multiget::{AddressbookMultigetRequest, handle_addressbook_multiget}; use axum::{ @@ -81,7 +81,7 @@ pub async fn route_report_addressbook), + + // CardDAV (RFC 6352) + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] + AddressbookDescription(Option), + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", skip_deserializing)] + SupportedAddressData(SupportedAddressData), + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", skip_deserializing)] + SupportedReportSet(SupportedReportSet), + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + MaxResourceSize(i64), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "AddressbookPropWrapperName", untagged)] +pub enum AddressbookPropWrapper { + Addressbook(AddressbookProp), + SyncToken(SyncTokenExtensionProp), + DavPush(DavPushExtensionProp), + Common(CommonPropertiesProp), +} #[derive(Debug, Clone, XmlSerialize, PartialEq)] pub struct AddressDataType { diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 7d5b71f..80d2ad7 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -1,78 +1,16 @@ -use super::methods::mkcol::route_mkcol; -use super::methods::report::route_report_addressbook; use super::prop::{SupportedAddressData, SupportedReportSet}; -use crate::address_object::resource::{AddressObjectResource, AddressObjectResourceService}; -use crate::{CardDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::Router; -use axum::extract::Request; -use axum::handler::Handler; -use axum::response::Response; -use derive_more::derive::{From, Into}; -use futures_util::future::BoxFuture; -use rustical_dav::extensions::{ - CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp, +use crate::Error; +use crate::addressbook::prop::{ + AddressbookProp, AddressbookPropName, AddressbookPropWrapper, AddressbookPropWrapperName, }; +use derive_more::derive::{From, Into}; +use rustical_dav::extensions::{CommonPropertiesExtension, SyncTokenExtension}; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceName}; use rustical_dav::xml::{Resourcetype, ResourcetypeInner}; -use rustical_dav_push::{DavPushExtension, DavPushExtensionProp}; +use rustical_dav_push::DavPushExtension; +use rustical_store::Addressbook; use rustical_store::auth::User; -use rustical_store::{Addressbook, AddressbookStore, SubscriptionStore}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use std::convert::Infallible; -use std::sync::Arc; -use tower::Service; - -pub struct AddressbookResourceService { - pub(crate) addr_store: Arc, - pub(crate) sub_store: Arc, -} - -impl AddressbookResourceService { - pub fn new(addr_store: Arc, sub_store: Arc) -> Self { - Self { - addr_store, - sub_store, - } - } -} - -impl Clone for AddressbookResourceService { - fn clone(&self) -> Self { - Self { - addr_store: self.addr_store.clone(), - sub_store: self.sub_store.clone(), - } - } -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "AddressbookPropName")] -pub enum AddressbookProp { - // WebDAV (RFC 2518) - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - Displayname(Option), - - // CardDAV (RFC 6352) - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] - AddressbookDescription(Option), - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", skip_deserializing)] - SupportedAddressData(SupportedAddressData), - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV", skip_deserializing)] - SupportedReportSet(SupportedReportSet), - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - MaxResourceSize(i64), -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "AddressbookPropWrapperName", untagged)] -pub enum AddressbookPropWrapper { - Addressbook(AddressbookProp), - SyncToken(SyncTokenExtensionProp), - DavPush(DavPushExtensionProp), - Common(CommonPropertiesProp), -} #[derive(Clone, Debug, From, Into)] pub struct AddressbookResource(pub(crate) Addressbook); @@ -205,92 +143,3 @@ impl Resource for AddressbookResource { )) } } - -#[async_trait] -impl ResourceService - for AddressbookResourceService -{ - type MemberType = AddressObjectResource; - type PathComponents = (String, String); // principal, addressbook_id - type Resource = AddressbookResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CardDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, addressbook"; - - async fn get_resource( - &self, - (principal, addressbook_id): &Self::PathComponents, - ) -> Result { - let addressbook = self - .addr_store - .get_addressbook(principal, addressbook_id, false) - .await - .map_err(|_e| Error::NotFound)?; - Ok(addressbook.into()) - } - - async fn get_members( - &self, - (principal, addressbook_id): &Self::PathComponents, - ) -> Result, Self::Error> { - Ok(self - .addr_store - .get_objects(principal, addressbook_id) - .await? - .into_iter() - .map(|object| AddressObjectResource { - object, - principal: principal.to_owned(), - }) - .collect()) - } - - async fn save_resource( - &self, - (principal, addressbook_id): &Self::PathComponents, - file: Self::Resource, - ) -> Result<(), Self::Error> { - self.addr_store - .update_addressbook(principal.to_owned(), addressbook_id.to_owned(), file.into()) - .await?; - Ok(()) - } - - async fn delete_resource( - &self, - (principal, addressbook_id): &Self::PathComponents, - use_trashbin: bool, - ) -> Result<(), Self::Error> { - self.addr_store - .delete_addressbook(principal, addressbook_id, use_trashbin) - .await?; - Ok(()) - } - - fn axum_router(self) -> Router { - Router::new() - .nest( - "/{object_id}", - AddressObjectResourceService::new(self.addr_store.clone()).axum_router(), - ) - .route_service("/", self.axum_service()) - } -} - -impl AxumMethods for AddressbookResourceService { - fn report() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(route_report_addressbook::, state); - Box::pin(Service::call(&mut service, req)) - }) - } - - fn mkcol() -> Option BoxFuture<'static, Result>> { - Some(|state, req| { - let mut service = Handler::with_state(route_mkcol::, state); - Box::pin(Service::call(&mut service, req)) - }) - } -} diff --git a/crates/carddav/src/addressbook/service.rs b/crates/carddav/src/addressbook/service.rs new file mode 100644 index 0000000..71daaec --- /dev/null +++ b/crates/carddav/src/addressbook/service.rs @@ -0,0 +1,130 @@ +use super::methods::mkcol::route_mkcol; +use super::methods::report::route_report_addressbook; +use crate::address_object::AddressObjectResourceService; +use crate::address_object::resource::AddressObjectResource; +use crate::addressbook::resource::AddressbookResource; +use crate::{CardDavPrincipalUri, Error}; +use async_trait::async_trait; +use axum::Router; +use axum::extract::Request; +use axum::handler::Handler; +use axum::response::Response; +use futures_util::future::BoxFuture; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::auth::User; +use rustical_store::{AddressbookStore, SubscriptionStore}; +use std::convert::Infallible; +use std::sync::Arc; +use tower::Service; + +pub struct AddressbookResourceService { + pub(crate) addr_store: Arc, + pub(crate) sub_store: Arc, +} + +impl AddressbookResourceService { + pub fn new(addr_store: Arc, sub_store: Arc) -> Self { + Self { + addr_store, + sub_store, + } + } +} + +impl Clone for AddressbookResourceService { + fn clone(&self) -> Self { + Self { + addr_store: self.addr_store.clone(), + sub_store: self.sub_store.clone(), + } + } +} + +#[async_trait] +impl ResourceService + for AddressbookResourceService +{ + type MemberType = AddressObjectResource; + type PathComponents = (String, String); // principal, addressbook_id + type Resource = AddressbookResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CardDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + + async fn get_resource( + &self, + (principal, addressbook_id): &Self::PathComponents, + ) -> Result { + let addressbook = self + .addr_store + .get_addressbook(principal, addressbook_id, false) + .await + .map_err(|_e| Error::NotFound)?; + Ok(addressbook.into()) + } + + async fn get_members( + &self, + (principal, addressbook_id): &Self::PathComponents, + ) -> Result, Self::Error> { + Ok(self + .addr_store + .get_objects(principal, addressbook_id) + .await? + .into_iter() + .map(|object| AddressObjectResource { + object, + principal: principal.to_owned(), + }) + .collect()) + } + + async fn save_resource( + &self, + (principal, addressbook_id): &Self::PathComponents, + file: Self::Resource, + ) -> Result<(), Self::Error> { + self.addr_store + .update_addressbook(principal.to_owned(), addressbook_id.to_owned(), file.into()) + .await?; + Ok(()) + } + + async fn delete_resource( + &self, + (principal, addressbook_id): &Self::PathComponents, + use_trashbin: bool, + ) -> Result<(), Self::Error> { + self.addr_store + .delete_addressbook(principal, addressbook_id, use_trashbin) + .await?; + Ok(()) + } + + fn axum_router(self) -> Router { + Router::new() + .nest( + "/{object_id}", + AddressObjectResourceService::new(self.addr_store.clone()).axum_router(), + ) + .route_service("/", self.axum_service()) + } +} + +impl AxumMethods for AddressbookResourceService { + fn report() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(route_report_addressbook::, state); + Box::pin(Service::call(&mut service, req)) + }) + } + + fn mkcol() -> Option BoxFuture<'static, Result>> { + Some(|state, req| { + let mut service = Handler::with_state(route_mkcol::, state); + Box::pin(Service::call(&mut service, req)) + }) + } +} diff --git a/crates/carddav/src/principal/mod.rs b/crates/carddav/src/principal/mod.rs index b028f97..7ffcd18 100644 --- a/crates/carddav/src/principal/mod.rs +++ b/crates/carddav/src/principal/mod.rs @@ -1,49 +1,14 @@ -use crate::addressbook::resource::{AddressbookResource, AddressbookResourceService}; -use crate::{CardDavPrincipalUri, Error}; -use async_trait::async_trait; -use axum::Router; -use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp}; +use crate::Error; +use rustical_dav::extensions::CommonPropertiesExtension; use rustical_dav::privileges::UserPrivilegeSet; -use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceName, ResourceService}; +use rustical_dav::resource::{PrincipalUri, Resource, ResourceName}; use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner}; -use rustical_store::auth::{AuthenticationProvider, User}; -use rustical_store::{AddressbookStore, SubscriptionStore}; -use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; -use std::sync::Arc; +use rustical_store::auth::User; -pub struct PrincipalResourceService< - A: AddressbookStore, - AP: AuthenticationProvider, - S: SubscriptionStore, -> { - addr_store: Arc, - auth_provider: Arc, - sub_store: Arc, -} - -impl Clone - for PrincipalResourceService -{ - fn clone(&self) -> Self { - Self { - addr_store: self.addr_store.clone(), - auth_provider: self.auth_provider.clone(), - sub_store: self.sub_store.clone(), - } - } -} - -impl - PrincipalResourceService -{ - pub fn new(addr_store: Arc, auth_provider: Arc, sub_store: Arc) -> Self { - Self { - addr_store, - auth_provider, - sub_store, - } - } -} +mod service; +pub use service::*; +mod prop; +pub use prop::*; #[derive(Debug, Clone)] pub struct PrincipalResource { @@ -56,34 +21,6 @@ impl ResourceName for PrincipalResource { } } -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] -pub struct AddressbookHomeSet(#[xml(ty = "untagged", flatten)] Vec); - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "PrincipalPropName")] -pub enum PrincipalProp { - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - Displayname(String), - - // WebDAV Access Control (RFC 3744) - #[xml(rename = b"principal-URL")] - #[xml(ns = "rustical_dav::namespace::NS_DAV")] - PrincipalUrl(HrefElement), - - // CardDAV (RFC 6352) - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] - AddressbookHomeSet(AddressbookHomeSet), - #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] - PrincipalAddress(Option), -} - -#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] -#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] -pub enum PrincipalPropWrapper { - Principal(PrincipalProp), - Common(CommonPropertiesProp), -} - impl Resource for PrincipalResource { type Prop = PrincipalPropWrapper; type Error = Error; @@ -145,55 +82,3 @@ impl Resource for PrincipalResource { )) } } - -#[async_trait] -impl ResourceService - for PrincipalResourceService -{ - type PathComponents = (String,); - type MemberType = AddressbookResource; - type Resource = PrincipalResource; - type Error = Error; - type Principal = User; - type PrincipalUri = CardDavPrincipalUri; - - const DAV_HEADER: &str = "1, 3, access-control, addressbook"; - - async fn get_resource( - &self, - (principal,): &Self::PathComponents, - ) -> Result { - let user = self - .auth_provider - .get_principal(principal) - .await? - .ok_or(crate::Error::NotFound)?; - Ok(PrincipalResource { principal: user }) - } - - async fn get_members( - &self, - (principal,): &Self::PathComponents, - ) -> Result, Self::Error> { - let addressbooks = self.addr_store.get_addressbooks(principal).await?; - Ok(addressbooks - .into_iter() - .map(AddressbookResource::from) - .collect()) - } - - fn axum_router(self) -> Router { - Router::new() - .nest( - "/{addressbook_id}", - AddressbookResourceService::new(self.addr_store.clone(), self.sub_store.clone()) - .axum_router(), - ) - .route_service("/", self.axum_service()) - } -} - -impl AxumMethods - for PrincipalResourceService -{ -} diff --git a/crates/carddav/src/principal/prop.rs b/crates/carddav/src/principal/prop.rs new file mode 100644 index 0000000..6f98ffc --- /dev/null +++ b/crates/carddav/src/principal/prop.rs @@ -0,0 +1,30 @@ +use rustical_dav::{extensions::CommonPropertiesProp, xml::HrefElement}; +use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize}; + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone)] +pub struct AddressbookHomeSet(#[xml(ty = "untagged", flatten)] pub(super) Vec); + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "PrincipalPropName")] +pub enum PrincipalProp { + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + Displayname(String), + + // WebDAV Access Control (RFC 3744) + #[xml(rename = b"principal-URL")] + #[xml(ns = "rustical_dav::namespace::NS_DAV")] + PrincipalUrl(HrefElement), + + // CardDAV (RFC 6352) + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] + AddressbookHomeSet(AddressbookHomeSet), + #[xml(ns = "rustical_dav::namespace::NS_CARDDAV")] + PrincipalAddress(Option), +} + +#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)] +#[xml(unit_variants_ident = "PrincipalPropWrapperName", untagged)] +pub enum PrincipalPropWrapper { + Principal(PrincipalProp), + Common(CommonPropertiesProp), +} diff --git a/crates/carddav/src/principal/service.rs b/crates/carddav/src/principal/service.rs new file mode 100644 index 0000000..5edd65f --- /dev/null +++ b/crates/carddav/src/principal/service.rs @@ -0,0 +1,96 @@ +use crate::addressbook::AddressbookResourceService; +use crate::addressbook::resource::AddressbookResource; +use crate::principal::PrincipalResource; +use crate::{CardDavPrincipalUri, Error}; +use async_trait::async_trait; +use axum::Router; +use rustical_dav::resource::{AxumMethods, ResourceService}; +use rustical_store::auth::{AuthenticationProvider, User}; +use rustical_store::{AddressbookStore, SubscriptionStore}; +use std::sync::Arc; + +pub struct PrincipalResourceService< + A: AddressbookStore, + AP: AuthenticationProvider, + S: SubscriptionStore, +> { + addr_store: Arc, + auth_provider: Arc, + sub_store: Arc, +} + +impl Clone + for PrincipalResourceService +{ + fn clone(&self) -> Self { + Self { + addr_store: self.addr_store.clone(), + auth_provider: self.auth_provider.clone(), + sub_store: self.sub_store.clone(), + } + } +} + +impl + PrincipalResourceService +{ + pub fn new(addr_store: Arc, auth_provider: Arc, sub_store: Arc) -> Self { + Self { + addr_store, + auth_provider, + sub_store, + } + } +} + +#[async_trait] +impl ResourceService + for PrincipalResourceService +{ + type PathComponents = (String,); + type MemberType = AddressbookResource; + type Resource = PrincipalResource; + type Error = Error; + type Principal = User; + type PrincipalUri = CardDavPrincipalUri; + + const DAV_HEADER: &str = "1, 3, access-control, addressbook"; + + async fn get_resource( + &self, + (principal,): &Self::PathComponents, + ) -> Result { + let user = self + .auth_provider + .get_principal(principal) + .await? + .ok_or(crate::Error::NotFound)?; + Ok(PrincipalResource { principal: user }) + } + + async fn get_members( + &self, + (principal,): &Self::PathComponents, + ) -> Result, Self::Error> { + let addressbooks = self.addr_store.get_addressbooks(principal).await?; + Ok(addressbooks + .into_iter() + .map(AddressbookResource::from) + .collect()) + } + + fn axum_router(self) -> Router { + Router::new() + .nest( + "/{addressbook_id}", + AddressbookResourceService::new(self.addr_store.clone(), self.sub_store.clone()) + .axum_router(), + ) + .route_service("/", self.axum_service()) + } +} + +impl AxumMethods + for PrincipalResourceService +{ +}