rustical_caldav/calendar/methods/
import.rs1use crate::Error;
2use crate::calendar::CalendarResourceService;
3use axum::{
4 extract::{Path, State},
5 response::{IntoResponse, Response},
6};
7use axum_extra::TypedHeader;
8use caldata::component::{Component, ComponentMut};
9use caldata::{IcalParser, parser::ParserOptions};
10use http::StatusCode;
11use rustical_dav::header::Overwrite;
12use rustical_ical::CalendarObjectType;
13use rustical_store::{
14 Calendar, CalendarMetadata, CalendarStore, SubscriptionStore, auth::Principal,
15};
16use tracing::instrument;
17
18#[instrument(skip(resource_service))]
19pub async fn route_import<C: CalendarStore, S: SubscriptionStore>(
20 Path((principal, cal_id)): Path<(String, String)>,
21 user: Principal,
22 State(resource_service): State<CalendarResourceService<C, S>>,
23 overwrite: Option<TypedHeader<Overwrite>>,
24 body: String,
25) -> Result<Response, Error> {
26 if !user.is_principal(&principal) {
27 return Err(Error::Unauthorized);
28 }
29
30 let overwrite = overwrite
31 .map(|TypedHeader(overwrite)| overwrite)
32 .unwrap_or_default()
33 .into();
34
35 let parser = IcalParser::from_slice(body.as_bytes());
36 let mut cal = match parser.expect_one() {
37 Ok(cal) => cal.mutable(),
38 Err(err) => return Ok((StatusCode::BAD_REQUEST, err.to_string()).into_response()),
39 };
40
41 let displayname = cal
43 .get_property("X-WR-CALNAME")
44 .map(|prop| prop.value.clone());
45 let description = cal
46 .get_property("X-WR-CALDESC")
47 .map(|prop| prop.value.clone());
48 let color = cal
49 .get_property("X-WR-CALCOLOR")
50 .map(|prop| prop.value.clone());
51 let timezone_id = cal
52 .get_property("X-WR-TIMEZONE")
53 .map(|prop| prop.value.clone());
54 cal.remove_property("X-WR-CALNAME");
56 cal.remove_property("X-WR-CALDESC");
57 cal.remove_property("X-WR-CALCOLOR");
58 cal.remove_property("X-WR-TIMEZONE");
59 let cal = cal.build(&ParserOptions::default(), None).unwrap();
60
61 if let Some(timezone_id) = timezone_id.as_ref() {
63 assert!(
64 vtimezones_rs::VTIMEZONES.contains_key(timezone_id),
65 "Invalid calendar timezone id"
66 );
67 }
68 let mut cal_components = vec![];
70 if !cal.events.is_empty() {
71 cal_components.push(CalendarObjectType::Event);
72 }
73 if !cal.journals.is_empty() {
74 cal_components.push(CalendarObjectType::Journal);
75 }
76 if !cal.todos.is_empty() {
77 cal_components.push(CalendarObjectType::Todo);
78 }
79
80 let objects = match cal.into_objects() {
81 Ok(objects) => objects.into_iter().map(Into::into).collect(),
82 Err(err) => return Ok((StatusCode::BAD_REQUEST, err.to_string()).into_response()),
83 };
84 let new_cal = Calendar {
85 principal,
86 id: cal_id,
87 meta: CalendarMetadata {
88 displayname,
89 order: 0,
90 description,
91 color,
92 },
93 timezone_id,
94 deleted_at: None,
95 synctoken: 0,
96 subscription_url: None,
97 push_topic: uuid::Uuid::new_v4().to_string(),
98 components: cal_components,
99 };
100
101 let cal_store = resource_service.cal_store;
102 cal_store
103 .import_calendar(new_cal, objects, overwrite)
104 .await?;
105
106 Ok(StatusCode::OK.into_response())
107}