mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 07:02:24 +00:00
caldav: Rewrite of MKCALENDAR method
This commit is contained in:
84
crates/caldav/src/calendar/methods/mkcalendar.rs
Normal file
84
crates/caldav/src/calendar/methods/mkcalendar.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use crate::CalDavContext;
|
||||||
|
use crate::Error;
|
||||||
|
use actix_web::web::{Data, Path};
|
||||||
|
use actix_web::HttpResponse;
|
||||||
|
use anyhow::Result;
|
||||||
|
use rustical_auth::{AuthInfoExtractor, CheckAuthentication};
|
||||||
|
use rustical_store::calendar::{Calendar, CalendarStore};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct Resourcetype {
|
||||||
|
#[serde(rename = "C:calendar", alias = "calendar")]
|
||||||
|
calendar: Option<()>,
|
||||||
|
collection: Option<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub struct MkcolCalendarProp {
|
||||||
|
resourcetype: Resourcetype,
|
||||||
|
displayname: Option<String>,
|
||||||
|
calendar_description: Option<String>,
|
||||||
|
calendar_color: Option<String>,
|
||||||
|
calendar_timezone: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct PropElement<T: Serialize> {
|
||||||
|
prop: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
#[serde(rename = "mkcalendar")]
|
||||||
|
struct MkcalendarRequest {
|
||||||
|
set: PropElement<MkcolCalendarProp>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Not sure yet what to send back :)
|
||||||
|
#[derive(Serialize, Clone, Debug)]
|
||||||
|
#[serde(rename = "mkcalendar-response")]
|
||||||
|
struct MkcalendarResponse;
|
||||||
|
|
||||||
|
pub async fn route_mkcol_calendar<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
||||||
|
path: Path<(String, String)>,
|
||||||
|
body: String,
|
||||||
|
auth: AuthInfoExtractor<A>,
|
||||||
|
context: Data<CalDavContext<C>>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let (principal, cid) = path.into_inner();
|
||||||
|
if principal != auth.inner.user_id {
|
||||||
|
return Err(Error::Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
let request: MkcalendarRequest = quick_xml::de::from_str(&body).map_err(|e| {
|
||||||
|
dbg!(e.to_string());
|
||||||
|
Error::BadRequest
|
||||||
|
})?;
|
||||||
|
let request = request.set.prop;
|
||||||
|
|
||||||
|
let calendar = Calendar {
|
||||||
|
id: cid.to_owned(),
|
||||||
|
owner: principal,
|
||||||
|
name: request.displayname,
|
||||||
|
timezone: request.calendar_timezone,
|
||||||
|
color: request.calendar_color,
|
||||||
|
description: request.calendar_description,
|
||||||
|
};
|
||||||
|
|
||||||
|
match context
|
||||||
|
.store
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert_calendar(cid, calendar)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
let response = quick_xml::se::to_string(&MkcalendarResponse).unwrap();
|
||||||
|
Ok(HttpResponse::Created().body(response))
|
||||||
|
}
|
||||||
|
Err(_err) => Ok(HttpResponse::InternalServerError().body("")),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,28 +11,10 @@ use rustical_dav::namespace::Namespace;
|
|||||||
use rustical_dav::propfind::ServicePrefix;
|
use rustical_dav::propfind::ServicePrefix;
|
||||||
use rustical_dav::resource::HandlePropfind;
|
use rustical_dav::resource::HandlePropfind;
|
||||||
use rustical_dav::xml_snippets::generate_multistatus;
|
use rustical_dav::xml_snippets::generate_multistatus;
|
||||||
use rustical_store::calendar::{Calendar, CalendarStore};
|
use rustical_store::calendar::CalendarStore;
|
||||||
use rustical_store::event::Event;
|
use rustical_store::event::Event;
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
async fn _parse_filter(filter_node: &Node<'_, '_>) {
|
pub mod mkcalendar;
|
||||||
for comp_filter_node in filter_node.children() {
|
|
||||||
if comp_filter_node.tag_name().name() != "comp-filter" {
|
|
||||||
dbg!("wtf", comp_filter_node.tag_name().name());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for filter in filter_node.children() {
|
|
||||||
match filter.tag_name().name() {
|
|
||||||
// <time-range start=\"20230804T125257Z\" end=\"20231013T125257Z\"/
|
|
||||||
"time-range" => {}
|
|
||||||
_ => {
|
|
||||||
dbg!("unknown filter", filter.tag_name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_report_calendar_query(
|
async fn handle_report_calendar_query(
|
||||||
query_node: Node<'_, '_>,
|
query_node: Node<'_, '_>,
|
||||||
@@ -107,81 +89,6 @@ pub async fn route_report_calendar<A: CheckAuthentication, C: CalendarStore + ?S
|
|||||||
handle_report_calendar_query(query_node, events, prefix).await
|
handle_report_calendar_query(query_node, events, prefix).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_mkcol_calendar_set<C: CalendarStore + ?Sized>(
|
|
||||||
store: &RwLock<C>,
|
|
||||||
prop_node: Node<'_, '_>,
|
|
||||||
cid: String,
|
|
||||||
owner: String,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut cal = Calendar {
|
|
||||||
owner,
|
|
||||||
id: cid.clone(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
for prop in prop_node.children() {
|
|
||||||
match prop.tag_name().name() {
|
|
||||||
"displayname" => {
|
|
||||||
cal.name = prop.text().map(str::to_string);
|
|
||||||
}
|
|
||||||
"timezone" => {
|
|
||||||
cal.timezone = prop.text().map(str::to_string);
|
|
||||||
}
|
|
||||||
"calendar-color" => {
|
|
||||||
cal.color = prop.text().map(str::to_string);
|
|
||||||
}
|
|
||||||
"calendar-description" => {
|
|
||||||
cal.description = prop.text().map(str::to_string);
|
|
||||||
}
|
|
||||||
"calendar-timezone" => {
|
|
||||||
cal.timezone = prop.text().map(str::to_string);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("unsupported mkcol tag: {}", prop.tag_name().name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store.write().await.insert_calendar(cid, cal).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn route_mkcol_calendar<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
|
||||||
path: Path<(String, String)>,
|
|
||||||
body: String,
|
|
||||||
auth: AuthInfoExtractor<A>,
|
|
||||||
context: Data<CalDavContext<C>>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
let (_principal, cid) = path.into_inner();
|
|
||||||
let doc = roxmltree::Document::parse(&body).map_err(|_e| Error::BadRequest)?;
|
|
||||||
let mkcol_node = doc.root_element();
|
|
||||||
match mkcol_node.tag_name().name() {
|
|
||||||
"mkcol" => {}
|
|
||||||
_ => return Err(Error::BadRequest),
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Why does the spec (rfc5689) allow multiple <set/> elements but only one resource? :/
|
|
||||||
// Well, for now just bother with the first one
|
|
||||||
let set_node = mkcol_node.first_element_child().ok_or(Error::BadRequest)?;
|
|
||||||
match set_node.tag_name().name() {
|
|
||||||
"set" => {}
|
|
||||||
_ => return Err(Error::BadRequest),
|
|
||||||
}
|
|
||||||
|
|
||||||
let prop_node = set_node.first_element_child().ok_or(Error::BadRequest)?;
|
|
||||||
if prop_node.tag_name().name() != "prop" {
|
|
||||||
return Err(Error::BadRequest);
|
|
||||||
}
|
|
||||||
handle_mkcol_calendar_set(
|
|
||||||
&context.store,
|
|
||||||
prop_node,
|
|
||||||
cid.clone(),
|
|
||||||
auth.inner.user_id.clone(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Created().body(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_calendar<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
pub async fn delete_calendar<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
||||||
context: Data<CalDavContext<C>>,
|
context: Data<CalDavContext<C>>,
|
||||||
path: Path<(String, String)>,
|
path: Path<(String, String)>,
|
||||||
@@ -34,7 +34,7 @@ pub fn configure_dav<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
|||||||
) {
|
) {
|
||||||
let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap());
|
let propfind_method = || web::method(Method::from_str("PROPFIND").unwrap());
|
||||||
let report_method = || web::method(Method::from_str("REPORT").unwrap());
|
let report_method = || web::method(Method::from_str("REPORT").unwrap());
|
||||||
let mkcol_method = || web::method(Method::from_str("MKCOL").unwrap());
|
let mkcalendar_method = || web::method(Method::from_str("MKCALENDAR").unwrap());
|
||||||
|
|
||||||
cfg.app_data(Data::new(CalDavContext {
|
cfg.app_data(Data::new(CalDavContext {
|
||||||
store: store.clone(),
|
store: store.clone(),
|
||||||
@@ -57,7 +57,9 @@ pub fn configure_dav<A: CheckAuthentication, C: CalendarStore + ?Sized>(
|
|||||||
web::resource("/{principal}/{calendar}")
|
web::resource("/{principal}/{calendar}")
|
||||||
.route(report_method().to(calendar::methods::route_report_calendar::<A, C>))
|
.route(report_method().to(calendar::methods::route_report_calendar::<A, C>))
|
||||||
.route(propfind_method().to(handle_propfind::<A, CalendarResource<C>>))
|
.route(propfind_method().to(handle_propfind::<A, CalendarResource<C>>))
|
||||||
.route(mkcol_method().to(calendar::methods::route_mkcol_calendar::<A, C>))
|
.route(
|
||||||
|
mkcalendar_method().to(calendar::methods::mkcalendar::route_mkcol_calendar::<A, C>),
|
||||||
|
)
|
||||||
.route(web::method(Method::DELETE).to(calendar::methods::delete_calendar::<A, C>)),
|
.route(web::method(Method::DELETE).to(calendar::methods::delete_calendar::<A, C>)),
|
||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
|
|||||||
Reference in New Issue
Block a user