mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 09:22:26 +00:00
caldav: Merge calendar store and birthday store into combined store
This commit is contained in:
@@ -59,7 +59,7 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
|
|||||||
let calendar = self.cal_store.get_calendar(principal, cal_id).await?;
|
let calendar = self.cal_store.get_calendar(principal, cal_id).await?;
|
||||||
Ok(CalendarResource {
|
Ok(CalendarResource {
|
||||||
cal: calendar,
|
cal: calendar,
|
||||||
read_only: self.cal_store.is_read_only(),
|
read_only: self.cal_store.is_read_only(cal_id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
use crate::Error;
|
|
||||||
use rustical_dav::extensions::CommonPropertiesExtension;
|
|
||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
|
||||||
use rustical_dav::resource::{PrincipalUri, Resource, ResourceName};
|
|
||||||
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
|
|
||||||
use rustical_store::auth::User;
|
|
||||||
|
|
||||||
mod service;
|
|
||||||
pub use service::*;
|
|
||||||
mod prop;
|
|
||||||
pub use prop::*;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct CalendarSetResource {
|
|
||||||
pub(crate) principal: String,
|
|
||||||
pub(crate) read_only: bool,
|
|
||||||
pub(crate) name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResourceName for CalendarSetResource {
|
|
||||||
fn get_name(&self) -> String {
|
|
||||||
self.name.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resource for CalendarSetResource {
|
|
||||||
type Prop = PrincipalPropWrapper;
|
|
||||||
type Error = Error;
|
|
||||||
type Principal = User;
|
|
||||||
|
|
||||||
const IS_COLLECTION: bool = true;
|
|
||||||
|
|
||||||
fn get_resourcetype(&self) -> Resourcetype {
|
|
||||||
Resourcetype(&[ResourcetypeInner(
|
|
||||||
Some(rustical_dav::namespace::NS_DAV),
|
|
||||||
"collection",
|
|
||||||
)])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_prop(
|
|
||||||
&self,
|
|
||||||
puri: &impl PrincipalUri,
|
|
||||||
user: &User,
|
|
||||||
prop: &PrincipalPropWrapperName,
|
|
||||||
) -> Result<Self::Prop, Self::Error> {
|
|
||||||
Ok(match prop {
|
|
||||||
PrincipalPropWrapperName::Common(prop) => PrincipalPropWrapper::Common(
|
|
||||||
<Self as CommonPropertiesExtension>::get_prop(self, puri, user, prop)?,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_owner(&self) -> Option<&str> {
|
|
||||||
Some(&self.principal)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> {
|
|
||||||
Ok(if self.read_only {
|
|
||||||
UserPrivilegeSet::owner_read(user.is_principal(&self.principal))
|
|
||||||
} else {
|
|
||||||
UserPrivilegeSet::owner_only(user.is_principal(&self.principal))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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),
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
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<C: CalendarStore, S: SubscriptionStore> {
|
|
||||||
name: &'static str,
|
|
||||||
cal_store: Arc<C>,
|
|
||||||
sub_store: Arc<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> Clone for CalendarSetResourceService<C, S> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
name: self.name,
|
|
||||||
cal_store: self.cal_store.clone(),
|
|
||||||
sub_store: self.sub_store.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> CalendarSetResourceService<C, S> {
|
|
||||||
pub fn new(name: &'static str, cal_store: Arc<C>, sub_store: Arc<S>) -> Self {
|
|
||||||
Self {
|
|
||||||
name,
|
|
||||||
cal_store,
|
|
||||||
sub_store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetResourceService<C, S> {
|
|
||||||
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<Self::Resource, Self::Error> {
|
|
||||||
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<Vec<Self::MemberType>, 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<State: Send + Sync + Clone + 'static>(self) -> axum::Router<State> {
|
|
||||||
Router::new()
|
|
||||||
.nest(
|
|
||||||
"/{calendar_id}",
|
|
||||||
CalendarResourceService::new(self.cal_store.clone(), self.sub_store.clone())
|
|
||||||
.axum_router(),
|
|
||||||
)
|
|
||||||
.route_service("/", self.axum_service())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<C: CalendarStore, S: SubscriptionStore> AxumMethods for CalendarSetResourceService<C, S> {}
|
|
||||||
@@ -7,12 +7,11 @@ use rustical_dav::resource::{PrincipalUri, ResourceService};
|
|||||||
use rustical_dav::resources::RootResourceService;
|
use rustical_dav::resources::RootResourceService;
|
||||||
use rustical_store::auth::middleware::AuthenticationLayer;
|
use rustical_store::auth::middleware::AuthenticationLayer;
|
||||||
use rustical_store::auth::{AuthenticationProvider, User};
|
use rustical_store::auth::{AuthenticationProvider, User};
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
use rustical_store::{CalendarStore, SubscriptionStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod calendar;
|
pub mod calendar;
|
||||||
pub mod calendar_object;
|
pub mod calendar_object;
|
||||||
pub mod calendar_set;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod principal;
|
pub mod principal;
|
||||||
// mod subscription;
|
// mod subscription;
|
||||||
@@ -28,23 +27,15 @@ impl PrincipalUri for CalDavPrincipalUri {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn caldav_router<
|
pub fn caldav_router<AP: AuthenticationProvider, C: CalendarStore, S: SubscriptionStore>(
|
||||||
AP: AuthenticationProvider,
|
|
||||||
AS: AddressbookStore,
|
|
||||||
C: CalendarStore,
|
|
||||||
S: SubscriptionStore,
|
|
||||||
>(
|
|
||||||
prefix: &'static str,
|
prefix: &'static str,
|
||||||
auth_provider: Arc<AP>,
|
auth_provider: Arc<AP>,
|
||||||
store: Arc<C>,
|
store: Arc<C>,
|
||||||
addr_store: Arc<AS>,
|
|
||||||
subscription_store: Arc<S>,
|
subscription_store: Arc<S>,
|
||||||
) -> Router {
|
) -> Router {
|
||||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
|
||||||
let principal_service = PrincipalResourceService {
|
let principal_service = PrincipalResourceService {
|
||||||
auth_provider: auth_provider.clone(),
|
auth_provider: auth_provider.clone(),
|
||||||
sub_store: subscription_store.clone(),
|
sub_store: subscription_store.clone(),
|
||||||
birthday_store: birthday_store.clone(),
|
|
||||||
cal_store: store.clone(),
|
cal_store: store.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::Error;
|
|||||||
use rustical_dav::extensions::CommonPropertiesExtension;
|
use rustical_dav::extensions::CommonPropertiesExtension;
|
||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
use rustical_dav::privileges::UserPrivilegeSet;
|
||||||
use rustical_dav::resource::{PrincipalUri, Resource, ResourceName};
|
use rustical_dav::resource::{PrincipalUri, Resource, ResourceName};
|
||||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
|
||||||
use rustical_store::auth::User;
|
use rustical_store::auth::User;
|
||||||
|
|
||||||
mod service;
|
mod service;
|
||||||
@@ -13,7 +13,6 @@ pub use prop::*;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PrincipalResource {
|
pub struct PrincipalResource {
|
||||||
principal: User,
|
principal: User,
|
||||||
home_set: &'static [&'static str],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceName for PrincipalResource {
|
impl ResourceName for PrincipalResource {
|
||||||
@@ -47,12 +46,7 @@ impl Resource for PrincipalResource {
|
|||||||
let home_set = CalendarHomeSet(
|
let home_set = CalendarHomeSet(
|
||||||
user.memberships()
|
user.memberships()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|principal| puri.principal_uri(principal))
|
.map(|principal| puri.principal_uri(principal).into())
|
||||||
.flat_map(|principal_url| {
|
|
||||||
self.home_set.iter().map(move |&home_name| {
|
|
||||||
HrefElement::new(format!("{}{}/", &principal_url, home_name))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::calendar_set::{CalendarSetResource, CalendarSetResourceService};
|
use crate::calendar::CalendarResourceService;
|
||||||
|
use crate::calendar::resource::CalendarResource;
|
||||||
use crate::principal::PrincipalResource;
|
use crate::principal::PrincipalResource;
|
||||||
use crate::{CalDavPrincipalUri, Error};
|
use crate::{CalDavPrincipalUri, Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -13,33 +14,30 @@ pub struct PrincipalResourceService<
|
|||||||
AP: AuthenticationProvider,
|
AP: AuthenticationProvider,
|
||||||
S: SubscriptionStore,
|
S: SubscriptionStore,
|
||||||
CS: CalendarStore,
|
CS: CalendarStore,
|
||||||
BS: CalendarStore,
|
|
||||||
> {
|
> {
|
||||||
pub(crate) auth_provider: Arc<AP>,
|
pub(crate) auth_provider: Arc<AP>,
|
||||||
pub(crate) sub_store: Arc<S>,
|
pub(crate) sub_store: Arc<S>,
|
||||||
pub(crate) cal_store: Arc<CS>,
|
pub(crate) cal_store: Arc<CS>,
|
||||||
pub(crate) birthday_store: Arc<BS>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore> Clone
|
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> Clone
|
||||||
for PrincipalResourceService<AP, S, CS, BS>
|
for PrincipalResourceService<AP, S, CS>
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
auth_provider: self.auth_provider.clone(),
|
auth_provider: self.auth_provider.clone(),
|
||||||
sub_store: self.sub_store.clone(),
|
sub_store: self.sub_store.clone(),
|
||||||
cal_store: self.cal_store.clone(),
|
cal_store: self.cal_store.clone(),
|
||||||
birthday_store: self.birthday_store.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> ResourceService
|
||||||
ResourceService for PrincipalResourceService<AP, S, CS, BS>
|
for PrincipalResourceService<AP, S, CS>
|
||||||
{
|
{
|
||||||
type PathComponents = (String,);
|
type PathComponents = (String,);
|
||||||
type MemberType = CalendarSetResource;
|
type MemberType = CalendarResource;
|
||||||
type Resource = PrincipalResource;
|
type Resource = PrincipalResource;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Principal = User;
|
type Principal = User;
|
||||||
@@ -56,55 +54,36 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
|
|||||||
.get_principal(principal)
|
.get_principal(principal)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(crate::Error::NotFound)?;
|
.ok_or(crate::Error::NotFound)?;
|
||||||
Ok(PrincipalResource {
|
Ok(PrincipalResource { principal: user })
|
||||||
principal: user,
|
|
||||||
home_set: &["calendar", "birthdays"],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_members(
|
async fn get_members(
|
||||||
&self,
|
&self,
|
||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
) -> Result<Vec<Self::MemberType>, Self::Error> {
|
) -> Result<Vec<Self::MemberType>, Self::Error> {
|
||||||
Ok(vec![
|
let calendars = self.cal_store.get_calendars(principal).await?;
|
||||||
CalendarSetResource {
|
|
||||||
name: "calendar",
|
Ok(calendars
|
||||||
principal: principal.to_owned(),
|
.into_iter()
|
||||||
read_only: false,
|
.map(|cal| CalendarResource {
|
||||||
},
|
read_only: self.cal_store.is_read_only(&cal.id),
|
||||||
CalendarSetResource {
|
cal,
|
||||||
name: "birthdays",
|
})
|
||||||
principal: principal.to_owned(),
|
.collect())
|
||||||
read_only: true,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axum_router<State: Send + Sync + Clone + 'static>(self) -> axum::Router<State> {
|
fn axum_router<State: Send + Sync + Clone + 'static>(self) -> axum::Router<State> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.nest(
|
.nest(
|
||||||
"/calendar",
|
"/{calendar_id}",
|
||||||
CalendarSetResourceService::new(
|
CalendarResourceService::new(self.cal_store.clone(), self.sub_store.clone())
|
||||||
"calendar",
|
.axum_router(),
|
||||||
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())
|
.route_service("/", self.axum_service())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> AxumMethods
|
||||||
AxumMethods for PrincipalResourceService<AP, S, CS, BS>
|
for PrincipalResourceService<AP, S, CS>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,5 +80,5 @@ pub trait CalendarStore: Send + Sync + 'static {
|
|||||||
object_id: &str,
|
object_id: &str,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
fn is_read_only(&self) -> bool;
|
fn is_read_only(&self, cal_id: &str) -> bool;
|
||||||
}
|
}
|
||||||
|
|||||||
240
crates/store/src/combined_calendar_store.rs
Normal file
240
crates/store/src/combined_calendar_store.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use derive_more::Constructor;
|
||||||
|
use rustical_ical::CalendarObject;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Calendar, CalendarStore, Error, calendar_store::CalendarQuery,
|
||||||
|
contact_birthday_store::BIRTHDAYS_PREFIX,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Constructor)]
|
||||||
|
pub struct CombinedCalendarStore<CS: CalendarStore, BS: CalendarStore> {
|
||||||
|
cal_store: Arc<CS>,
|
||||||
|
birthday_store: Arc<BS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<CS: CalendarStore, BS: CalendarStore> Clone for CombinedCalendarStore<CS, BS> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
cal_store: self.cal_store.clone(),
|
||||||
|
birthday_store: self.birthday_store.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<CS: CalendarStore, BS: CalendarStore> CalendarStore for CombinedCalendarStore<CS, BS> {
|
||||||
|
#[inline]
|
||||||
|
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
|
||||||
|
if id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store.get_calendar(principal, id).await
|
||||||
|
} else {
|
||||||
|
self.cal_store.get_calendar(principal, id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn update_calendar(
|
||||||
|
&self,
|
||||||
|
principal: String,
|
||||||
|
id: String,
|
||||||
|
calendar: Calendar,
|
||||||
|
) -> Result<(), crate::Error> {
|
||||||
|
if id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.update_calendar(principal, id, calendar)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.update_calendar(principal, id, calendar)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error> {
|
||||||
|
if calendar.id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
Err(Error::ReadOnly)
|
||||||
|
} else {
|
||||||
|
self.cal_store.insert_calendar(calendar).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||||
|
Ok([
|
||||||
|
self.cal_store.get_calendars(principal).await?,
|
||||||
|
self.birthday_store.get_calendars(principal).await?,
|
||||||
|
]
|
||||||
|
.concat())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn delete_object(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
object_id: &str,
|
||||||
|
use_trashbin: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
Err(Error::ReadOnly)
|
||||||
|
} else {
|
||||||
|
self.birthday_store
|
||||||
|
.delete_object(principal, cal_id, object_id, use_trashbin)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn get_object(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
object_id: &str,
|
||||||
|
) -> Result<CalendarObject, Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.get_object(principal, cal_id, object_id)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.get_object(principal, cal_id, object_id)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn sync_changes(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
synctoken: i64,
|
||||||
|
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.sync_changes(principal, cal_id, synctoken)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.sync_changes(principal, cal_id, synctoken)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn get_objects(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
) -> Result<Vec<CalendarObject>, Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store.get_objects(principal, cal_id).await
|
||||||
|
} else {
|
||||||
|
self.cal_store.get_objects(principal, cal_id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn calendar_query(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
query: CalendarQuery,
|
||||||
|
) -> Result<Vec<CalendarObject>, Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.calendar_query(principal, cal_id, query)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.calendar_query(principal, cal_id, query)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn restore_calendar(&self, principal: &str, name: &str) -> Result<(), Error> {
|
||||||
|
if name.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store.restore_calendar(principal, name).await
|
||||||
|
} else {
|
||||||
|
self.cal_store.restore_calendar(principal, name).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn delete_calendar(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
name: &str,
|
||||||
|
use_trashbin: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if name.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.delete_calendar(principal, name, use_trashbin)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.delete_calendar(principal, name, use_trashbin)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn get_deleted_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||||
|
Ok([
|
||||||
|
self.birthday_store.get_deleted_calendars(principal).await?,
|
||||||
|
self.cal_store.get_deleted_calendars(principal).await?,
|
||||||
|
]
|
||||||
|
.concat())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn restore_object(
|
||||||
|
&self,
|
||||||
|
principal: &str,
|
||||||
|
cal_id: &str,
|
||||||
|
object_id: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.restore_object(principal, cal_id, object_id)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.restore_object(principal, cal_id, object_id)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
async fn put_object(
|
||||||
|
&self,
|
||||||
|
principal: String,
|
||||||
|
cal_id: String,
|
||||||
|
object: CalendarObject,
|
||||||
|
overwrite: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store
|
||||||
|
.put_object(principal, cal_id, object, overwrite)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
self.cal_store
|
||||||
|
.put_object(principal, cal_id, object, overwrite)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_read_only(&self, cal_id: &str) -> bool {
|
||||||
|
if cal_id.starts_with(BIRTHDAYS_PREFIX) {
|
||||||
|
self.birthday_store.is_read_only(cal_id)
|
||||||
|
} else {
|
||||||
|
self.cal_store.is_read_only(cal_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,13 +5,15 @@ use rustical_ical::{AddressObject, CalendarObject, CalendarObjectType};
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
pub(crate) const BIRTHDAYS_PREFIX: &str = "_birthdays_";
|
||||||
|
|
||||||
#[derive(Constructor, Clone)]
|
#[derive(Constructor, Clone)]
|
||||||
pub struct ContactBirthdayStore<AS: AddressbookStore>(Arc<AS>);
|
pub struct ContactBirthdayStore<AS: AddressbookStore>(Arc<AS>);
|
||||||
|
|
||||||
fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
||||||
Calendar {
|
Calendar {
|
||||||
principal: addressbook.principal,
|
principal: addressbook.principal,
|
||||||
id: addressbook.id,
|
id: format!("{}{}", BIRTHDAYS_PREFIX, addressbook.id),
|
||||||
displayname: addressbook
|
displayname: addressbook
|
||||||
.displayname
|
.displayname
|
||||||
.map(|name| format!("{} birthdays", name)),
|
.map(|name| format!("{} birthdays", name)),
|
||||||
@@ -33,9 +35,11 @@ fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Objects are all prefixed with BIRTHDAYS_PREFIX
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||||
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
|
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
|
||||||
|
let id = id.strip_prefix(BIRTHDAYS_PREFIX).ok_or(Error::NotFound)?;
|
||||||
let addressbook = self.0.get_addressbook(principal, id, false).await?;
|
let addressbook = self.0.get_addressbook(principal, id, false).await?;
|
||||||
Ok(birthday_calendar(addressbook))
|
Ok(birthday_calendar(addressbook))
|
||||||
}
|
}
|
||||||
@@ -80,6 +84,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
|||||||
cal_id: &str,
|
cal_id: &str,
|
||||||
synctoken: i64,
|
synctoken: i64,
|
||||||
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
|
) -> Result<(Vec<CalendarObject>, Vec<String>, i64), Error> {
|
||||||
|
let cal_id = cal_id
|
||||||
|
.strip_prefix(BIRTHDAYS_PREFIX)
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
let (objects, deleted_objects, new_synctoken) =
|
let (objects, deleted_objects, new_synctoken) =
|
||||||
self.0.sync_changes(principal, cal_id, synctoken).await?;
|
self.0.sync_changes(principal, cal_id, synctoken).await?;
|
||||||
let objects: Result<Vec<Option<CalendarObject>>, rustical_ical::Error> = objects
|
let objects: Result<Vec<Option<CalendarObject>>, rustical_ical::Error> = objects
|
||||||
@@ -96,6 +103,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
|||||||
principal: &str,
|
principal: &str,
|
||||||
cal_id: &str,
|
cal_id: &str,
|
||||||
) -> Result<Vec<CalendarObject>, Error> {
|
) -> Result<Vec<CalendarObject>, Error> {
|
||||||
|
let cal_id = cal_id
|
||||||
|
.strip_prefix(BIRTHDAYS_PREFIX)
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
let objects: Result<Vec<HashMap<&'static str, CalendarObject>>, rustical_ical::Error> =
|
let objects: Result<Vec<HashMap<&'static str, CalendarObject>>, rustical_ical::Error> =
|
||||||
self.0
|
self.0
|
||||||
.get_objects(principal, cal_id)
|
.get_objects(principal, cal_id)
|
||||||
@@ -117,6 +127,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
|||||||
cal_id: &str,
|
cal_id: &str,
|
||||||
object_id: &str,
|
object_id: &str,
|
||||||
) -> Result<CalendarObject, Error> {
|
) -> Result<CalendarObject, Error> {
|
||||||
|
let cal_id = cal_id
|
||||||
|
.strip_prefix(BIRTHDAYS_PREFIX)
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
let (addressobject_id, date_type) = object_id.rsplit_once("-").ok_or(Error::NotFound)?;
|
let (addressobject_id, date_type) = object_id.rsplit_once("-").ok_or(Error::NotFound)?;
|
||||||
self.0
|
self.0
|
||||||
.get_object(principal, cal_id, addressobject_id, false)
|
.get_object(principal, cal_id, addressobject_id, false)
|
||||||
@@ -155,7 +168,7 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
|||||||
Err(Error::ReadOnly)
|
Err(Error::ReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_read_only(&self) -> bool {
|
fn is_read_only(&self, _cal_id: &str) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub mod error;
|
|||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
mod calendar;
|
mod calendar;
|
||||||
|
mod combined_calendar_store;
|
||||||
mod contact_birthday_store;
|
mod contact_birthday_store;
|
||||||
mod secret;
|
mod secret;
|
||||||
mod subscription_store;
|
mod subscription_store;
|
||||||
@@ -12,6 +13,7 @@ pub mod synctoken;
|
|||||||
|
|
||||||
pub use addressbook_store::AddressbookStore;
|
pub use addressbook_store::AddressbookStore;
|
||||||
pub use calendar_store::CalendarStore;
|
pub use calendar_store::CalendarStore;
|
||||||
|
pub use combined_calendar_store::CombinedCalendarStore;
|
||||||
pub use contact_birthday_store::ContactBirthdayStore;
|
pub use contact_birthday_store::ContactBirthdayStore;
|
||||||
pub use secret::Secret;
|
pub use secret::Secret;
|
||||||
pub use subscription_store::*;
|
pub use subscription_store::*;
|
||||||
|
|||||||
@@ -671,7 +671,7 @@ impl CalendarStore for SqliteCalendarStore {
|
|||||||
Self::_sync_changes(&self.db, principal, cal_id, synctoken).await
|
Self::_sync_changes(&self.db, principal, cal_id, synctoken).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_read_only(&self) -> bool {
|
fn is_read_only(&self, _cal_id: &str) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/app.rs
16
src/app.rs
@@ -2,7 +2,7 @@ use crate::config::NextcloudLoginConfig;
|
|||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::extract::Request;
|
use axum::extract::Request;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::Response;
|
||||||
use axum::routing::options;
|
use axum::routing::options;
|
||||||
use headers::{HeaderMapExt, UserAgent};
|
use headers::{HeaderMapExt, UserAgent};
|
||||||
use http::{HeaderValue, StatusCode};
|
use http::{HeaderValue, StatusCode};
|
||||||
@@ -12,7 +12,9 @@ use rustical_frontend::nextcloud_login::nextcloud_login_router;
|
|||||||
use rustical_frontend::{FrontendConfig, frontend_router};
|
use rustical_frontend::{FrontendConfig, frontend_router};
|
||||||
use rustical_oidc::OidcConfig;
|
use rustical_oidc::OidcConfig;
|
||||||
use rustical_store::auth::AuthenticationProvider;
|
use rustical_store::auth::AuthenticationProvider;
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
|
use rustical_store::{
|
||||||
|
AddressbookStore, CalendarStore, CombinedCalendarStore, ContactBirthdayStore, SubscriptionStore,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tower_http::catch_panic::CatchPanicLayer;
|
use tower_http::catch_panic::CatchPanicLayer;
|
||||||
@@ -33,12 +35,16 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
oidc_config: Option<OidcConfig>,
|
oidc_config: Option<OidcConfig>,
|
||||||
nextcloud_login_config: NextcloudLoginConfig,
|
nextcloud_login_config: NextcloudLoginConfig,
|
||||||
) -> Router<()> {
|
) -> Router<()> {
|
||||||
|
let combined_cal_store = Arc::new(CombinedCalendarStore::new(
|
||||||
|
cal_store.clone(),
|
||||||
|
ContactBirthdayStore::new(addr_store.clone()).into(),
|
||||||
|
));
|
||||||
|
|
||||||
let mut router = Router::new()
|
let mut router = Router::new()
|
||||||
.merge(caldav_router(
|
.merge(caldav_router(
|
||||||
"/caldav",
|
"/caldav",
|
||||||
auth_provider.clone(),
|
auth_provider.clone(),
|
||||||
cal_store.clone(),
|
combined_cal_store.clone(),
|
||||||
addr_store.clone(),
|
|
||||||
subscription_store.clone(),
|
subscription_store.clone(),
|
||||||
))
|
))
|
||||||
.merge(carddav_router(
|
.merge(carddav_router(
|
||||||
@@ -71,7 +77,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
router = router.merge(frontend_router(
|
router = router.merge(frontend_router(
|
||||||
"/frontend",
|
"/frontend",
|
||||||
auth_provider.clone(),
|
auth_provider.clone(),
|
||||||
cal_store.clone(),
|
combined_cal_store.clone(),
|
||||||
addr_store.clone(),
|
addr_store.clone(),
|
||||||
frontend_config,
|
frontend_config,
|
||||||
oidc_config,
|
oidc_config,
|
||||||
|
|||||||
Reference in New Issue
Block a user