Add .ics and .vcf suffix to object resources

This fixes #61
This commit is contained in:
Lennart
2025-05-02 14:55:11 +02:00
parent 99388cf992
commit a78dd4a451
10 changed files with 49 additions and 70 deletions

View File

@@ -33,7 +33,7 @@ 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}"));
let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}.ics"));
let mut result = vec![];
let mut not_found = vec![];
@@ -42,6 +42,7 @@ pub async fn get_objects_calendar_multiget<C: CalendarStore>(
let mut path = Path::new(href.as_str());
if !resource_def.capture_match_info(&mut path) {
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 {
@@ -85,7 +86,7 @@ pub async fn handle_calendar_multiget<C: CalendarStore>(
let mut responses = Vec::new();
for object in objects {
let path = format!("{}/{}", req.path(), object.get_id());
let path = format!("{}/{}.ics", req.path(), object.get_id());
responses.push(
CalendarObjectResource {
object,

View File

@@ -244,7 +244,11 @@ pub async fn handle_calendar_query<C: CalendarStore>(
let mut responses = Vec::new();
for object in objects {
let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id());
let path = format!(
"{}/{}.ics",
req.path().trim_end_matches('/'),
object.get_id()
);
responses.push(
CalendarObjectResource {
object,

View File

@@ -49,7 +49,11 @@ pub async fn handle_sync_collection<C: CalendarStore>(
let mut responses = Vec::new();
for object in new_objects {
let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id());
let path = format!(
"{}/{}.ics",
req.path().trim_end_matches('/'),
object.get_id()
);
responses.push(
CalendarObjectResource {
object,
@@ -60,7 +64,7 @@ pub async fn handle_sync_collection<C: CalendarStore>(
}
for object_id in deleted_objects {
let path = format!("{}/{}", req.path().trim_end_matches('/'), object_id);
let path = format!("{}/{}.ics", req.path().trim_end_matches('/'), object_id);
responses.push(ResponseElement {
href: path,
status: Some(StatusCode::NOT_FOUND),

View File

@@ -1,9 +1,9 @@
use crate::Error;
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 actix_web::HttpRequest;
use actix_web::HttpResponse;
use rustical_store::auth::User;
use rustical_store::{CalendarObject, CalendarStore};
use tracing::instrument;
@@ -20,7 +20,7 @@ pub async fn get_event<C: CalendarStore>(
) -> Result<HttpResponse, Error> {
let CalendarObjectPathComponents {
principal,
cal_id,
calendar_id,
object_id,
} = path.into_inner();
@@ -28,12 +28,14 @@ pub async fn get_event<C: CalendarStore>(
return Ok(HttpResponse::Unauthorized().body(""));
}
let calendar = store.get_calendar(&principal, &cal_id).await?;
let calendar = store.get_calendar(&principal, &calendar_id).await?;
if !user.is_principal(&calendar.principal) {
return Ok(HttpResponse::Unauthorized().body(""));
}
let event = store.get_object(&principal, &cal_id, &object_id).await?;
let event = store
.get_object(&principal, &calendar_id, &object_id)
.await?;
Ok(HttpResponse::Ok()
.insert_header(("ETag", event.get_etag()))
@@ -52,7 +54,7 @@ pub async fn put_event<C: CalendarStore>(
) -> Result<HttpResponse, Error> {
let CalendarObjectPathComponents {
principal,
cal_id,
calendar_id,
object_id,
} = path.into_inner();
@@ -65,7 +67,7 @@ pub async fn put_event<C: CalendarStore>(
let object = CalendarObject::from_ics(object_id, body)?;
store
.put_object(principal, cal_id, object, overwrite)
.put_object(principal, calendar_id, object, overwrite)
.await?;
Ok(HttpResponse::Created().body(""))

View File

@@ -105,31 +105,13 @@ impl Resource for CalendarObjectResource {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Deserialize)]
pub struct CalendarObjectPathComponents {
pub principal: String,
pub cal_id: String,
pub calendar_id: String,
pub object_id: String,
}
impl<'de> Deserialize<'de> for CalendarObjectPathComponents {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
type Inner = (String, String, String);
let (principal, calendar, mut object) = Inner::deserialize(deserializer)?;
if object.ends_with(".ics") {
object.truncate(object.len() - 4);
}
Ok(Self {
principal,
cal_id: calendar,
object_id: object,
})
}
}
#[async_trait(?Send)]
impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
type PathComponents = CalendarObjectPathComponents;
@@ -142,13 +124,13 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
&self,
CalendarObjectPathComponents {
principal,
cal_id,
calendar_id,
object_id,
}: &Self::PathComponents,
) -> Result<Self::Resource, Self::Error> {
let object = self
.cal_store
.get_object(principal, cal_id, object_id)
.get_object(principal, calendar_id, object_id)
.await?;
Ok(CalendarObjectResource {
object,
@@ -160,13 +142,13 @@ impl<C: CalendarStore> ResourceService for CalendarObjectResourceService<C> {
&self,
CalendarObjectPathComponents {
principal,
cal_id,
calendar_id,
object_id,
}: &Self::PathComponents,
use_trashbin: bool,
) -> Result<(), Self::Error> {
self.cal_store
.delete_object(principal, cal_id, object_id, use_trashbin)
.delete_object(principal, calendar_id, object_id, use_trashbin)
.await?;
Ok(())
}

View File

@@ -75,22 +75,22 @@ pub fn caldav_service<
.service(web::scope("/calendar")
.service(CalendarSetResourceService::new(store.clone()).actix_resource())
.service(
web::scope("/{calendar}")
web::scope("/{calendar_id}")
.service(
ResourceServiceRoute(CalendarResourceService::<_, S>::new(store.clone()))
)
.service(web::scope("/{object}").service(CalendarObjectResourceService::new(store.clone()).actix_resource()
.service(web::scope("/{object_id}.ics").service(CalendarObjectResourceService::new(store.clone()).actix_resource()
))
)
)
.service(web::scope("/birthdays")
.service(CalendarSetResourceService::new(birthday_store.clone()).actix_resource())
.service(
web::scope("/{calendar}")
web::scope("/{calendar_id}")
.service(
ResourceServiceRoute(CalendarResourceService::<_, S>::new(birthday_store.clone()))
)
.service(web::scope("/{object}").service(CalendarObjectResourceService::new(birthday_store.clone()).actix_resource()
.service(web::scope("/{object_id}.ics").service(CalendarObjectResourceService::new(birthday_store.clone()).actix_resource()
))
)
)

View File

@@ -101,31 +101,13 @@ impl Resource for AddressObjectResource {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Deserialize)]
pub struct AddressObjectPathComponents {
pub principal: String,
pub addressbook_id: String,
pub object_id: String,
}
impl<'de> Deserialize<'de> for AddressObjectPathComponents {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
type Inner = (String, String, String);
let (principal, addressbook_id, mut object) = Inner::deserialize(deserializer)?;
if object.ends_with(".vcf") {
object.truncate(object.len() - 4);
}
Ok(Self {
principal,
addressbook_id,
object_id: object,
})
}
}
#[async_trait(?Send)]
impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS> {
type PathComponents = AddressObjectPathComponents;

View File

@@ -31,7 +31,7 @@ pub async fn get_objects_addressbook_multiget<AS: AddressbookStore>(
addressbook_id: &str,
store: &AS,
) -> Result<(Vec<AddressObject>, Vec<String>), Error> {
let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}"));
let resource_def = ResourceDef::prefix(path).join(&ResourceDef::new("/{object_id}.vcf"));
let mut result = vec![];
let mut not_found = vec![];
@@ -83,7 +83,7 @@ pub async fn handle_addressbook_multiget<AS: AddressbookStore>(
let mut responses = Vec::new();
for object in objects {
let path = format!("{}/{}", req.path(), object.get_id());
let path = format!("{}/{}.vcf", req.path(), object.get_id());
responses.push(
AddressObjectResource {
object,

View File

@@ -1,19 +1,19 @@
use crate::{
address_object::resource::{AddressObjectPropWrapper, AddressObjectResource},
Error,
address_object::resource::{AddressObjectPropWrapper, AddressObjectResource},
};
use actix_web::{http::StatusCode, HttpRequest};
use actix_web::{HttpRequest, http::StatusCode};
use rustical_dav::{
resource::Resource,
xml::{
multistatus::ResponseElement, sync_collection::SyncCollectionRequest, MultistatusElement,
PropElement, PropfindType,
MultistatusElement, PropElement, PropfindType, multistatus::ResponseElement,
sync_collection::SyncCollectionRequest,
},
};
use rustical_store::{
AddressbookStore,
auth::User,
synctoken::{format_synctoken, parse_synctoken},
AddressbookStore,
};
pub async fn handle_sync_collection<AS: AddressbookStore>(
@@ -44,7 +44,11 @@ pub async fn handle_sync_collection<AS: AddressbookStore>(
let mut responses = Vec::new();
for object in new_objects {
let path = format!("{}/{}", req.path().trim_end_matches('/'), object.get_id());
let path = format!(
"{}/{}.vcf",
req.path().trim_end_matches('/'),
object.get_id()
);
responses.push(
AddressObjectResource {
object,
@@ -55,7 +59,7 @@ pub async fn handle_sync_collection<AS: AddressbookStore>(
}
for object_id in deleted_objects {
let path = format!("{}/{}", req.path().trim_end_matches('/'), object_id);
let path = format!("{}/{}.vcf", req.path().trim_end_matches('/'), object_id);
responses.push(ResponseElement {
href: path,
status: Some(StatusCode::NOT_FOUND),

View File

@@ -68,13 +68,13 @@ pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: Subsc
.name(PrincipalResource::route_name()),
)
.service(
web::scope("/{addressbook}")
web::scope("/{addressbook_id}")
.service(
AddressbookResourceService::<A, S>::new(store.clone())
.actix_resource(),
)
.service(
web::scope("/{object}").service(
web::scope("/{object_id}.vcf").service(
AddressObjectResourceService::<A>::new(store.clone())
.actix_resource(),
),