CalDAV basic implementation
This commit is contained in:
185
crates/groupware/src/calendar/index.rs
Normal file
185
crates/groupware/src/calendar/index.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::storage::index::{
|
||||
IndexItem, IndexValue, IndexableAndSerializableObject, IndexableObject,
|
||||
};
|
||||
use jmap_proto::types::{collection::Collection, value::AclGrant};
|
||||
use store::SerializeInfallible;
|
||||
|
||||
use crate::{IDX_NAME, IDX_UID};
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, ArchivedCalendarPreferences, ArchivedDefaultAlert,
|
||||
ArchivedTimezone, Calendar, CalendarEvent, CalendarPreferences, DefaultAlert, Timezone,
|
||||
};
|
||||
|
||||
impl IndexableObject for Calendar {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
// Note: When adding a new value with index id above 0u8, tune `build_hierarchy`` to skip
|
||||
// this value during iteration.
|
||||
[
|
||||
IndexValue::Index {
|
||||
field: IDX_NAME,
|
||||
value: self.name.as_str().into(),
|
||||
},
|
||||
IndexValue::Acl {
|
||||
value: (&self.acls).into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>() as u32
|
||||
+ self.default_alerts.iter().map(|a| a.size()).sum::<usize>() as u32
|
||||
+ self.name.len() as u32,
|
||||
},
|
||||
IndexValue::LogChild { prefix: None },
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for &ArchivedCalendar {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::Index {
|
||||
field: IDX_NAME,
|
||||
value: self.name.as_str().into(),
|
||||
},
|
||||
IndexValue::Acl {
|
||||
value: self
|
||||
.acls
|
||||
.iter()
|
||||
.map(AclGrant::from)
|
||||
.collect::<Vec<_>>()
|
||||
.into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.preferences.iter().map(|p| p.size()).sum::<usize>() as u32
|
||||
+ self.default_alerts.iter().map(|a| a.size()).sum::<usize>() as u32
|
||||
+ self.name.len() as u32,
|
||||
},
|
||||
IndexValue::LogChild { prefix: None },
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for Calendar {}
|
||||
|
||||
impl IndexableObject for CalendarEvent {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::IndexList {
|
||||
field: IDX_NAME,
|
||||
value: self
|
||||
.names
|
||||
.iter()
|
||||
.map(|v| IndexItem::Vec(v.serialize()))
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_UID,
|
||||
value: self.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
|
||||
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
|
||||
+ self.size,
|
||||
},
|
||||
IndexValue::LogChild { prefix: None },
|
||||
IndexValue::LogParent {
|
||||
collection: Collection::Calendar.into(),
|
||||
ids: self.names.iter().map(|n| n.parent_id).collect(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableObject for &ArchivedCalendarEvent {
|
||||
fn index_values(&self) -> impl Iterator<Item = IndexValue<'_>> {
|
||||
[
|
||||
IndexValue::IndexList {
|
||||
field: IDX_NAME,
|
||||
value: self
|
||||
.names
|
||||
.iter()
|
||||
.map(|v| IndexItem::Vec(v.serialize()))
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_UID,
|
||||
value: self.event.uids().next().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
used: self.dead_properties.size() as u32
|
||||
+ self.display_name.as_ref().map_or(0, |n| n.len() as u32)
|
||||
+ self.names.iter().map(|n| n.name.len() as u32).sum::<u32>()
|
||||
+ self.size,
|
||||
},
|
||||
IndexValue::LogChild { prefix: None },
|
||||
IndexValue::LogParent {
|
||||
collection: Collection::Calendar.into(),
|
||||
ids: self.names.iter().map(|n| n.parent_id.to_native()).collect(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexableAndSerializableObject for CalendarEvent {}
|
||||
|
||||
impl CalendarPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len()
|
||||
+ self.description.as_ref().map_or(0, |n| n.len())
|
||||
+ self.color.as_ref().map_or(0, |n| n.len())
|
||||
+ self.time_zone.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendarPreferences {
|
||||
pub fn size(&self) -> usize {
|
||||
self.name.len()
|
||||
+ self.description.as_ref().map_or(0, |n| n.len())
|
||||
+ self.color.as_ref().map_or(0, |n| n.len())
|
||||
+ self.time_zone.size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Timezone {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Timezone::IANA(s) => s.len(),
|
||||
Timezone::Custom(c) => c.size(),
|
||||
Timezone::Default => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedTimezone {
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
ArchivedTimezone::IANA(s) => s.len(),
|
||||
ArchivedTimezone::Custom(c) => c.size(),
|
||||
ArchivedTimezone::Default => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultAlert {
|
||||
pub fn size(&self) -> usize {
|
||||
self.alert.size() + self.id.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedDefaultAlert {
|
||||
pub fn size(&self) -> usize {
|
||||
self.alert.size() + self.id.len()
|
||||
}
|
||||
}
|
||||
@@ -4,46 +4,63 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use calcard::icalendar::ICalendar;
|
||||
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use jmap_proto::types::{acl::Acl, value::AclGrant};
|
||||
use store::{SERIALIZE_CALENDAR_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion, ahash};
|
||||
use utils::map::vec_map::VecMap;
|
||||
pub mod index;
|
||||
pub mod storage;
|
||||
|
||||
use crate::DavName;
|
||||
use calcard::icalendar::ICalendar;
|
||||
use dav_proto::schema::request::DeadProperty;
|
||||
use jmap_proto::types::{acl::Acl, value::AclGrant};
|
||||
use store::{SERIALIZE_CALENDAR_V1, SERIALIZE_CALENDAREVENT_V1, SerializedVersion};
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct Calendar {
|
||||
pub name: String,
|
||||
pub preferences: HashMap<u32, CalendarPreferences, ahash::RandomState>,
|
||||
pub preferences: Vec<CalendarPreferences>,
|
||||
pub default_alerts: Vec<DefaultAlert>,
|
||||
pub acls: Vec<AclGrant>,
|
||||
pub dead_properties: DeadProperty,
|
||||
pub created: i64,
|
||||
pub modified: i64,
|
||||
}
|
||||
|
||||
pub const CALENDAR_SUBSCRIBED: u16 = 1;
|
||||
pub const CALENDAR_DEFAULT: u16 = 1 << 1;
|
||||
pub const CALENDAR_VISIBLE: u16 = 1 << 2;
|
||||
pub const CALENDAR_AVAILABILITY_ALL: u16 = 1 << 3;
|
||||
pub const CALENDAR_AVAILABILITY_ATTENDING: u16 = 1 << 4;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct CalendarPreferences {
|
||||
pub account_id: u32,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub sort_order: u32,
|
||||
pub color: Option<String>,
|
||||
pub is_subscribed: bool,
|
||||
pub is_default: bool,
|
||||
pub is_visible: bool,
|
||||
pub include_in_availability: IncludeInAvailability,
|
||||
pub default_alerts_with_time: HashMap<String, ICalendar, ahash::RandomState>,
|
||||
pub default_alerts_without_time: HashMap<String, ICalendar, ahash::RandomState>,
|
||||
pub flags: u16,
|
||||
pub time_zone: Timezone,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct DefaultAlert {
|
||||
pub account_id: u32,
|
||||
pub id: String,
|
||||
pub alert: ICalendar,
|
||||
pub with_time: bool,
|
||||
}
|
||||
|
||||
pub const EVENT_INVITE_SELF: u16 = 1;
|
||||
pub const EVENT_INVITE_OTHERS: u16 = 1 << 1;
|
||||
pub const EVENT_HIDE_ATTENDEES: u16 = 1 << 2;
|
||||
pub const EVENT_DRAFT: u16 = 1 << 3;
|
||||
pub const EVENT_ORIGIN: u16 = 1 << 4;
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
@@ -51,17 +68,22 @@ pub struct CalendarEvent {
|
||||
pub names: Vec<DavName>,
|
||||
pub display_name: Option<String>,
|
||||
pub event: ICalendar,
|
||||
pub user_properties: VecMap<u32, ICalendar>,
|
||||
pub may_invite_self: bool,
|
||||
pub may_invite_others: bool,
|
||||
pub hide_attendees: bool,
|
||||
pub is_draft: bool,
|
||||
pub user_properties: Vec<UserProperties>,
|
||||
pub flags: u16,
|
||||
pub dead_properties: DeadProperty,
|
||||
pub size: u32,
|
||||
pub created: i64,
|
||||
pub modified: i64,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
pub struct UserProperties {
|
||||
pub account_id: u32,
|
||||
pub properties: ICalendar,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
@@ -72,17 +94,6 @@ pub enum Timezone {
|
||||
Default,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default, Clone, PartialEq, Eq,
|
||||
)]
|
||||
#[rkyv(derive(Debug))]
|
||||
pub enum IncludeInAvailability {
|
||||
All,
|
||||
Attending,
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
pub enum CalendarRight {
|
||||
ReadFreeBusy,
|
||||
ReadItems,
|
||||
@@ -139,14 +150,43 @@ impl SerializedVersion for CalendarEvent {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendar {
|
||||
pub fn preferences(&self, account_id: u32) -> Option<&ArchivedCalendarPreferences> {
|
||||
impl Calendar {
|
||||
pub fn preferences(&self, account_id: u32) -> &CalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
self.preferences.values().next()
|
||||
&self.preferences[0]
|
||||
} else {
|
||||
self.preferences
|
||||
.get(&rkyv::rend::u32_le::from_native(account_id))
|
||||
.or_else(|| self.preferences.values().next())
|
||||
.iter()
|
||||
.find(|p| p.account_id == account_id)
|
||||
.or_else(|| self.preferences.first())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preferences_mut(&mut self, account_id: u32) -> &mut CalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
&mut self.preferences[0]
|
||||
} else {
|
||||
let idx = self
|
||||
.preferences
|
||||
.iter()
|
||||
.position(|p| p.account_id == account_id)
|
||||
.unwrap_or(0);
|
||||
&mut self.preferences[idx]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchivedCalendar {
|
||||
pub fn preferences(&self, account_id: u32) -> &ArchivedCalendarPreferences {
|
||||
if self.preferences.len() == 1 {
|
||||
&self.preferences[0]
|
||||
} else {
|
||||
self.preferences
|
||||
.iter()
|
||||
.find(|p| p.account_id == account_id)
|
||||
.or_else(|| self.preferences.first())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
246
crates/groupware/src/calendar/storage.rs
Normal file
246
crates/groupware/src/calendar/storage.rs
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::DestroyArchive;
|
||||
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::write::{Archive, BatchBuilder, now};
|
||||
use trc::AddContext;
|
||||
|
||||
use super::{
|
||||
ArchivedCalendar, ArchivedCalendarEvent, Calendar, CalendarEvent, CalendarPreferences,
|
||||
};
|
||||
|
||||
impl CalendarEvent {
|
||||
pub fn update<'x>(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
event: Archive<&ArchivedCalendarEvent>,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
let mut new_event = self;
|
||||
|
||||
// Build event
|
||||
new_event.modified = now() as i64;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(event)
|
||||
.with_changes(new_event)
|
||||
.with_tenant_id(access_token),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
|
||||
pub fn insert<'x>(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
// Build event
|
||||
let mut event = self;
|
||||
let now = now() as i64;
|
||||
event.modified = now;
|
||||
event.created = now;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent)
|
||||
.create_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(event)
|
||||
.with_tenant_id(access_token),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
pub fn insert<'x>(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
// Build address calendar
|
||||
let mut calendar = self;
|
||||
let now = now() as i64;
|
||||
calendar.modified = now;
|
||||
calendar.created = now;
|
||||
|
||||
if calendar.preferences.is_empty() {
|
||||
calendar.preferences.push(CalendarPreferences {
|
||||
account_id,
|
||||
name: "default".to_string(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.create_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<(), _>::new()
|
||||
.with_changes(calendar)
|
||||
.with_tenant_id(access_token),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
|
||||
pub fn update<'x>(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
calendar: Archive<&ArchivedCalendar>,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &'x mut BatchBuilder,
|
||||
) -> trc::Result<&'x mut BatchBuilder> {
|
||||
// Build address calendar
|
||||
let mut new_calendar = self;
|
||||
new_calendar.modified = now() as i64;
|
||||
|
||||
// Prepare write batch
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_current(calendar)
|
||||
.with_changes(new_calendar)
|
||||
.with_tenant_id(access_token),
|
||||
)
|
||||
.map(|b| b.commit_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendar>> {
|
||||
pub async fn delete_with_events(
|
||||
self,
|
||||
server: &Server,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
children_ids: Vec<u32>,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
// Process deletions
|
||||
let calendar_id = document_id;
|
||||
for document_id in children_ids {
|
||||
if let Some(event_) = server
|
||||
.get_archive(account_id, Collection::CalendarEvent, document_id)
|
||||
.await?
|
||||
{
|
||||
DestroyArchive(
|
||||
event_
|
||||
.to_unarchived::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?,
|
||||
)
|
||||
.delete(
|
||||
access_token,
|
||||
account_id,
|
||||
document_id,
|
||||
calendar_id,
|
||||
batch,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.delete(access_token, account_id, document_id, batch)
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let calendar = self.0;
|
||||
// Delete calendar
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Calendar)
|
||||
.delete_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(calendar),
|
||||
)
|
||||
.caused_by(trc::location!())?
|
||||
.commit_point();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DestroyArchive<Archive<&ArchivedCalendarEvent>> {
|
||||
pub fn delete(
|
||||
self,
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
document_id: u32,
|
||||
calendar_id: u32,
|
||||
batch: &mut BatchBuilder,
|
||||
) -> trc::Result<()> {
|
||||
let event = self.0;
|
||||
if let Some(delete_idx) = event
|
||||
.inner
|
||||
.names
|
||||
.iter()
|
||||
.position(|name| name.parent_id == calendar_id)
|
||||
{
|
||||
batch
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::CalendarEvent);
|
||||
|
||||
if event.inner.names.len() > 1 {
|
||||
// Unlink calendar id from event
|
||||
let mut new_event = event
|
||||
.deserialize::<CalendarEvent>()
|
||||
.caused_by(trc::location!())?;
|
||||
new_event.names.swap_remove(delete_idx);
|
||||
batch
|
||||
.update_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(event)
|
||||
.with_changes(new_event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
} else {
|
||||
// Delete event
|
||||
batch
|
||||
.delete_document(document_id)
|
||||
.custom(
|
||||
ObjectIndexBuilder::<_, ()>::new()
|
||||
.with_tenant_id(access_token)
|
||||
.with_current(event),
|
||||
)
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
|
||||
batch.commit_point();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use common::storage::index::{
|
||||
use jmap_proto::types::{collection::Collection, value::AclGrant};
|
||||
use store::SerializeInfallible;
|
||||
|
||||
use crate::{IDX_CARD_UID, IDX_NAME};
|
||||
use crate::{IDX_NAME, IDX_UID};
|
||||
|
||||
use super::{AddressBook, ArchivedAddressBook, ArchivedContactCard, ContactCard};
|
||||
|
||||
@@ -79,7 +79,7 @@ impl IndexableObject for ContactCard {
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_CARD_UID,
|
||||
field: IDX_UID,
|
||||
value: self.card.uid().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
@@ -110,7 +110,7 @@ impl IndexableObject for &ArchivedContactCard {
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
IndexValue::Index {
|
||||
field: IDX_CARD_UID,
|
||||
field: IDX_UID,
|
||||
value: self.card.uid().into(),
|
||||
},
|
||||
IndexValue::Quota {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::{Server, auth::AccessToken, storage::index::ObjectIndexBuilder};
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::write::{Archive, BatchBuilder, now};
|
||||
|
||||
@@ -18,7 +18,12 @@ use store::{
|
||||
use trc::AddContext;
|
||||
use utils::bimap::IdBimap;
|
||||
|
||||
use crate::{DavName, DavResourceName, IDX_NAME, contact::AddressBook, file::FileNode};
|
||||
use crate::{
|
||||
DavName, DavResourceName, IDX_NAME,
|
||||
calendar::{Calendar, CalendarPreferences},
|
||||
contact::AddressBook,
|
||||
file::FileNode,
|
||||
};
|
||||
|
||||
pub trait DavHierarchy: Sync + Send {
|
||||
fn fetch_dav_resources(
|
||||
@@ -127,7 +132,27 @@ impl DavHierarchy for Server {
|
||||
access_token: &AccessToken,
|
||||
account_id: u32,
|
||||
) -> trc::Result<()> {
|
||||
todo!()
|
||||
if let Some(name) = &self.core.dav.default_calendar_name {
|
||||
let mut batch = BatchBuilder::new();
|
||||
let document_id = self
|
||||
.store()
|
||||
.assign_document_ids(account_id, Collection::Calendar, 1)
|
||||
.await?;
|
||||
Calendar {
|
||||
name: name.clone(),
|
||||
preferences: vec![CalendarPreferences {
|
||||
account_id,
|
||||
name: name.clone(),
|
||||
description: self.core.dav.default_calendar_display_name.clone(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
}
|
||||
.insert(access_token, account_id, document_id, &mut batch)?;
|
||||
self.commit_batch(batch).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ pub mod file;
|
||||
pub mod hierarchy;
|
||||
|
||||
pub const IDX_NAME: u8 = 0;
|
||||
pub const IDX_CARD_UID: u8 = 1;
|
||||
pub const IDX_UID: u8 = 1;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DavResourceName {
|
||||
|
||||
Reference in New Issue
Block a user