use crate::Error; use crate::calendar::CalendarResourceService; use axum::{ extract::{Path, State}, response::{IntoResponse, Response}, }; use http::StatusCode; use ical::{ generator::Emitter, parser::{Component, ComponentMut}, }; use rustical_dav::header::Overwrite; use rustical_ical::{CalendarObject, CalendarObjectType}; use rustical_store::{ Calendar, CalendarMetadata, CalendarStore, SubscriptionStore, auth::Principal, }; use std::io::BufReader; use tracing::instrument; #[instrument(skip(resource_service))] pub async fn route_import( Path((principal, cal_id)): Path<(String, String)>, user: Principal, State(resource_service): State>, Overwrite(overwrite): Overwrite, body: String, ) -> Result { if !user.is_principal(&principal) { return Err(Error::Unauthorized); } let mut parser = ical::IcalParser::new(BufReader::new(body.as_bytes())); let mut cal = parser .next() .expect("input must contain calendar") .unwrap() .mutable(); if parser.next().is_some() { return Err(rustical_ical::Error::InvalidData( "multiple calendars, only one allowed".to_owned(), ) .into()); } // Extract calendar metadata let displayname = cal .get_property("X-WR-CALNAME") .and_then(|prop| prop.value.clone()); let description = cal .get_property("X-WR-CALDESC") .and_then(|prop| prop.value.clone()); let timezone_id = cal .get_property("X-WR-TIMEZONE") .and_then(|prop| prop.value.clone()); // These properties should not appear in the expanded calendar objects cal.remove_property("X-WR-CALNAME"); cal.remove_property("X-WR-CALDESC"); cal.remove_property("X-WR-TIMEZONE"); let cal = cal.verify().unwrap(); // Make sure timezone is valid if let Some(timezone_id) = timezone_id.as_ref() { assert!( vtimezones_rs::VTIMEZONES.contains_key(timezone_id), "Invalid calendar timezone id" ); } // Extract necessary component types let mut cal_components = vec![]; if !cal.events.is_empty() { cal_components.push(CalendarObjectType::Event); } if !cal.journals.is_empty() { cal_components.push(CalendarObjectType::Journal); } if !cal.todos.is_empty() { cal_components.push(CalendarObjectType::Todo); } let expanded_cals = cal.expand_calendar(); // Janky way to convert between IcalCalendar and CalendarObject let objects = expanded_cals .into_iter() .map(|cal| cal.generate()) .map(|ics| CalendarObject::from_ics(ics, None)) .collect::, _>>()?; let new_cal = Calendar { principal, id: cal_id, meta: CalendarMetadata { displayname, order: 0, description, color: None, }, timezone_id, deleted_at: None, synctoken: 0, subscription_url: None, push_topic: uuid::Uuid::new_v4().to_string(), components: cal_components, }; let cal_store = resource_service.cal_store; cal_store .import_calendar(new_cal, objects, overwrite) .await?; Ok(StatusCode::OK.into_response()) }