Compare commits

...

1 Commits

Author SHA1 Message Date
Lennart
41039242ee Some work on caldav imports 2025-06-11 00:17:57 +02:00
6 changed files with 152 additions and 0 deletions

View File

@@ -58,6 +58,13 @@ pub async fn route_get<C: CalendarStore, S: SubscriptionStore>(
params: None, params: None,
}); });
} }
if calendar.color.is_some() {
ical_calendar_builder = ical_calendar_builder.set(Property {
name: "X-RUSTICAL-COLOR".to_owned(),
value: calendar.color,
params: None,
});
}
let mut ical_calendar = ical_calendar_builder.build(); let mut ical_calendar = ical_calendar_builder.build();
for object in &objects { for object in &objects {

View File

@@ -1,4 +1,5 @@
pub mod mkcalendar; pub mod mkcalendar;
// pub mod post; // pub mod post;
pub mod get; pub mod get;
pub mod put;
pub mod report; pub mod report;

View File

@@ -0,0 +1,101 @@
use std::collections::HashMap;
use crate::calendar::prop::SupportedCalendarComponent;
use crate::calendar::{self, CalendarResourceService};
use crate::{Error, calendar_set};
use axum::{
extract::{Path, State},
response::{IntoResponse, Response},
};
use http::StatusCode;
use ical::generator::Emitter;
use ical::parser::ical::component::IcalTimeZone;
use ical::{IcalParser, parser::Component};
use rustical_ical::CalendarObjectType;
use rustical_store::{Calendar, CalendarStore, SubscriptionStore, auth::User};
use tracing::instrument;
#[instrument(skip(cal_store))]
pub async fn route_put<C: CalendarStore, S: SubscriptionStore>(
Path((principal, cal_id)): Path<(String, String)>,
State(CalendarResourceService { cal_store, .. }): State<CalendarResourceService<C, S>>,
user: User,
body: String,
) -> Result<Response, Error> {
if !user.is_principal(&principal) {
return Err(crate::Error::Unauthorized);
}
let mut parser = IcalParser::new(body.as_bytes());
let cal = parser
.next()
.ok_or(rustical_ical::Error::MissingCalendar)?
.map_err(rustical_ical::Error::from)?;
if parser.next().is_some() {
return Err(rustical_ical::Error::InvalidData(
"multiple calendars, only one allowed".to_owned(),
)
.into());
}
if !cal.alarms.is_empty() || !cal.free_busys.is_empty() {
return Err(rustical_ical::Error::InvalidData(
"Importer does not support VALARM and VFREEBUSY components".to_owned(),
)
.into());
}
let mut objects = vec![];
for event in cal.events {}
for todo in cal.todos {}
for journal in cal.journals {}
let timezones: HashMap<String, IcalTimeZone> = cal
.timezones
.clone()
.into_iter()
.filter_map(|timezone| {
let timezone_prop = timezone.get_property("TZID")?.to_owned();
let tzid = timezone_prop.value?;
Some((tzid, timezone))
})
.collect();
let displayname = cal.get_property("X-WR-CALNAME").and_then(|prop| prop.value);
let description = cal.get_property("X-WR-CALDESC").and_then(|prop| prop.value);
let color = cal
.get_property("X-RUSTICAL-COLOR")
.and_then(|prop| prop.value);
let timezone_id = cal
.get_property("X-WR-TIMEZONE")
.and_then(|prop| prop.value);
let timezone = timezone_id
.and_then(|tzid| timezones.get(&tzid))
.map(|timezone| timezone.generate());
let mut components = vec![CalendarObjectType::Event, CalendarObjectType::Todo];
if !cal.journals.is_empty() {
components.push(CalendarObjectType::Journal);
}
let calendar = Calendar {
principal: principal.clone(),
id: cal_id,
displayname,
description,
color,
timezone_id,
timezone,
components,
subscription_url: None,
push_topic: uuid::Uuid::new_v4().to_string(),
synctoken: 0,
deleted_at: None,
order: 0,
};
cal_store
.import_calendar(&principal, calendar, objects)
.await?;
Ok(StatusCode::CREATED.into_response())
}

View File

@@ -81,4 +81,11 @@ pub trait CalendarStore: Send + Sync + 'static {
) -> Result<(), Error>; ) -> Result<(), Error>;
fn is_read_only(&self) -> bool; fn is_read_only(&self) -> bool;
async fn import_calendar(
&self,
principal: &str,
calendar: Calendar,
objects: Vec<CalendarObject>,
) -> Result<(), Error>;
} }

View File

@@ -158,4 +158,13 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
fn is_read_only(&self) -> bool { fn is_read_only(&self) -> bool {
true true
} }
async fn import_calendar(
&self,
_principal: &str,
_calendar: Calendar,
_objects: Vec<CalendarObject>,
) -> Result<(), Error> {
Err(Error::ReadOnly)
}
} }

View File

@@ -673,6 +673,33 @@ impl CalendarStore for SqliteCalendarStore {
fn is_read_only(&self) -> bool { fn is_read_only(&self) -> bool {
false false
} }
#[instrument(skip(calendar, objects))]
async fn import_calendar(
&self,
principal: &str,
calendar: Calendar,
objects: Vec<CalendarObject>,
) -> Result<(), Error> {
let mut tx = self.db.begin().await.map_err(crate::Error::from)?;
let cal_id = calendar.id.clone();
Self::_insert_calendar(&mut *tx, calendar).await?;
for object in objects {
Self::_put_object(
&mut *tx,
principal.to_owned(),
cal_id.clone(),
object,
false,
)
.await?;
}
tx.commit().await.map_err(crate::Error::from)?;
Ok(())
}
} }
// Logs an operation to the events // Logs an operation to the events