rustical_caldav/calendar/methods/
post.rs1use 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 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}