mirror of
https://github.com/lennart-k/rustical.git
synced 2026-01-30 20:08:19 +00:00
113 lines
3.7 KiB
Rust
113 lines
3.7 KiB
Rust
use crate::Error;
|
|
use crate::calendar_object::{CalendarObjectPathComponents, CalendarObjectResourceService};
|
|
use crate::error::Precondition;
|
|
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::{HeaderMap, HeaderValue, Method, StatusCode};
|
|
use rustical_ical::CalendarObject;
|
|
use rustical_store::CalendarStore;
|
|
use rustical_store::auth::Principal;
|
|
use std::str::FromStr;
|
|
use tracing::{debug, instrument};
|
|
|
|
#[instrument(skip(cal_store))]
|
|
pub async fn get_event<C: CalendarStore>(
|
|
Path(CalendarObjectPathComponents {
|
|
principal,
|
|
calendar_id,
|
|
object_id,
|
|
}): Path<CalendarObjectPathComponents>,
|
|
State(CalendarObjectResourceService { cal_store }): State<CalendarObjectResourceService<C>>,
|
|
user: Principal,
|
|
method: Method,
|
|
) -> Result<Response, Error> {
|
|
if !user.is_principal(&principal) {
|
|
return Err(crate::Error::Unauthorized);
|
|
}
|
|
|
|
let calendar = cal_store
|
|
.get_calendar(&principal, &calendar_id, false)
|
|
.await?;
|
|
if !user.is_principal(&calendar.principal) {
|
|
return Err(crate::Error::Unauthorized);
|
|
}
|
|
|
|
let event = cal_store
|
|
.get_object(&principal, &calendar_id, &object_id, false)
|
|
.await?;
|
|
|
|
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; charset=utf-8").unwrap());
|
|
if matches!(method, Method::HEAD) {
|
|
Ok(resp.body(Body::empty()).unwrap())
|
|
} else {
|
|
Ok(resp.body(Body::new(event.get_ics().to_owned())).unwrap())
|
|
}
|
|
}
|
|
|
|
#[instrument(skip(cal_store))]
|
|
pub async fn put_event<C: CalendarStore>(
|
|
Path(CalendarObjectPathComponents {
|
|
principal,
|
|
calendar_id,
|
|
object_id,
|
|
}): Path<CalendarObjectPathComponents>,
|
|
State(CalendarObjectResourceService { cal_store }): State<CalendarObjectResourceService<C>>,
|
|
user: Principal,
|
|
mut if_none_match: Option<TypedHeader<IfNoneMatch>>,
|
|
header_map: HeaderMap,
|
|
body: String,
|
|
) -> Result<Response, Error> {
|
|
if !user.is_principal(&principal) {
|
|
return Err(crate::Error::Unauthorized);
|
|
}
|
|
|
|
// https://github.com/hyperium/headers/issues/204
|
|
if !header_map.contains_key("If-None-Match") {
|
|
if_none_match = None;
|
|
}
|
|
|
|
let overwrite = if let Some(TypedHeader(if_none_match)) = if_none_match {
|
|
// TODO: Put into transaction?
|
|
let existing = match cal_store
|
|
.get_object(&principal, &calendar_id, &object_id, false)
|
|
.await
|
|
{
|
|
Ok(existing) => Some(existing),
|
|
Err(rustical_store::Error::NotFound) => None,
|
|
Err(err) => Err(err)?,
|
|
};
|
|
existing.is_none_or(|existing| {
|
|
if_none_match.precondition_passes(
|
|
&existing
|
|
.get_etag()
|
|
.parse()
|
|
.expect("We only generate valid ETags"),
|
|
)
|
|
})
|
|
} else {
|
|
true
|
|
};
|
|
|
|
let Ok(object) = CalendarObject::from_ics(body.clone()) else {
|
|
debug!("invalid calendar data:\n{body}");
|
|
return Err(Error::PreconditionFailed(Precondition::ValidCalendarData));
|
|
};
|
|
let etag = object.get_etag();
|
|
cal_store
|
|
.put_object(&principal, &calendar_id, &object_id, object, overwrite)
|
|
.await?;
|
|
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(
|
|
"ETag",
|
|
HeaderValue::from_str(&etag).expect("Contains no invalid characters"),
|
|
);
|
|
Ok((StatusCode::CREATED, headers).into_response())
|
|
}
|