Skip to main content

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