mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 21:42:34 +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?;
|
||||
Ok(CalendarResource {
|
||||
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_store::auth::middleware::AuthenticationLayer;
|
||||
use rustical_store::auth::{AuthenticationProvider, User};
|
||||
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
||||
use rustical_store::{CalendarStore, SubscriptionStore};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod calendar;
|
||||
pub mod calendar_object;
|
||||
pub mod calendar_set;
|
||||
pub mod error;
|
||||
pub mod principal;
|
||||
// mod subscription;
|
||||
@@ -28,23 +27,15 @@ impl PrincipalUri for CalDavPrincipalUri {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn caldav_router<
|
||||
AP: AuthenticationProvider,
|
||||
AS: AddressbookStore,
|
||||
C: CalendarStore,
|
||||
S: SubscriptionStore,
|
||||
>(
|
||||
pub fn caldav_router<AP: AuthenticationProvider, C: CalendarStore, S: SubscriptionStore>(
|
||||
prefix: &'static str,
|
||||
auth_provider: Arc<AP>,
|
||||
store: Arc<C>,
|
||||
addr_store: Arc<AS>,
|
||||
subscription_store: Arc<S>,
|
||||
) -> Router {
|
||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
||||
let principal_service = PrincipalResourceService {
|
||||
auth_provider: auth_provider.clone(),
|
||||
sub_store: subscription_store.clone(),
|
||||
birthday_store: birthday_store.clone(),
|
||||
cal_store: store.clone(),
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Error;
|
||||
use rustical_dav::extensions::CommonPropertiesExtension;
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
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;
|
||||
|
||||
mod service;
|
||||
@@ -13,7 +13,6 @@ pub use prop::*;
|
||||
#[derive(Clone)]
|
||||
pub struct PrincipalResource {
|
||||
principal: User,
|
||||
home_set: &'static [&'static str],
|
||||
}
|
||||
|
||||
impl ResourceName for PrincipalResource {
|
||||
@@ -47,12 +46,7 @@ impl Resource for PrincipalResource {
|
||||
let home_set = CalendarHomeSet(
|
||||
user.memberships()
|
||||
.into_iter()
|
||||
.map(|principal| puri.principal_uri(principal))
|
||||
.flat_map(|principal_url| {
|
||||
self.home_set.iter().map(move |&home_name| {
|
||||
HrefElement::new(format!("{}{}/", &principal_url, home_name))
|
||||
})
|
||||
})
|
||||
.map(|principal| puri.principal_uri(principal).into())
|
||||
.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::{CalDavPrincipalUri, Error};
|
||||
use async_trait::async_trait;
|
||||
@@ -13,33 +14,30 @@ pub struct PrincipalResourceService<
|
||||
AP: AuthenticationProvider,
|
||||
S: SubscriptionStore,
|
||||
CS: CalendarStore,
|
||||
BS: CalendarStore,
|
||||
> {
|
||||
pub(crate) auth_provider: Arc<AP>,
|
||||
pub(crate) sub_store: Arc<S>,
|
||||
pub(crate) cal_store: Arc<CS>,
|
||||
pub(crate) birthday_store: Arc<BS>,
|
||||
}
|
||||
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore> Clone
|
||||
for PrincipalResourceService<AP, S, CS, BS>
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> Clone
|
||||
for PrincipalResourceService<AP, S, CS>
|
||||
{
|
||||
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<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
||||
ResourceService for PrincipalResourceService<AP, S, CS, BS>
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> ResourceService
|
||||
for PrincipalResourceService<AP, S, CS>
|
||||
{
|
||||
type PathComponents = (String,);
|
||||
type MemberType = CalendarSetResource;
|
||||
type MemberType = CalendarResource;
|
||||
type Resource = PrincipalResource;
|
||||
type Error = Error;
|
||||
type Principal = User;
|
||||
@@ -56,55 +54,36 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
|
||||
.get_principal(principal)
|
||||
.await?
|
||||
.ok_or(crate::Error::NotFound)?;
|
||||
Ok(PrincipalResource {
|
||||
principal: user,
|
||||
home_set: &["calendar", "birthdays"],
|
||||
})
|
||||
Ok(PrincipalResource { principal: user })
|
||||
}
|
||||
|
||||
async fn get_members(
|
||||
&self,
|
||||
(principal,): &Self::PathComponents,
|
||||
) -> Result<Vec<Self::MemberType>, Self::Error> {
|
||||
Ok(vec![
|
||||
CalendarSetResource {
|
||||
name: "calendar",
|
||||
principal: principal.to_owned(),
|
||||
read_only: false,
|
||||
},
|
||||
CalendarSetResource {
|
||||
name: "birthdays",
|
||||
principal: principal.to_owned(),
|
||||
read_only: true,
|
||||
},
|
||||
])
|
||||
let calendars = self.cal_store.get_calendars(principal).await?;
|
||||
|
||||
Ok(calendars
|
||||
.into_iter()
|
||||
.map(|cal| CalendarResource {
|
||||
read_only: self.cal_store.is_read_only(&cal.id),
|
||||
cal,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn axum_router<State: Send + Sync + Clone + 'static>(self) -> axum::Router<State> {
|
||||
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(),
|
||||
"/{calendar_id}",
|
||||
CalendarResourceService::new(self.cal_store.clone(), self.sub_store.clone())
|
||||
.axum_router(),
|
||||
)
|
||||
.route_service("/", self.axum_service())
|
||||
}
|
||||
}
|
||||
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
||||
AxumMethods for PrincipalResourceService<AP, S, CS, BS>
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore> AxumMethods
|
||||
for PrincipalResourceService<AP, S, CS>
|
||||
{
|
||||
}
|
||||
|
||||
@@ -80,5 +80,5 @@ pub trait CalendarStore: Send + Sync + 'static {
|
||||
object_id: &str,
|
||||
) -> 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 std::{collections::HashMap, sync::Arc};
|
||||
|
||||
pub(crate) const BIRTHDAYS_PREFIX: &str = "_birthdays_";
|
||||
|
||||
#[derive(Constructor, Clone)]
|
||||
pub struct ContactBirthdayStore<AS: AddressbookStore>(Arc<AS>);
|
||||
|
||||
fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
||||
Calendar {
|
||||
principal: addressbook.principal,
|
||||
id: addressbook.id,
|
||||
id: format!("{}{}", BIRTHDAYS_PREFIX, addressbook.id),
|
||||
displayname: addressbook
|
||||
.displayname
|
||||
.map(|name| format!("{} birthdays", name)),
|
||||
@@ -33,9 +35,11 @@ fn birthday_calendar(addressbook: Addressbook) -> Calendar {
|
||||
}
|
||||
}
|
||||
|
||||
/// Objects are all prefixed with BIRTHDAYS_PREFIX
|
||||
#[async_trait]
|
||||
impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
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?;
|
||||
Ok(birthday_calendar(addressbook))
|
||||
}
|
||||
@@ -80,6 +84,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
cal_id: &str,
|
||||
synctoken: i64,
|
||||
) -> 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) =
|
||||
self.0.sync_changes(principal, cal_id, synctoken).await?;
|
||||
let objects: Result<Vec<Option<CalendarObject>>, rustical_ical::Error> = objects
|
||||
@@ -96,6 +103,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
principal: &str,
|
||||
cal_id: &str,
|
||||
) -> 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> =
|
||||
self.0
|
||||
.get_objects(principal, cal_id)
|
||||
@@ -117,6 +127,9 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
cal_id: &str,
|
||||
object_id: &str,
|
||||
) -> 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)?;
|
||||
self.0
|
||||
.get_object(principal, cal_id, addressobject_id, false)
|
||||
@@ -155,7 +168,7 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
|
||||
Err(Error::ReadOnly)
|
||||
}
|
||||
|
||||
fn is_read_only(&self) -> bool {
|
||||
fn is_read_only(&self, _cal_id: &str) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod error;
|
||||
pub use error::Error;
|
||||
pub mod auth;
|
||||
mod calendar;
|
||||
mod combined_calendar_store;
|
||||
mod contact_birthday_store;
|
||||
mod secret;
|
||||
mod subscription_store;
|
||||
@@ -12,6 +13,7 @@ pub mod synctoken;
|
||||
|
||||
pub use addressbook_store::AddressbookStore;
|
||||
pub use calendar_store::CalendarStore;
|
||||
pub use combined_calendar_store::CombinedCalendarStore;
|
||||
pub use contact_birthday_store::ContactBirthdayStore;
|
||||
pub use secret::Secret;
|
||||
pub use subscription_store::*;
|
||||
|
||||
@@ -671,7 +671,7 @@ impl CalendarStore for SqliteCalendarStore {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
16
src/app.rs
16
src/app.rs
@@ -2,7 +2,7 @@ use crate::config::NextcloudLoginConfig;
|
||||
use axum::Router;
|
||||
use axum::body::Body;
|
||||
use axum::extract::Request;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::response::Response;
|
||||
use axum::routing::options;
|
||||
use headers::{HeaderMapExt, UserAgent};
|
||||
use http::{HeaderValue, StatusCode};
|
||||
@@ -12,7 +12,9 @@ use rustical_frontend::nextcloud_login::nextcloud_login_router;
|
||||
use rustical_frontend::{FrontendConfig, frontend_router};
|
||||
use rustical_oidc::OidcConfig;
|
||||
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::time::Duration;
|
||||
use tower_http::catch_panic::CatchPanicLayer;
|
||||
@@ -33,12 +35,16 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
||||
oidc_config: Option<OidcConfig>,
|
||||
nextcloud_login_config: NextcloudLoginConfig,
|
||||
) -> Router<()> {
|
||||
let combined_cal_store = Arc::new(CombinedCalendarStore::new(
|
||||
cal_store.clone(),
|
||||
ContactBirthdayStore::new(addr_store.clone()).into(),
|
||||
));
|
||||
|
||||
let mut router = Router::new()
|
||||
.merge(caldav_router(
|
||||
"/caldav",
|
||||
auth_provider.clone(),
|
||||
cal_store.clone(),
|
||||
addr_store.clone(),
|
||||
combined_cal_store.clone(),
|
||||
subscription_store.clone(),
|
||||
))
|
||||
.merge(carddav_router(
|
||||
@@ -71,7 +77,7 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
||||
router = router.merge(frontend_router(
|
||||
"/frontend",
|
||||
auth_provider.clone(),
|
||||
cal_store.clone(),
|
||||
combined_cal_store.clone(),
|
||||
addr_store.clone(),
|
||||
frontend_config,
|
||||
oidc_config,
|
||||
|
||||
Reference in New Issue
Block a user