rustical_caldav/calendar/methods/
post.rs

1use crate::Error;
2use crate::calendar::CalendarResourceService;
3use crate::calendar::resource::CalendarResource;
4use axum::extract::{Path, State};
5use axum::response::{IntoResponse, Response};
6use http::{HeaderMap, HeaderValue, StatusCode, header};
7use rustical_dav::privileges::UserPrivilege;
8use rustical_dav::resource::Resource;
9use rustical_dav_push::register::PushRegister;
10use rustical_store::auth::Principal;
11use rustical_store::{CalendarStore, Subscription, SubscriptionStore};
12use rustical_xml::XmlDocument;
13use tracing::instrument;
14
15#[instrument(skip(resource_service))]
16pub async fn route_post<C: CalendarStore, S: SubscriptionStore>(
17    Path((principal, cal_id)): Path<(String, String)>,
18    user: Principal,
19    State(resource_service): State<CalendarResourceService<C, S>>,
20    body: String,
21) -> Result<Response, Error> {
22    if !user.is_principal(&principal) {
23        return Err(Error::Unauthorized);
24    }
25
26    let calendar = resource_service
27        .cal_store
28        .get_calendar(&principal, &cal_id, false)
29        .await?;
30    let calendar_resource = CalendarResource {
31        cal: calendar,
32        read_only: true,
33    };
34
35    if !calendar_resource
36        .get_user_privileges(&user)?
37        .has(&UserPrivilege::Read)
38    {
39        return Err(Error::Unauthorized);
40    }
41
42    let request = PushRegister::parse_str(&body)?;
43    let sub_id = uuid::Uuid::new_v4().to_string();
44
45    let expires = if let Some(expires) = request.expires {
46        chrono::DateTime::parse_from_rfc2822(&expires).map_err(Error::from)?
47    } else {
48        chrono::Utc::now().fixed_offset() + chrono::Duration::weeks(1)
49    };
50
51    let subscription = Subscription {
52        id: sub_id.clone(),
53        push_resource: request
54            .subscription
55            .web_push_subscription
56            .push_resource
57            .clone(),
58        topic: calendar_resource.cal.push_topic,
59        expiration: expires.naive_local(),
60        public_key: request
61            .subscription
62            .web_push_subscription
63            .subscription_public_key
64            .key,
65        public_key_type: request
66            .subscription
67            .web_push_subscription
68            .subscription_public_key
69            .ty,
70        auth_secret: request.subscription.web_push_subscription.auth_secret,
71    };
72    resource_service
73        .sub_store
74        .upsert_subscription(subscription)
75        .await?;
76
77    // TODO: make nicer
78    let location = format!("/push_subscription/{sub_id}");
79    Ok((
80        StatusCode::CREATED,
81        HeaderMap::from_iter([
82            (header::LOCATION, HeaderValue::from_str(&location).unwrap()),
83            (
84                header::EXPIRES,
85                HeaderValue::from_str(&expires.to_rfc2822()).unwrap(),
86            ),
87        ]),
88    )
89        .into_response())
90}