mirror of
https://github.com/stalwartlabs/stalwart.git
synced 2026-03-17 14:34:03 +00:00
1006 lines
34 KiB
Rust
1006 lines
34 KiB
Rust
/*
|
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
|
*
|
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
|
*/
|
|
|
|
use super::assert_is_unique_uid;
|
|
use crate::{
|
|
DavError, DavMethod,
|
|
common::{
|
|
lock::{LockRequestHandler, ResourceState},
|
|
uri::DavUriResource,
|
|
},
|
|
file::DavFileResource,
|
|
};
|
|
use calcard::common::timezone::Tz;
|
|
use common::{DavName, Server, auth::AccessToken};
|
|
use dav_proto::{Depth, RequestHeaders};
|
|
use groupware::{
|
|
DestroyArchive,
|
|
cache::GroupwareCache,
|
|
calendar::{Calendar, CalendarEvent, CalendarPreferences, Timezone},
|
|
};
|
|
use http_proto::HttpResponse;
|
|
use hyper::StatusCode;
|
|
use store::write::{BatchBuilder, now};
|
|
use trc::AddContext;
|
|
use types::{
|
|
acl::Acl,
|
|
collection::{Collection, SyncCollection, VanishedCollection},
|
|
};
|
|
|
|
pub(crate) trait CalendarCopyMoveRequestHandler: Sync + Send {
|
|
fn handle_calendar_copy_move_request(
|
|
&self,
|
|
access_token: &AccessToken,
|
|
headers: &RequestHeaders<'_>,
|
|
is_move: bool,
|
|
) -> impl Future<Output = crate::Result<HttpResponse>> + Send;
|
|
}
|
|
|
|
impl CalendarCopyMoveRequestHandler for Server {
|
|
async fn handle_calendar_copy_move_request(
|
|
&self,
|
|
access_token: &AccessToken,
|
|
headers: &RequestHeaders<'_>,
|
|
is_move: bool,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Validate source
|
|
let from_resource_ = self
|
|
.validate_uri(access_token, headers.uri)
|
|
.await?
|
|
.into_owned_uri()?;
|
|
let from_account_id = from_resource_.account_id;
|
|
let from_resources = self
|
|
.fetch_dav_resources(access_token, from_account_id, SyncCollection::Calendar)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
let from_resource_name = from_resource_
|
|
.resource
|
|
.ok_or(DavError::Code(StatusCode::FORBIDDEN))?;
|
|
let from_resource = from_resources
|
|
.by_path(from_resource_name)
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
#[cfg(not(debug_assertions))]
|
|
if is_move
|
|
&& from_resource.is_container()
|
|
&& self
|
|
.core
|
|
.groupware
|
|
.default_calendar_name
|
|
.as_ref()
|
|
.is_some_and(|name| name == from_resource_name)
|
|
{
|
|
return Err(DavError::Condition(crate::DavErrorCondition::new(
|
|
StatusCode::FORBIDDEN,
|
|
dav_proto::schema::response::CalCondition::DefaultCalendarNeeded,
|
|
)));
|
|
}
|
|
|
|
// Validate ACL
|
|
if !access_token.is_member(from_account_id)
|
|
&& !from_resources.has_access_to_container(
|
|
access_token,
|
|
if from_resource.is_container() {
|
|
from_resource.document_id()
|
|
} else {
|
|
from_resource.parent_id().unwrap()
|
|
},
|
|
Acl::ReadItems,
|
|
)
|
|
{
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
// Validate destination
|
|
let destination = self
|
|
.validate_uri_with_status(
|
|
access_token,
|
|
headers
|
|
.destination
|
|
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?,
|
|
StatusCode::BAD_GATEWAY,
|
|
)
|
|
.await?;
|
|
if destination.collection != Collection::Calendar {
|
|
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
|
}
|
|
let to_account_id = destination
|
|
.account_id
|
|
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
|
|
let to_resources = if to_account_id == from_account_id {
|
|
from_resources.clone()
|
|
} else {
|
|
self.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
};
|
|
|
|
// Validate headers
|
|
let destination_resource_name = destination
|
|
.resource
|
|
.ok_or(DavError::Code(StatusCode::BAD_GATEWAY))?;
|
|
let to_resource = to_resources.by_path(destination_resource_name);
|
|
self.validate_headers(
|
|
access_token,
|
|
headers,
|
|
vec![
|
|
ResourceState {
|
|
account_id: from_account_id,
|
|
collection: if from_resource.is_container() {
|
|
Collection::Calendar
|
|
} else {
|
|
Collection::CalendarEvent
|
|
},
|
|
document_id: Some(from_resource.document_id()),
|
|
path: from_resource_name,
|
|
..Default::default()
|
|
},
|
|
ResourceState {
|
|
account_id: to_account_id,
|
|
collection: to_resource
|
|
.map(|r| {
|
|
if r.is_container() {
|
|
Collection::Calendar
|
|
} else {
|
|
Collection::CalendarEvent
|
|
}
|
|
})
|
|
.unwrap_or(Collection::Calendar),
|
|
document_id: Some(to_resource.map(|r| r.document_id()).unwrap_or(u32::MAX)),
|
|
path: destination_resource_name,
|
|
..Default::default()
|
|
},
|
|
],
|
|
Default::default(),
|
|
if is_move {
|
|
DavMethod::MOVE
|
|
} else {
|
|
DavMethod::COPY
|
|
},
|
|
)
|
|
.await?;
|
|
|
|
// Map destination
|
|
if let Some(to_resource) = to_resource {
|
|
if from_resource.path() == to_resource.path() {
|
|
// Same resource
|
|
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
|
}
|
|
let new_name = destination_resource_name
|
|
.rsplit_once('/')
|
|
.map(|(_, name)| name)
|
|
.unwrap_or(destination_resource_name);
|
|
|
|
match (from_resource.is_container(), to_resource.is_container()) {
|
|
(true, true) => {
|
|
let from_children_ids = from_resources
|
|
.subtree(from_resource_name)
|
|
.filter(|r| !r.is_container())
|
|
.map(|r| r.document_id())
|
|
.collect::<Vec<_>>();
|
|
let to_document_ids = to_resources
|
|
.subtree(destination_resource_name)
|
|
.filter(|r| !r.is_container())
|
|
.map(|r| r.document_id())
|
|
.collect::<Vec<_>>();
|
|
|
|
// Validate ACLs
|
|
if !access_token.is_member(to_account_id)
|
|
|| (!access_token.is_member(from_account_id)
|
|
&& !from_resources.has_access_to_container(
|
|
access_token,
|
|
from_resource.document_id(),
|
|
if is_move {
|
|
Acl::RemoveItems
|
|
} else {
|
|
Acl::ReadItems
|
|
},
|
|
))
|
|
{
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
// Overwrite container
|
|
copy_container(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
from_children_ids,
|
|
from_resources.format_collection(from_resource_name),
|
|
to_account_id,
|
|
to_resource.document_id().into(),
|
|
to_document_ids,
|
|
new_name,
|
|
is_move,
|
|
)
|
|
.await
|
|
}
|
|
(false, false) => {
|
|
// Overwrite event
|
|
let from_calendar_id = from_resource.parent_id().unwrap();
|
|
let to_calendar_id = to_resource.parent_id().unwrap();
|
|
|
|
// Validate ACL
|
|
if (!access_token.is_member(from_account_id)
|
|
&& !from_resources.has_access_to_container(
|
|
access_token,
|
|
from_calendar_id,
|
|
if is_move {
|
|
Acl::RemoveItems
|
|
} else {
|
|
Acl::ReadItems
|
|
},
|
|
))
|
|
|| (!access_token.is_member(to_account_id)
|
|
&& !to_resources.has_access_to_container(
|
|
access_token,
|
|
to_calendar_id,
|
|
Acl::RemoveItems,
|
|
))
|
|
{
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
if is_move {
|
|
move_event(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
from_calendar_id,
|
|
from_resources.format_item(from_resource_name),
|
|
to_account_id,
|
|
to_resource.document_id().into(),
|
|
to_calendar_id,
|
|
new_name,
|
|
headers.if_schedule_tag,
|
|
)
|
|
.await
|
|
} else {
|
|
copy_event(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
to_account_id,
|
|
to_resource.document_id().into(),
|
|
to_calendar_id,
|
|
new_name,
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
_ => Err(DavError::Code(StatusCode::BAD_GATEWAY)),
|
|
}
|
|
} else if let Some((parent_resource, new_name)) =
|
|
to_resources.map_parent(destination_resource_name)
|
|
{
|
|
if let Some(parent_resource) = parent_resource {
|
|
// Creating items under an event is not allowed
|
|
// Copying/moving containers under a container is not allowed
|
|
if !parent_resource.is_container() || from_resource.is_container() {
|
|
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
|
}
|
|
|
|
// Validate ACL
|
|
let from_calendar_id = from_resource.parent_id().unwrap();
|
|
let to_calendar_id = parent_resource.document_id();
|
|
if (!access_token.is_member(from_account_id)
|
|
&& !from_resources.has_access_to_container(
|
|
access_token,
|
|
from_calendar_id,
|
|
if is_move {
|
|
Acl::RemoveItems
|
|
} else {
|
|
Acl::ReadItems
|
|
},
|
|
))
|
|
|| (!access_token.is_member(to_account_id)
|
|
&& !to_resources.has_access_to_container(
|
|
access_token,
|
|
to_calendar_id,
|
|
Acl::AddItems,
|
|
))
|
|
{
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
// Copy/move event
|
|
if is_move {
|
|
if from_account_id != to_account_id
|
|
|| parent_resource.document_id() != from_calendar_id
|
|
{
|
|
move_event(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
from_calendar_id,
|
|
from_resources.format_item(from_resource_name),
|
|
to_account_id,
|
|
None,
|
|
to_calendar_id,
|
|
new_name,
|
|
headers.if_schedule_tag,
|
|
)
|
|
.await
|
|
} else {
|
|
rename_event(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
from_calendar_id,
|
|
new_name,
|
|
from_resources.format_item(from_resource_name),
|
|
)
|
|
.await
|
|
}
|
|
} else {
|
|
copy_event(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
to_account_id,
|
|
None,
|
|
to_calendar_id,
|
|
new_name,
|
|
)
|
|
.await
|
|
}
|
|
} else {
|
|
// Copying/moving events to the root is not allowed
|
|
if !from_resource.is_container() {
|
|
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
|
}
|
|
|
|
// Shared users cannot create containers
|
|
if !access_token.is_member(to_account_id) {
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
// Validate ACLs
|
|
if !access_token.is_member(from_account_id)
|
|
&& !from_resources.has_access_to_container(
|
|
access_token,
|
|
from_resource.document_id(),
|
|
if is_move {
|
|
Acl::RemoveItems
|
|
} else {
|
|
Acl::ReadItems
|
|
},
|
|
)
|
|
{
|
|
return Err(DavError::Code(StatusCode::FORBIDDEN));
|
|
}
|
|
|
|
// Copy/move container
|
|
let from_children_ids = from_resources
|
|
.subtree(from_resource_name)
|
|
.filter(|r| !r.is_container())
|
|
.map(|r| r.document_id())
|
|
.collect::<Vec<_>>();
|
|
if is_move {
|
|
if from_account_id != to_account_id {
|
|
copy_container(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
if headers.depth != Depth::Zero {
|
|
from_children_ids
|
|
} else {
|
|
return Err(DavError::Code(StatusCode::BAD_GATEWAY));
|
|
},
|
|
from_resources.format_collection(from_resource_name),
|
|
to_account_id,
|
|
None,
|
|
vec![],
|
|
new_name,
|
|
true,
|
|
)
|
|
.await
|
|
} else {
|
|
rename_container(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
new_name,
|
|
from_resources.format_collection(from_resource_name),
|
|
)
|
|
.await
|
|
}
|
|
} else {
|
|
copy_container(
|
|
self,
|
|
access_token,
|
|
from_account_id,
|
|
from_resource.document_id(),
|
|
if headers.depth != Depth::Zero {
|
|
from_children_ids
|
|
} else {
|
|
vec![]
|
|
},
|
|
from_resources.format_collection(from_resource_name),
|
|
to_account_id,
|
|
None,
|
|
vec![],
|
|
new_name,
|
|
false,
|
|
)
|
|
.await
|
|
}
|
|
}
|
|
} else {
|
|
Err(DavError::Code(StatusCode::CONFLICT))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn copy_event(
|
|
server: &Server,
|
|
access_token: &AccessToken,
|
|
from_account_id: u32,
|
|
from_document_id: u32,
|
|
to_account_id: u32,
|
|
to_document_id: Option<u32>,
|
|
to_calendar_id: u32,
|
|
new_name: &str,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Fetch event
|
|
let event_ = server
|
|
.get_archive(from_account_id, Collection::CalendarEvent, from_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
let mut batch = BatchBuilder::new();
|
|
|
|
// Validate UID
|
|
assert_is_unique_uid(
|
|
server,
|
|
server
|
|
.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.as_ref(),
|
|
to_account_id,
|
|
to_calendar_id,
|
|
event.inner.data.event.uids().next(),
|
|
)
|
|
.await?;
|
|
|
|
if from_account_id == to_account_id {
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
new_event.names.push(DavName {
|
|
name: new_name.to_string(),
|
|
parent_id: to_calendar_id,
|
|
});
|
|
new_event
|
|
.update(
|
|
access_token,
|
|
event,
|
|
from_account_id,
|
|
from_document_id,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
} else {
|
|
let next_email_alarm = event.inner.data.next_alarm(now() as i64, Tz::Floating);
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
new_event.names = vec![DavName {
|
|
name: new_name.to_string(),
|
|
parent_id: to_calendar_id,
|
|
}];
|
|
let to_document_id = server
|
|
.store()
|
|
.assign_document_ids(to_account_id, Collection::CalendarEvent, 1)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
new_event
|
|
.insert(
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
next_email_alarm,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
let response = if let Some(to_document_id) = to_document_id {
|
|
// Overwrite event on destination
|
|
let event_ = server
|
|
.get_archive(to_account_id, Collection::CalendarEvent, to_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
if let Some(event_) = event_ {
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
DestroyArchive(event)
|
|
.delete(
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
to_calendar_id,
|
|
None,
|
|
false,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
Ok(HttpResponse::new(StatusCode::NO_CONTENT))
|
|
} else {
|
|
Ok(HttpResponse::new(StatusCode::CREATED))
|
|
};
|
|
|
|
server
|
|
.commit_batch(batch)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
response
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn move_event(
|
|
server: &Server,
|
|
access_token: &AccessToken,
|
|
from_account_id: u32,
|
|
from_document_id: u32,
|
|
from_calendar_id: u32,
|
|
from_resource_path: String,
|
|
to_account_id: u32,
|
|
to_document_id: Option<u32>,
|
|
to_calendar_id: u32,
|
|
new_name: &str,
|
|
if_schedule_tag: Option<u32>,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Fetch event
|
|
let event_ = server
|
|
.get_archive(from_account_id, Collection::CalendarEvent, from_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
// Validate headers
|
|
if if_schedule_tag.is_some()
|
|
&& event.inner.schedule_tag.as_ref().map(|t| t.to_native()) != if_schedule_tag
|
|
{
|
|
return Err(DavError::Code(StatusCode::PRECONDITION_FAILED));
|
|
}
|
|
|
|
// Validate UID
|
|
if from_account_id != to_account_id
|
|
|| from_calendar_id != to_calendar_id
|
|
|| to_document_id.is_none()
|
|
{
|
|
assert_is_unique_uid(
|
|
server,
|
|
server
|
|
.fetch_dav_resources(access_token, to_account_id, SyncCollection::Calendar)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.as_ref(),
|
|
to_account_id,
|
|
to_calendar_id,
|
|
event.inner.data.event.uids().next(),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
let mut batch = BatchBuilder::new();
|
|
if from_account_id == to_account_id {
|
|
let mut name_idx = None;
|
|
for (idx, name) in event.inner.names.iter().enumerate() {
|
|
if name.parent_id == from_calendar_id {
|
|
name_idx = Some(idx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
let name_idx = if let Some(name_idx) = name_idx {
|
|
name_idx
|
|
} else {
|
|
return Err(DavError::Code(StatusCode::NOT_FOUND));
|
|
};
|
|
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
new_event.names.swap_remove(name_idx);
|
|
new_event.names.push(DavName {
|
|
name: new_name.to_string(),
|
|
parent_id: to_calendar_id,
|
|
});
|
|
new_event
|
|
.update(
|
|
access_token,
|
|
event.clone(),
|
|
from_account_id,
|
|
from_document_id,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
batch.log_vanished_item(VanishedCollection::Calendar, from_resource_path);
|
|
} else {
|
|
let next_email_alarm = event.inner.data.next_alarm(now() as i64, Tz::Floating);
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
new_event.names = vec![DavName {
|
|
name: new_name.to_string(),
|
|
parent_id: to_calendar_id,
|
|
}];
|
|
|
|
DestroyArchive(event)
|
|
.delete(
|
|
access_token,
|
|
from_account_id,
|
|
from_document_id,
|
|
from_calendar_id,
|
|
from_resource_path.into(),
|
|
false,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
|
|
let to_document_id = server
|
|
.store()
|
|
.assign_document_ids(to_account_id, Collection::CalendarEvent, 1)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
new_event
|
|
.insert(
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
next_email_alarm,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
let response = if let Some(to_document_id) = to_document_id {
|
|
// Overwrite event on destination
|
|
let event_ = server
|
|
.get_archive(to_account_id, Collection::CalendarEvent, to_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
if let Some(event_) = event_ {
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
DestroyArchive(event)
|
|
.delete(
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
to_calendar_id,
|
|
None,
|
|
false,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
Ok(HttpResponse::new(StatusCode::NO_CONTENT))
|
|
} else {
|
|
Ok(HttpResponse::new(StatusCode::CREATED))
|
|
};
|
|
|
|
server
|
|
.commit_batch(batch)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
response
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn rename_event(
|
|
server: &Server,
|
|
access_token: &AccessToken,
|
|
account_id: u32,
|
|
document_id: u32,
|
|
calendar_id: u32,
|
|
new_name: &str,
|
|
from_resource_path: String,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Fetch event
|
|
let event_ = server
|
|
.get_archive(account_id, Collection::CalendarEvent, document_id)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
let name_idx = event
|
|
.inner
|
|
.names
|
|
.iter()
|
|
.position(|n| n.parent_id == calendar_id)
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
new_event.names[name_idx].name = new_name.to_string();
|
|
|
|
let mut batch = BatchBuilder::new();
|
|
new_event
|
|
.update(access_token, event, account_id, document_id, &mut batch)
|
|
.caused_by(trc::location!())?;
|
|
batch.log_vanished_item(VanishedCollection::Calendar, from_resource_path);
|
|
server
|
|
.commit_batch(batch)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
Ok(HttpResponse::new(StatusCode::CREATED))
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn copy_container(
|
|
server: &Server,
|
|
access_token: &AccessToken,
|
|
from_account_id: u32,
|
|
from_document_id: u32,
|
|
from_children_ids: Vec<u32>,
|
|
from_resource_path: String,
|
|
to_account_id: u32,
|
|
to_document_id: Option<u32>,
|
|
to_children_ids: Vec<u32>,
|
|
new_name: &str,
|
|
remove_source: bool,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Fetch calendar
|
|
let calendar_ = server
|
|
.get_archive(from_account_id, Collection::Calendar, from_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let old_calendar = calendar_
|
|
.to_unarchived::<Calendar>()
|
|
.caused_by(trc::location!())?;
|
|
let mut calendar = old_calendar
|
|
.deserialize::<Calendar>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
// Prepare write batch
|
|
let mut batch = BatchBuilder::new();
|
|
|
|
if remove_source {
|
|
DestroyArchive(old_calendar)
|
|
.delete(
|
|
access_token,
|
|
from_account_id,
|
|
from_document_id,
|
|
from_resource_path.into(),
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
let preference = calendar.preferences.into_iter().next().unwrap();
|
|
calendar.name = new_name.to_string();
|
|
calendar.default_alerts.clear();
|
|
calendar.acls.clear();
|
|
calendar.preferences = vec![CalendarPreferences {
|
|
account_id: to_account_id,
|
|
name: preference.name,
|
|
description: preference.description,
|
|
sort_order: 0,
|
|
color: preference.color,
|
|
flags: 0,
|
|
time_zone: Timezone::Default,
|
|
}];
|
|
|
|
let is_overwrite = to_document_id.is_some();
|
|
let to_document_id = if let Some(to_document_id) = to_document_id {
|
|
// Overwrite destination
|
|
let calendar_ = server
|
|
.get_archive(to_account_id, Collection::Calendar, to_document_id)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
if let Some(calendar_) = calendar_ {
|
|
let calendar = calendar_
|
|
.to_unarchived::<Calendar>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
DestroyArchive(calendar)
|
|
.delete_with_events(
|
|
server,
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
to_children_ids,
|
|
None,
|
|
false,
|
|
&mut batch,
|
|
)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
|
|
to_document_id
|
|
} else {
|
|
server
|
|
.store()
|
|
.assign_document_ids(to_account_id, Collection::Calendar, 1)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
};
|
|
calendar
|
|
.insert(access_token, to_account_id, to_document_id, &mut batch)
|
|
.caused_by(trc::location!())?;
|
|
|
|
// Copy children
|
|
let mut required_space = 0;
|
|
for from_child_document_id in from_children_ids {
|
|
if let Some(event_) = server
|
|
.get_archive(
|
|
from_account_id,
|
|
Collection::CalendarEvent,
|
|
from_child_document_id,
|
|
)
|
|
.await?
|
|
{
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
let mut new_name = None;
|
|
|
|
for name in event.inner.names.iter() {
|
|
if name.parent_id == to_document_id {
|
|
continue;
|
|
} else if name.parent_id == from_document_id {
|
|
new_name = Some(name.name.to_string());
|
|
}
|
|
}
|
|
let new_name = if let Some(new_name) = new_name {
|
|
DavName {
|
|
name: new_name,
|
|
parent_id: to_document_id,
|
|
}
|
|
} else {
|
|
continue;
|
|
};
|
|
let event = event_
|
|
.to_unarchived::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
let mut new_event = event
|
|
.deserialize::<CalendarEvent>()
|
|
.caused_by(trc::location!())?;
|
|
|
|
if from_account_id == to_account_id {
|
|
if remove_source {
|
|
new_event
|
|
.names
|
|
.retain(|name| name.parent_id != from_document_id);
|
|
}
|
|
|
|
new_event.names.push(new_name);
|
|
new_event
|
|
.update(
|
|
access_token,
|
|
event,
|
|
from_account_id,
|
|
from_child_document_id,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
} else {
|
|
let next_email_alarm = event.inner.data.next_alarm(now() as i64, Tz::Floating);
|
|
if remove_source {
|
|
DestroyArchive(event)
|
|
.delete(
|
|
access_token,
|
|
from_account_id,
|
|
from_child_document_id,
|
|
from_document_id,
|
|
None,
|
|
false,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
let to_document_id = server
|
|
.store()
|
|
.assign_document_ids(to_account_id, Collection::CalendarEvent, 1)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
new_event.names = vec![new_name];
|
|
required_space += new_event.size as u64;
|
|
new_event
|
|
.insert(
|
|
access_token,
|
|
to_account_id,
|
|
to_document_id,
|
|
next_email_alarm,
|
|
&mut batch,
|
|
)
|
|
.caused_by(trc::location!())?;
|
|
}
|
|
}
|
|
}
|
|
|
|
if from_account_id != to_account_id && required_space > 0 {
|
|
server
|
|
.has_available_quota(
|
|
&server
|
|
.get_resource_token(access_token, to_account_id)
|
|
.await?,
|
|
required_space,
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
server
|
|
.commit_batch(batch)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
if !is_overwrite {
|
|
Ok(HttpResponse::new(StatusCode::CREATED))
|
|
} else {
|
|
Ok(HttpResponse::new(StatusCode::NO_CONTENT))
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
async fn rename_container(
|
|
server: &Server,
|
|
access_token: &AccessToken,
|
|
account_id: u32,
|
|
document_id: u32,
|
|
new_name: &str,
|
|
from_resource_path: String,
|
|
) -> crate::Result<HttpResponse> {
|
|
// Fetch calendar
|
|
let calendar_ = server
|
|
.get_archive(account_id, Collection::Calendar, document_id)
|
|
.await
|
|
.caused_by(trc::location!())?
|
|
.ok_or(DavError::Code(StatusCode::NOT_FOUND))?;
|
|
let calendar = calendar_
|
|
.to_unarchived::<Calendar>()
|
|
.caused_by(trc::location!())?;
|
|
let mut new_calendar = calendar
|
|
.deserialize::<Calendar>()
|
|
.caused_by(trc::location!())?;
|
|
new_calendar.name = new_name.to_string();
|
|
|
|
let mut batch = BatchBuilder::new();
|
|
new_calendar
|
|
.update(access_token, calendar, account_id, document_id, &mut batch)
|
|
.caused_by(trc::location!())?;
|
|
batch.log_vanished_item(VanishedCollection::Calendar, from_resource_path);
|
|
server
|
|
.commit_batch(batch)
|
|
.await
|
|
.caused_by(trc::location!())?;
|
|
|
|
Ok(HttpResponse::new(StatusCode::CREATED))
|
|
}
|