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