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