mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 03:32:15 +00:00
Checkpoint: Migration to axum
This commit is contained in:
@@ -7,15 +7,15 @@ repository.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
actix-web = { workspace = true }
|
||||
axum.workspace = true
|
||||
axum-extra.workspace = true
|
||||
tower.workspace = true
|
||||
async-trait = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
quick-xml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-actix-web = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
derive_more = { workspace = true }
|
||||
actix-web-httpauth = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
@@ -30,3 +30,4 @@ uuid.workspace = true
|
||||
rustical_dav_push.workspace = true
|
||||
rustical_ical.workspace = true
|
||||
http.workspace = true
|
||||
headers.workspace = true
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::Error;
|
||||
use crate::calendar::prop::SupportedCalendarComponentSet;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::web::{Data, Path};
|
||||
use crate::calendar::resource::CalendarResourceService;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use http::StatusCode;
|
||||
use rustical_ical::CalendarObjectType;
|
||||
use rustical_store::auth::User;
|
||||
use rustical_store::{Calendar, CalendarStore};
|
||||
use rustical_store::{Calendar, CalendarStore, SubscriptionStore};
|
||||
use rustical_xml::{Unparsed, XmlDeserialize, XmlDocument, XmlRootTag};
|
||||
use tracing::instrument;
|
||||
use tracing_actix_web::RootSpan;
|
||||
|
||||
#[derive(XmlDeserialize, Clone, Debug)]
|
||||
pub struct MkcolCalendarProp {
|
||||
@@ -48,15 +49,13 @@ struct MkcalendarRequest {
|
||||
set: PropElement,
|
||||
}
|
||||
|
||||
#[instrument(parent = root_span.id(), skip(store, root_span))]
|
||||
pub async fn route_mkcalendar<C: CalendarStore>(
|
||||
path: Path<(String, String)>,
|
||||
body: String,
|
||||
#[instrument(skip(cal_store))]
|
||||
pub async fn route_mkcalendar<C: CalendarStore, S: SubscriptionStore>(
|
||||
Path((principal, cal_id)): Path<(String, String)>,
|
||||
user: User,
|
||||
store: Data<C>,
|
||||
root_span: RootSpan,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let (principal, cal_id) = path.into_inner();
|
||||
State(CalendarResourceService { cal_store, .. }): State<CalendarResourceService<C, S>>,
|
||||
body: String,
|
||||
) -> Result<Response, Error> {
|
||||
if !user.is_principal(&principal) {
|
||||
return Err(Error::Unauthorized);
|
||||
}
|
||||
@@ -87,12 +86,10 @@ pub async fn route_mkcalendar<C: CalendarStore>(
|
||||
]),
|
||||
};
|
||||
|
||||
match store.insert_calendar(calendar).await {
|
||||
match cal_store.insert_calendar(calendar).await {
|
||||
// The spec says we should return a mkcalendar-response but I don't know what goes into it.
|
||||
// However, it works without one but breaks on iPadOS when using an empty one :)
|
||||
Ok(()) => Ok(HttpResponse::Created()
|
||||
.insert_header(("Cache-Control", "no-cache"))
|
||||
.body("")),
|
||||
Ok(()) => Ok(StatusCode::CREATED.into_response()),
|
||||
Err(err) => {
|
||||
dbg!(err.to_string());
|
||||
Err(err.into())
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod mkcalendar;
|
||||
pub mod post;
|
||||
// pub mod post;
|
||||
pub mod report;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::Error;
|
||||
use crate::calendar::resource::{CalendarResource, CalendarResourceService};
|
||||
use actix_web::http::header;
|
||||
use actix_web::web::{Data, Path};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use axum::extract::{Path, State};
|
||||
use axum::response::Response;
|
||||
use rustical_dav::privileges::UserPrivilege;
|
||||
use rustical_dav::resource::Resource;
|
||||
use rustical_dav_push::register::PushRegister;
|
||||
@@ -10,18 +9,14 @@ use rustical_store::auth::User;
|
||||
use rustical_store::{CalendarStore, Subscription, SubscriptionStore};
|
||||
use rustical_xml::XmlDocument;
|
||||
use tracing::instrument;
|
||||
use tracing_actix_web::RootSpan;
|
||||
|
||||
#[instrument(parent = root_span.id(), skip(resource_service, root_span, req))]
|
||||
#[instrument(skip(resource_service))]
|
||||
pub async fn route_post<C: CalendarStore, S: SubscriptionStore>(
|
||||
path: Path<(String, String)>,
|
||||
body: String,
|
||||
Path((principal, cal_id)): Path<(String, String)>,
|
||||
user: User,
|
||||
resource_service: Data<CalendarResourceService<C, S>>,
|
||||
root_span: RootSpan,
|
||||
req: HttpRequest,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let (principal, cal_id) = path.into_inner();
|
||||
State(resource_service): State<CalendarResourceService<C, S>>,
|
||||
body: String,
|
||||
) -> Result<Response, Error> {
|
||||
if !user.is_principal(&principal) {
|
||||
return Err(Error::Unauthorized);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::{Error, calendar_object::resource::CalendarObjectPropWrapperName};
|
||||
use actix_web::dev::{Path, ResourceDef};
|
||||
use rustical_dav::xml::PropfindType;
|
||||
use rustical_ical::CalendarObject;
|
||||
use rustical_store::CalendarStore;
|
||||
@@ -23,23 +22,25 @@ pub async fn get_objects_calendar_multiget<C: CalendarStore>(
|
||||
cal_id: &str,
|
||||
store: &C,
|
||||
) -> Result<(Vec<CalendarObject>, Vec<String>), Error> {
|
||||
let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}.ics"));
|
||||
|
||||
let mut result = vec![];
|
||||
let mut not_found = vec![];
|
||||
|
||||
for href in &cal_query.href {
|
||||
let mut path = Path::new(href.as_str());
|
||||
if !resource_def.capture_match_info(&mut path) {
|
||||
if let Some(filename) = href.strip_prefix(path) {
|
||||
if let Some(object_id) = filename.strip_suffix(".ics") {
|
||||
match store.get_object(principal, cal_id, object_id).await {
|
||||
Ok(object) => result.push(object),
|
||||
Err(rustical_store::Error::NotFound) => not_found.push(href.to_owned()),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
} else {
|
||||
not_found.push(href.to_owned());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
not_found.push(href.to_owned());
|
||||
continue;
|
||||
};
|
||||
let object_id = path.get("object_id").unwrap();
|
||||
match store.get_object(principal, cal_id, object_id).await {
|
||||
Ok(object) => result.push(object),
|
||||
Err(rustical_store::Error::NotFound) => not_found.push(href.to_owned()),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok((result, not_found))
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::{
|
||||
CalDavPrincipalUri, Error,
|
||||
calendar::resource::CalendarResourceService,
|
||||
calendar_object::resource::{
|
||||
CalendarObjectPropWrapper, CalendarObjectPropWrapperName, CalendarObjectResource,
|
||||
},
|
||||
};
|
||||
use actix_web::{
|
||||
HttpRequest, Responder,
|
||||
web::{Data, Path},
|
||||
use axum::{
|
||||
Extension,
|
||||
extract::{OriginalUri, Path, State},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use calendar_multiget::{CalendarMultigetRequest, get_objects_calendar_multiget};
|
||||
use calendar_query::{CalendarQueryRequest, get_objects_calendar_query};
|
||||
@@ -19,7 +21,7 @@ use rustical_dav::{
|
||||
},
|
||||
};
|
||||
use rustical_ical::CalendarObject;
|
||||
use rustical_store::{CalendarStore, auth::User};
|
||||
use rustical_store::{CalendarStore, SubscriptionStore, auth::User};
|
||||
use rustical_xml::{XmlDeserialize, XmlDocument};
|
||||
use sync_collection::handle_sync_collection;
|
||||
use tracing::instrument;
|
||||
@@ -85,16 +87,15 @@ fn objects_response(
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(req, cal_store))]
|
||||
pub async fn route_report_calendar<C: CalendarStore>(
|
||||
path: Path<(String, String)>,
|
||||
body: String,
|
||||
#[instrument(skip(cal_store))]
|
||||
pub async fn route_report_calendar<C: CalendarStore, S: SubscriptionStore>(
|
||||
Path((principal, cal_id)): Path<(String, String)>,
|
||||
user: User,
|
||||
req: HttpRequest,
|
||||
puri: Data<CalDavPrincipalUri>,
|
||||
cal_store: Data<C>,
|
||||
) -> Result<impl Responder, Error> {
|
||||
let (principal, cal_id) = path.into_inner();
|
||||
Extension(puri): Extension<CalDavPrincipalUri>,
|
||||
State(CalendarResourceService { cal_store, .. }): State<CalendarResourceService<C, S>>,
|
||||
OriginalUri(uri): OriginalUri,
|
||||
body: String,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
if !user.is_principal(&principal) {
|
||||
return Err(Error::Unauthorized);
|
||||
}
|
||||
@@ -107,20 +108,12 @@ pub async fn route_report_calendar<C: CalendarStore>(
|
||||
let objects =
|
||||
get_objects_calendar_query(cal_query, &principal, &cal_id, cal_store.as_ref())
|
||||
.await?;
|
||||
objects_response(
|
||||
objects,
|
||||
vec![],
|
||||
req.path(),
|
||||
&principal,
|
||||
puri.as_ref(),
|
||||
&user,
|
||||
props,
|
||||
)?
|
||||
objects_response(objects, vec![], uri.path(), &principal, &puri, &user, props)?
|
||||
}
|
||||
ReportRequest::CalendarMultiget(cal_multiget) => {
|
||||
let (objects, not_found) = get_objects_calendar_multiget(
|
||||
cal_multiget,
|
||||
req.path(),
|
||||
uri.path(),
|
||||
&principal,
|
||||
&cal_id,
|
||||
cal_store.as_ref(),
|
||||
@@ -129,9 +122,9 @@ pub async fn route_report_calendar<C: CalendarStore>(
|
||||
objects_response(
|
||||
objects,
|
||||
not_found,
|
||||
req.path(),
|
||||
uri.path(),
|
||||
&principal,
|
||||
puri.as_ref(),
|
||||
&puri,
|
||||
&user,
|
||||
props,
|
||||
)?
|
||||
@@ -139,8 +132,8 @@ pub async fn route_report_calendar<C: CalendarStore>(
|
||||
ReportRequest::SyncCollection(sync_collection) => {
|
||||
handle_sync_collection(
|
||||
sync_collection,
|
||||
req.path(),
|
||||
puri.as_ref(),
|
||||
uri.path(),
|
||||
&puri,
|
||||
&user,
|
||||
&principal,
|
||||
&cal_id,
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use super::methods::mkcalendar::route_mkcalendar;
|
||||
use super::methods::post::route_post;
|
||||
use super::methods::report::route_report_calendar;
|
||||
use super::prop::{SupportedCalendarComponentSet, SupportedCalendarData, SupportedReportSet};
|
||||
use crate::calendar_object::resource::{CalendarObjectResource, CalendarObjectResourceService};
|
||||
use crate::calendar::methods::mkcalendar::route_mkcalendar;
|
||||
use crate::calendar::methods::report::route_report_calendar;
|
||||
use crate::calendar_object::resource::CalendarObjectResource;
|
||||
use crate::{CalDavPrincipalUri, Error};
|
||||
use actix_web::http::Method;
|
||||
use actix_web::web::{self};
|
||||
use async_trait::async_trait;
|
||||
use axum::extract::Request;
|
||||
use axum::handler::Handler;
|
||||
use axum::response::Response;
|
||||
use chrono::{DateTime, Utc};
|
||||
use derive_more::derive::{From, Into};
|
||||
use futures_util::future::BoxFuture;
|
||||
use rustical_dav::extensions::{
|
||||
CommonPropertiesExtension, CommonPropertiesProp, SyncTokenExtension, SyncTokenExtensionProp,
|
||||
};
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||
use rustical_dav_push::{DavPushExtension, DavPushExtensionProp};
|
||||
use rustical_ical::CalDateTime;
|
||||
@@ -21,8 +22,10 @@ use rustical_store::auth::User;
|
||||
use rustical_store::{Calendar, CalendarStore, SubscriptionStore};
|
||||
use rustical_xml::{EnumVariants, PropName};
|
||||
use rustical_xml::{XmlDeserialize, XmlSerialize};
|
||||
use std::convert::Infallible;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tower::Service;
|
||||
|
||||
#[derive(XmlDeserialize, XmlSerialize, PartialEq, Clone, EnumVariants, PropName)]
|
||||
#[xml(unit_variants_ident = "CalendarPropName")]
|
||||
@@ -313,6 +316,15 @@ pub struct CalendarResourceService<C: CalendarStore, S: SubscriptionStore> {
|
||||
pub(crate) sub_store: Arc<S>,
|
||||
}
|
||||
|
||||
impl<C: CalendarStore, S: SubscriptionStore> Clone for CalendarResourceService<C, S> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
cal_store: self.cal_store.clone(),
|
||||
sub_store: self.sub_store.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CalendarStore, S: SubscriptionStore> CalendarResourceService<C, S> {
|
||||
pub fn new(cal_store: Arc<C>, sub_store: Arc<S>) -> Self {
|
||||
Self {
|
||||
@@ -386,17 +398,21 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarResourc
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn actix_scope(self) -> actix_web::Scope {
|
||||
let report_method = web::method(Method::from_str("REPORT").unwrap());
|
||||
let mkcalendar_method = web::method(Method::from_str("MKCALENDAR").unwrap());
|
||||
web::scope("/{calendar_id}")
|
||||
.service(CalendarObjectResourceService::new(self.cal_store.clone()).actix_scope())
|
||||
.service(
|
||||
self.actix_resource()
|
||||
.route(report_method.to(route_report_calendar::<C>))
|
||||
.route(mkcalendar_method.to(route_mkcalendar::<C>))
|
||||
.post(route_post::<C, S>),
|
||||
)
|
||||
impl<C: CalendarStore, S: SubscriptionStore> AxumMethods for CalendarResourceService<C, S> {
|
||||
fn report() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
|
||||
Some(|state, req| {
|
||||
let mut service = Handler::with_state(route_report_calendar::<C, S>, state);
|
||||
Box::pin(Service::call(&mut service, req))
|
||||
})
|
||||
}
|
||||
|
||||
fn mkcalendar() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>>
|
||||
{
|
||||
Some(|state, req| {
|
||||
let mut service = Handler::with_state(route_mkcalendar::<C, S>, state);
|
||||
Box::pin(Service::call(&mut service, req))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,70 @@
|
||||
use super::resource::CalendarObjectPathComponents;
|
||||
use crate::Error;
|
||||
use crate::calendar_object::resource::CalendarObjectResourceService;
|
||||
use crate::error::Precondition;
|
||||
use actix_web::HttpRequest;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::http::header;
|
||||
use actix_web::http::header::HeaderValue;
|
||||
use actix_web::web::{Data, Path};
|
||||
use axum::body::Body;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum_extra::TypedHeader;
|
||||
use headers::{ContentType, ETag, HeaderMapExt, IfNoneMatch};
|
||||
use http::StatusCode;
|
||||
use rustical_ical::CalendarObject;
|
||||
use rustical_store::CalendarStore;
|
||||
use rustical_store::auth::User;
|
||||
use std::str::FromStr;
|
||||
use tracing::instrument;
|
||||
use tracing_actix_web::RootSpan;
|
||||
|
||||
use super::resource::CalendarObjectPathComponents;
|
||||
|
||||
#[instrument(parent = root_span.id(), skip(store, root_span))]
|
||||
#[instrument(skip(cal_store))]
|
||||
pub async fn get_event<C: CalendarStore>(
|
||||
path: Path<CalendarObjectPathComponents>,
|
||||
store: Data<C>,
|
||||
user: User,
|
||||
root_span: RootSpan,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let CalendarObjectPathComponents {
|
||||
Path(CalendarObjectPathComponents {
|
||||
principal,
|
||||
calendar_id,
|
||||
object_id,
|
||||
} = path.into_inner();
|
||||
|
||||
}): Path<CalendarObjectPathComponents>,
|
||||
State(CalendarObjectResourceService { cal_store }): State<CalendarObjectResourceService<C>>,
|
||||
user: User,
|
||||
) -> Result<Response, Error> {
|
||||
if !user.is_principal(&principal) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
return Err(crate::Error::Unauthorized);
|
||||
}
|
||||
|
||||
let calendar = store.get_calendar(&principal, &calendar_id).await?;
|
||||
let calendar = cal_store.get_calendar(&principal, &calendar_id).await?;
|
||||
if !user.is_principal(&calendar.principal) {
|
||||
return Ok(HttpResponse::Unauthorized().body(""));
|
||||
return Err(crate::Error::Unauthorized);
|
||||
}
|
||||
|
||||
let event = store
|
||||
let event = cal_store
|
||||
.get_object(&principal, &calendar_id, &object_id)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.insert_header(("ETag", event.get_etag()))
|
||||
.insert_header(("Content-Type", "text/calendar"))
|
||||
.body(event.get_ics().to_owned()))
|
||||
let mut resp = Response::builder().status(StatusCode::OK);
|
||||
let hdrs = resp.headers_mut().unwrap();
|
||||
hdrs.typed_insert(ETag::from_str(&event.get_etag()).unwrap());
|
||||
hdrs.typed_insert(ContentType::from_str("text/calendar").unwrap());
|
||||
Ok(resp.body(Body::new(event.get_ics().to_owned())).unwrap())
|
||||
}
|
||||
|
||||
#[instrument(parent = root_span.id(), skip(store, req, root_span))]
|
||||
#[instrument(skip(cal_store))]
|
||||
pub async fn put_event<C: CalendarStore>(
|
||||
path: Path<CalendarObjectPathComponents>,
|
||||
store: Data<C>,
|
||||
body: String,
|
||||
user: User,
|
||||
req: HttpRequest,
|
||||
root_span: RootSpan,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let CalendarObjectPathComponents {
|
||||
Path(CalendarObjectPathComponents {
|
||||
principal,
|
||||
calendar_id,
|
||||
object_id,
|
||||
} = path.into_inner();
|
||||
|
||||
}): Path<CalendarObjectPathComponents>,
|
||||
State(CalendarObjectResourceService { cal_store }): State<CalendarObjectResourceService<C>>,
|
||||
user: User,
|
||||
if_none_match: Option<TypedHeader<IfNoneMatch>>,
|
||||
body: String,
|
||||
) -> Result<Response, Error> {
|
||||
if !user.is_principal(&principal) {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
return Err(crate::Error::Unauthorized);
|
||||
}
|
||||
|
||||
let overwrite =
|
||||
Some(&HeaderValue::from_static("*")) != req.headers().get(header::IF_NONE_MATCH);
|
||||
let overwrite = if let Some(TypedHeader(if_none_match)) = if_none_match {
|
||||
if_none_match == IfNoneMatch::any()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let object = match CalendarObject::from_ics(object_id, body) {
|
||||
Ok(obj) => obj,
|
||||
@@ -73,9 +72,9 @@ pub async fn put_event<C: CalendarStore>(
|
||||
return Err(Error::PreconditionFailed(Precondition::ValidCalendarData));
|
||||
}
|
||||
};
|
||||
store
|
||||
cal_store
|
||||
.put_object(principal, calendar_id, object, overwrite)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Created().finish())
|
||||
Ok(StatusCode::CREATED.into_response())
|
||||
}
|
||||
|
||||
@@ -1,22 +1,35 @@
|
||||
use super::methods::{get_event, put_event};
|
||||
use crate::{CalDavPrincipalUri, Error};
|
||||
use actix_web::web;
|
||||
// use super::methods::{get_event, put_event};
|
||||
use crate::{
|
||||
CalDavPrincipalUri, Error,
|
||||
calendar_object::methods::{get_event, put_event},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use axum::{extract::Request, handler::Handler, response::Response};
|
||||
use derive_more::derive::{From, Into};
|
||||
use futures_util::future::BoxFuture;
|
||||
use rustical_dav::{
|
||||
extensions::{CommonPropertiesExtension, CommonPropertiesProp},
|
||||
privileges::UserPrivilegeSet,
|
||||
resource::{PrincipalUri, Resource, ResourceService},
|
||||
resource::{AxumMethods, PrincipalUri, Resource, ResourceService},
|
||||
xml::Resourcetype,
|
||||
};
|
||||
use rustical_ical::{CalendarObject, UtcDateTime};
|
||||
use rustical_store::{CalendarStore, auth::User};
|
||||
use rustical_xml::{EnumVariants, PropName, XmlDeserialize, XmlSerialize};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
use tower::Service;
|
||||
|
||||
pub struct CalendarObjectResourceService<C: CalendarStore> {
|
||||
cal_store: Arc<C>,
|
||||
pub(crate) cal_store: Arc<C>,
|
||||
}
|
||||
|
||||
impl<C: CalendarStore> Clone for CalendarObjectResourceService<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
cal_store: self.cal_store.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CalendarStore> CalendarObjectResourceService<C> {
|
||||
@@ -130,10 +143,23 @@ impl Resource for CalendarObjectResource {
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_ics_name<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let name: String = Deserialize::deserialize(deserializer)?;
|
||||
if let Some(object_id) = name.strip_suffix(".ics") {
|
||||
Ok(object_id.to_owned())
|
||||
} else {
|
||||
Err(serde::de::Error::custom("Missing .ics extension"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CalendarObjectPathComponents {
|
||||
pub principal: String,
|
||||
pub calendar_id: String,
|
||||
#[serde(deserialize_with = "deserialize_ics_name")]
|
||||
pub object_id: String,
|
||||
}
|
||||
|
||||
@@ -180,12 +206,19 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn actix_scope(self) -> actix_web::Scope {
|
||||
web::scope("/{object_id}.ics").service(
|
||||
self.actix_resource()
|
||||
.get(get_event::<C>)
|
||||
.put(put_event::<C>),
|
||||
)
|
||||
impl<C: CalendarStore> AxumMethods for CalendarObjectResourceService<C> {
|
||||
fn get() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
|
||||
Some(|state, req| {
|
||||
let mut service = Handler::with_state(get_event::<C>, state);
|
||||
Box::pin(Service::call(&mut service, req))
|
||||
})
|
||||
}
|
||||
fn put() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
|
||||
Some(|state, req| {
|
||||
let mut service = Handler::with_state(put_event::<C>, state);
|
||||
Box::pin(Service::call(&mut service, req))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::calendar::resource::{CalendarResource, CalendarResourceService};
|
||||
use crate::calendar::resource::CalendarResource;
|
||||
use crate::{CalDavPrincipalUri, Error};
|
||||
use actix_web::web;
|
||||
use async_trait::async_trait;
|
||||
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::xml::{Resourcetype, ResourcetypeInner};
|
||||
use rustical_store::auth::User;
|
||||
use rustical_store::{CalendarStore, SubscriptionStore};
|
||||
@@ -67,6 +66,16 @@ pub struct CalendarSetResourceService<C: CalendarStore, S: SubscriptionStore> {
|
||||
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 {
|
||||
@@ -116,16 +125,5 @@ impl<C: CalendarStore, S: SubscriptionStore> ResourceService for CalendarSetReso
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn actix_scope(self) -> actix_web::Scope {
|
||||
web::scope(&format!("/{}", self.name))
|
||||
.service(
|
||||
CalendarResourceService::<_, S>::new(
|
||||
self.cal_store.clone(),
|
||||
self.sub_store.clone(),
|
||||
)
|
||||
.actix_scope(),
|
||||
)
|
||||
.service(self.actix_resource())
|
||||
}
|
||||
}
|
||||
impl<C: CalendarStore, S: SubscriptionStore> AxumMethods for CalendarSetResourceService<C, S> {}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use actix_web::{
|
||||
HttpResponse,
|
||||
http::{StatusCode, header::ContentType},
|
||||
use axum::{
|
||||
body::Body,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use headers::{ContentType, HeaderMapExt};
|
||||
use http::StatusCode;
|
||||
use rustical_xml::{XmlSerialize, XmlSerializeRoot};
|
||||
use tracing::error;
|
||||
|
||||
@@ -12,22 +14,18 @@ pub enum Precondition {
|
||||
ValidCalendarData,
|
||||
}
|
||||
|
||||
impl actix_web::ResponseError for Precondition {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
StatusCode::PRECONDITION_FAILED
|
||||
}
|
||||
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
|
||||
impl IntoResponse for Precondition {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let mut output: Vec<_> = b"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".into();
|
||||
let mut writer = quick_xml::Writer::new_with_indent(&mut output, b' ', 4);
|
||||
|
||||
let error = rustical_dav::xml::ErrorElement(self);
|
||||
let error = rustical_dav::xml::ErrorElement(&self);
|
||||
if let Err(err) = error.serialize_root(&mut writer) {
|
||||
return rustical_dav::Error::from(err).error_response();
|
||||
return rustical_dav::Error::from(err).into_response();
|
||||
}
|
||||
|
||||
HttpResponse::PreconditionFailed()
|
||||
.content_type(ContentType::xml())
|
||||
.body(String::from_utf8(output).unwrap())
|
||||
let mut res = Response::builder().status(StatusCode::PRECONDITION_FAILED);
|
||||
res.headers_mut().unwrap().typed_insert(ContentType::xml());
|
||||
res.body(Body::from(output)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +59,8 @@ pub enum Error {
|
||||
PreconditionFailed(Precondition),
|
||||
}
|
||||
|
||||
impl actix_web::ResponseError for Error {
|
||||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
impl Error {
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Error::StoreError(err) => match err {
|
||||
rustical_store::Error::NotFound => StatusCode::NOT_FOUND,
|
||||
@@ -78,16 +76,13 @@ impl actix_web::ResponseError for Error {
|
||||
Error::NotImplemented => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Error::NotFound => StatusCode::NOT_FOUND,
|
||||
Error::IcalError(err) => err.status_code(),
|
||||
Error::PreconditionFailed(err) => err.status_code(),
|
||||
}
|
||||
}
|
||||
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
||||
error!("Error: {self}");
|
||||
match self {
|
||||
Error::DavError(err) => err.error_response(),
|
||||
Error::IcalError(err) => err.error_response(),
|
||||
Error::PreconditionFailed(err) => err.error_response(),
|
||||
_ => HttpResponse::build(self.status_code()).body(self.to_string()),
|
||||
Error::PreconditionFailed(_err) => StatusCode::PRECONDITION_FAILED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Error {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
(self.status_code(), self.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::body::BoxBody;
|
||||
use actix_web::dev::{HttpServiceFactory, ServiceResponse};
|
||||
use actix_web::http::header::{self, HeaderName, HeaderValue};
|
||||
use actix_web::http::{Method, StatusCode};
|
||||
use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers};
|
||||
use actix_web::web::Data;
|
||||
use axum::{Extension, Router};
|
||||
use derive_more::Constructor;
|
||||
use principal::PrincipalResourceService;
|
||||
use rustical_dav::resource::{PrincipalUri, ResourceService};
|
||||
use rustical_dav::resources::RootResourceService;
|
||||
use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider, User};
|
||||
use rustical_store::auth::middleware::AuthenticationLayer;
|
||||
use rustical_store::auth::{AuthenticationProvider, User};
|
||||
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
||||
use std::sync::Arc;
|
||||
use subscription::subscription_resource;
|
||||
|
||||
pub mod calendar;
|
||||
pub mod calendar_object;
|
||||
pub mod calendar_set;
|
||||
pub mod error;
|
||||
pub mod principal;
|
||||
mod subscription;
|
||||
// mod subscription;
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
use crate::calendar::resource::CalendarResourceService;
|
||||
use crate::calendar_object::resource::CalendarObjectResourceService;
|
||||
use crate::calendar_set::CalendarSetResourceService;
|
||||
|
||||
#[derive(Debug, Clone, Constructor)]
|
||||
pub struct CalDavPrincipalUri(&'static str);
|
||||
|
||||
@@ -32,33 +30,38 @@ impl PrincipalUri for CalDavPrincipalUri {
|
||||
}
|
||||
}
|
||||
|
||||
/// Quite a janky implementation but the default METHOD_NOT_ALLOWED response gives us the allowed
|
||||
/// methods of a resource
|
||||
fn options_handler() -> ErrorHandlers<BoxBody> {
|
||||
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
||||
Ok(ErrorHandlerResponse::Response(
|
||||
if res.request().method() == Method::OPTIONS {
|
||||
let mut response = HttpResponse::Ok();
|
||||
response.insert_header((
|
||||
HeaderName::from_static("dav"),
|
||||
// https://datatracker.ietf.org/doc/html/rfc4918#section-18
|
||||
HeaderValue::from_static(
|
||||
"1, 3, access-control, calendar-access, extended-mkcol, webdav-push",
|
||||
),
|
||||
));
|
||||
// pub fn caldav_service<
|
||||
// AP: AuthenticationProvider,
|
||||
// AS: AddressbookStore,
|
||||
// C: CalendarStore,
|
||||
// S: SubscriptionStore,
|
||||
// >(
|
||||
// prefix: &'static str,
|
||||
// auth_provider: Arc<AP>,
|
||||
// store: Arc<C>,
|
||||
// addr_store: Arc<AS>,
|
||||
// subscription_store: Arc<S>,
|
||||
// ) -> impl HttpServiceFactory {
|
||||
// let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
||||
//
|
||||
// RootResourceService::<_, User, CalDavPrincipalUri>::new(PrincipalResourceService {
|
||||
// auth_provider: auth_provider.clone(),
|
||||
// sub_store: subscription_store.clone(),
|
||||
// birthday_store: birthday_store.clone(),
|
||||
// cal_store: store.clone(),
|
||||
// })
|
||||
// .actix_scope()
|
||||
// .wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||
// .wrap(options_handler())
|
||||
// .app_data(Data::from(store.clone()))
|
||||
// .app_data(Data::from(birthday_store.clone()))
|
||||
// .app_data(Data::new(CalDavPrincipalUri::new(
|
||||
// format!("{prefix}/principal").leak(),
|
||||
// )))
|
||||
// .service(subscription_resource(subscription_store))
|
||||
// }
|
||||
|
||||
if let Some(allow) = res.headers().get(header::ALLOW) {
|
||||
response.insert_header((header::ALLOW, allow.to_owned()));
|
||||
}
|
||||
ServiceResponse::new(res.into_parts().0, response.finish()).map_into_right_body()
|
||||
} else {
|
||||
res.map_into_left_body()
|
||||
},
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn caldav_service<
|
||||
pub fn caldav_router<
|
||||
AP: AuthenticationProvider,
|
||||
AS: AddressbookStore,
|
||||
C: CalendarStore,
|
||||
@@ -69,22 +72,53 @@ pub fn caldav_service<
|
||||
store: Arc<C>,
|
||||
addr_store: Arc<AS>,
|
||||
subscription_store: Arc<S>,
|
||||
) -> impl HttpServiceFactory {
|
||||
) -> Router {
|
||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
||||
|
||||
RootResourceService::<_, User, CalDavPrincipalUri>::new(PrincipalResourceService {
|
||||
let principal_service = PrincipalResourceService {
|
||||
auth_provider: auth_provider.clone(),
|
||||
sub_store: subscription_store.clone(),
|
||||
birthday_store: birthday_store.clone(),
|
||||
cal_store: store.clone(),
|
||||
})
|
||||
.actix_scope()
|
||||
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||
.wrap(options_handler())
|
||||
.app_data(Data::from(store.clone()))
|
||||
.app_data(Data::from(birthday_store.clone()))
|
||||
.app_data(Data::new(CalDavPrincipalUri::new(
|
||||
format!("{prefix}/principal").leak(),
|
||||
)))
|
||||
.service(subscription_resource(subscription_store))
|
||||
};
|
||||
|
||||
Router::new()
|
||||
.route_service(
|
||||
"/",
|
||||
RootResourceService::<_, User, CalDavPrincipalUri>::new(principal_service.clone())
|
||||
.axum_service(),
|
||||
)
|
||||
.route_service("/principal/{principal}", principal_service.axum_service())
|
||||
.route_service(
|
||||
"/principal/{principal}/calendar",
|
||||
CalendarSetResourceService::new("calendar", store.clone(), subscription_store.clone())
|
||||
.axum_service(),
|
||||
)
|
||||
.route_service(
|
||||
"/principal/{principal}/calendar/{calendar_id}",
|
||||
CalendarResourceService::new(store.clone(), subscription_store.clone()).axum_service(),
|
||||
)
|
||||
.route_service(
|
||||
"/principal/{principal}/calendar/{calendar_id}/{object_id}",
|
||||
CalendarObjectResourceService::new(store.clone()).axum_service(),
|
||||
)
|
||||
.route_service(
|
||||
"/principal/{principal}/birthdays",
|
||||
CalendarSetResourceService::new(
|
||||
"birthdays",
|
||||
birthday_store.clone(),
|
||||
subscription_store.clone(),
|
||||
)
|
||||
.axum_service(),
|
||||
)
|
||||
.route_service(
|
||||
"/principal/{principal}/birthdays/{calendar_id}",
|
||||
CalendarResourceService::new(birthday_store.clone(), subscription_store.clone())
|
||||
.axum_service(),
|
||||
)
|
||||
.route_service(
|
||||
"/principal/{principal}/birthdays/{calendar_id}/{object_id}",
|
||||
CalendarObjectResourceService::new(birthday_store.clone()).axum_service(),
|
||||
)
|
||||
.layer(AuthenticationLayer::new(auth_provider))
|
||||
.layer(Extension(CalDavPrincipalUri(prefix)))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::calendar_set::{CalendarSetResource, CalendarSetResourceService};
|
||||
use crate::calendar_set::CalendarSetResource;
|
||||
use crate::{CalDavPrincipalUri, Error};
|
||||
use actix_web::web;
|
||||
use async_trait::async_trait;
|
||||
use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::resource::{AxumMethods, PrincipalUri, Resource, ResourceService};
|
||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||
use rustical_store::auth::user::PrincipalType;
|
||||
use rustical_store::auth::{AuthenticationProvider, User};
|
||||
@@ -194,25 +193,9 @@ impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: Ca
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
fn actix_scope(self) -> actix_web::Scope {
|
||||
web::scope("/principal/{principal}")
|
||||
.service(
|
||||
CalendarSetResourceService::<_, S>::new(
|
||||
"calendar",
|
||||
self.cal_store.clone(),
|
||||
self.sub_store.clone(),
|
||||
)
|
||||
.actix_scope(),
|
||||
)
|
||||
.service(
|
||||
CalendarSetResourceService::<_, S>::new(
|
||||
"birthdays",
|
||||
self.birthday_store.clone(),
|
||||
self.sub_store.clone(),
|
||||
)
|
||||
.actix_scope(),
|
||||
)
|
||||
.service(self.actix_resource())
|
||||
}
|
||||
}
|
||||
|
||||
impl<AP: AuthenticationProvider, S: SubscriptionStore, CS: CalendarStore, BS: CalendarStore>
|
||||
AxumMethods for PrincipalResourceService<AP, S, CS, BS>
|
||||
{
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user