caldav: Add calendar-timezone-id

This commit is contained in:
Lennart
2025-01-04 16:48:15 +01:00
parent d1350af269
commit 5738f56dfe
13 changed files with 74 additions and 37 deletions

View File

@@ -1,12 +1,12 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "UPDATE calendars SET principal = ?, id = ?, displayname = ?, description = ?, \"order\" = ?, color = ?, timezone = ?\n WHERE (principal, id) = (?, ?)", "query": "UPDATE calendars SET principal = ?, id = ?, displayname = ?, description = ?, \"order\" = ?, color = ?, timezone = ?, timezone_id = ?\n WHERE (principal, id) = (?, ?)",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
"Right": 9 "Right": 10
}, },
"nullable": [] "nullable": []
}, },
"hash": "c7a56d737fc8627427d0be202c3c77bb95c1a25b6a3c0c389ef0ad4f468320ee" "hash": "0d0bd23849e3e9492531fa8ce0eb22832d0c16d690cebbcb7e546cab3e942d64"
} }

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, deleted_at, subscription_url\n FROM calendars\n WHERE principal = ? AND deleted_at IS NULL", "query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, timezone_id, deleted_at, subscription_url\n FROM calendars\n WHERE principal = ? AND deleted_at IS NULL",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -44,13 +44,18 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "deleted_at", "name": "timezone_id",
"ordinal": 8, "ordinal": 8,
"type_info": "Text"
},
{
"name": "deleted_at",
"ordinal": 9,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "subscription_url", "name": "subscription_url",
"ordinal": 9, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -67,8 +72,9 @@
true, true,
true, true,
true, true,
true,
true true
] ]
}, },
"hash": "7d27bdb54fbc8e65e5482fa9bc974e57c1fc7abcc5c7ca6bbb4944554381285c" "hash": "24ece624823b418fa96a867f57d62ed755035e88b0d9363b71aac8aedff1cbb6"
} }

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO calendars (principal, id, displayname, description, \"order\", color, timezone, timezone_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 8
},
"nullable": []
},
"hash": "32969555ebbf5d7220c1c0d2d2c793a69d9bbaa1a1a9ec16611ad1b1d303d5d6"
}

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, deleted_at, subscription_url\n FROM calendars\n WHERE principal = ? AND deleted_at IS NOT NULL", "query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, timezone_id, deleted_at, subscription_url\n FROM calendars\n WHERE principal = ? AND deleted_at IS NOT NULL",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -44,13 +44,18 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "deleted_at", "name": "timezone_id",
"ordinal": 8, "ordinal": 8,
"type_info": "Text"
},
{
"name": "deleted_at",
"ordinal": 9,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "subscription_url", "name": "subscription_url",
"ordinal": 9, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -67,8 +72,9 @@
true, true,
true, true,
true, true,
true,
true true
] ]
}, },
"hash": "97d66b4b76bca677badd9087a6bab978a54d36e4bb12ea77a6d52b9dfa08312c" "hash": "9770a3a1c85aa92e7787ede67e86aa680e0831613cc9cb87cb19928fc538d35c"
} }

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT principal, id, synctoken, \"order\", displayname, description, color, timezone, deleted_at, subscription_url\n FROM calendars\n WHERE (principal, id) = (?, ?)", "query": "SELECT principal, id, synctoken, \"order\", displayname, description, color, timezone, timezone_id, deleted_at, subscription_url\n FROM calendars\n WHERE (principal, id) = (?, ?)",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -44,13 +44,18 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "deleted_at", "name": "timezone_id",
"ordinal": 8, "ordinal": 8,
"type_info": "Text"
},
{
"name": "deleted_at",
"ordinal": 9,
"type_info": "Datetime" "type_info": "Datetime"
}, },
{ {
"name": "subscription_url", "name": "subscription_url",
"ordinal": 9, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -67,8 +72,9 @@
true, true,
true, true,
true, true,
true,
true true
] ]
}, },
"hash": "38e44b7a271d9be098f00b233100fbce750a4e167ee547abccbefd36a089f399" "hash": "a74288844bf11fd318c3e688d3a4bc454d8b162d7afbeebfc448211990cd8823"
} }

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO calendars (principal, id, displayname, description, \"order\", color, timezone)\n VALUES (?, ?, ?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "eba774e8d8afa04d33443b42ec3a4af1c2f34515288bade397b87a1e43d789df"
}

View File

@@ -27,6 +27,7 @@ pub struct SupportedCalendarComponentSetElement {
#[derive(XmlDeserialize, Clone, Debug)] #[derive(XmlDeserialize, Clone, Debug)]
pub struct MkcolCalendarProp { pub struct MkcolCalendarProp {
// TODO: Add namespaces
resourcetype: Option<Resourcetype>, resourcetype: Option<Resourcetype>,
displayname: Option<String>, displayname: Option<String>,
calendar_description: Option<String>, calendar_description: Option<String>,
@@ -70,6 +71,7 @@ pub async fn route_mkcalendar<C: CalendarStore + ?Sized>(
order: request.order.unwrap_or(0), order: request.order.unwrap_or(0),
displayname: request.displayname, displayname: request.displayname,
timezone: request.calendar_timezone, timezone: request.calendar_timezone,
timezone_id: request.calendar_timezone_id,
color: request.calendar_color, color: request.calendar_color,
description: request.calendar_description, description: request.calendar_description,
deleted_at: None, deleted_at: None,

View File

@@ -61,6 +61,9 @@ pub enum CalendarProp {
CalendarDescription(Option<String>), CalendarDescription(Option<String>),
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarTimezone(Option<String>), CalendarTimezone(Option<String>),
// https://datatracker.ietf.org/doc/html/rfc7809
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
CalendarTimezoneId(Option<String>),
#[xml(ns = "rustical_dav::namespace::NS_ICAL")] #[xml(ns = "rustical_dav::namespace::NS_ICAL")]
CalendarOrder(Option<i64>), CalendarOrder(Option<i64>),
#[xml(ns = "rustical_dav::namespace::NS_CALDAV", skip_deserializing)] #[xml(ns = "rustical_dav::namespace::NS_CALDAV", skip_deserializing)]
@@ -138,6 +141,9 @@ impl Resource for CalendarResource {
CalendarPropName::CalendarTimezone => { CalendarPropName::CalendarTimezone => {
CalendarProp::CalendarTimezone(self.0.timezone.clone()) CalendarProp::CalendarTimezone(self.0.timezone.clone())
} }
CalendarPropName::CalendarTimezoneId => {
CalendarProp::CalendarTimezoneId(self.0.timezone_id.clone())
}
CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)), CalendarPropName::CalendarOrder => CalendarProp::CalendarOrder(Some(self.0.order)),
CalendarPropName::SupportedCalendarComponentSet => { CalendarPropName::SupportedCalendarComponentSet => {
CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default()) CalendarProp::SupportedCalendarComponentSet(SupportedCalendarComponentSet::default())
@@ -186,6 +192,11 @@ impl Resource for CalendarResource {
self.0.timezone = timezone; self.0.timezone = timezone;
Ok(()) Ok(())
} }
CalendarProp::CalendarTimezoneId(timezone_id) => {
// TODO: Set or remove timezone accordingly
self.0.timezone_id = timezone_id;
Ok(())
}
CalendarProp::CalendarOrder(order) => { CalendarProp::CalendarOrder(order) => {
self.0.order = order.unwrap_or_default(); self.0.order = order.unwrap_or_default();
Ok(()) Ok(())
@@ -224,6 +235,10 @@ impl Resource for CalendarResource {
self.0.timezone = None; self.0.timezone = None;
Ok(()) Ok(())
} }
CalendarPropName::CalendarTimezoneId => {
self.0.timezone_id = None;
Ok(())
}
CalendarPropName::CalendarOrder => { CalendarPropName::CalendarOrder => {
self.0.order = 0; self.0.order = 0;
Ok(()) Ok(())

View File

@@ -40,7 +40,7 @@ pub fn configure_dav<AP: AuthenticationProvider, C: CalendarStore + ?Sized>(
.insert_header(( .insert_header((
HeaderName::from_static("dav"), HeaderName::from_static("dav"),
HeaderValue::from_static( HeaderValue::from_static(
"1, 2, 3, access-control, calendar-access, extended-mkcol", "1, 2, 3, access-control, calendar-access, extended-mkcol, calendar-no-timezone",
), ),
)) ))
.finish(); .finish();

View File

@@ -11,6 +11,7 @@ pub struct Calendar {
pub description: Option<String>, pub description: Option<String>,
pub color: Option<String>, pub color: Option<String>,
pub timezone: Option<String>, pub timezone: Option<String>,
pub timezone_id: Option<String>,
pub deleted_at: Option<NaiveDateTime>, pub deleted_at: Option<NaiveDateTime>,
pub synctoken: i64, pub synctoken: i64,
pub subscription_url: Option<String>, pub subscription_url: Option<String>,

View File

@@ -16,7 +16,6 @@ pub struct EventObject {
impl EventObject { impl EventObject {
pub fn get_first_occurence(&self) -> Result<Option<CalDateTime>, Error> { pub fn get_first_occurence(&self) -> Result<Option<CalDateTime>, Error> {
// This is safe since we enforce the event's existance in the constructor
if let Some(dtstart) = self.event.get_property("DTSTART") { if let Some(dtstart) = self.event.get_property("DTSTART") {
CalDateTime::parse_prop(dtstart, &self.timezones) CalDateTime::parse_prop(dtstart, &self.timezones)
} else { } else {
@@ -25,7 +24,6 @@ impl EventObject {
} }
pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> { pub fn get_last_occurence(&self) -> Result<Option<CalDateTime>, Error> {
// This is safe since we enforce the event's existence in the constructor
if let Some(_rrule) = self.event.get_property("RRULE") { if let Some(_rrule) = self.event.get_property("RRULE") {
// TODO: understand recurrence rules // TODO: understand recurrence rules
return Ok(None); return Ok(None);

View File

@@ -7,6 +7,7 @@ CREATE TABLE calendars (
"order" INT DEFAULT 0 NOT NULL, "order" INT DEFAULT 0 NOT NULL,
color TEXT, color TEXT,
timezone TEXT, timezone TEXT,
timezone_id TEXT,
deleted_at DATETIME, deleted_at DATETIME,
subscription_url TEXT, subscription_url TEXT,
PRIMARY KEY (principal, id) PRIMARY KEY (principal, id)

View File

@@ -62,7 +62,7 @@ impl CalendarStore for SqliteStore {
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> { async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
let cal = sqlx::query_as!( let cal = sqlx::query_as!(
Calendar, Calendar,
r#"SELECT principal, id, synctoken, "order", displayname, description, color, timezone, deleted_at, subscription_url r#"SELECT principal, id, synctoken, "order", displayname, description, color, timezone, timezone_id, deleted_at, subscription_url
FROM calendars FROM calendars
WHERE (principal, id) = (?, ?)"#, WHERE (principal, id) = (?, ?)"#,
principal, principal,
@@ -77,7 +77,7 @@ impl CalendarStore for SqliteStore {
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> { async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
let cals = sqlx::query_as!( let cals = sqlx::query_as!(
Calendar, Calendar,
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at, subscription_url r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, timezone_id, deleted_at, subscription_url
FROM calendars FROM calendars
WHERE principal = ? AND deleted_at IS NULL"#, WHERE principal = ? AND deleted_at IS NULL"#,
principal principal
@@ -91,7 +91,7 @@ impl CalendarStore for SqliteStore {
async fn get_deleted_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> { async fn get_deleted_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
let cals = sqlx::query_as!( let cals = sqlx::query_as!(
Calendar, Calendar,
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at, subscription_url r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, timezone_id, deleted_at, subscription_url
FROM calendars FROM calendars
WHERE principal = ? AND deleted_at IS NOT NULL"#, WHERE principal = ? AND deleted_at IS NOT NULL"#,
principal principal
@@ -104,15 +104,16 @@ impl CalendarStore for SqliteStore {
#[instrument] #[instrument]
async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error> { async fn insert_calendar(&self, calendar: Calendar) -> Result<(), Error> {
sqlx::query!( sqlx::query!(
r#"INSERT INTO calendars (principal, id, displayname, description, "order", color, timezone) r#"INSERT INTO calendars (principal, id, displayname, description, "order", color, timezone, timezone_id)
VALUES (?, ?, ?, ?, ?, ?, ?)"#, VALUES (?, ?, ?, ?, ?, ?, ?, ?)"#,
calendar.principal, calendar.principal,
calendar.id, calendar.id,
calendar.displayname, calendar.displayname,
calendar.description, calendar.description,
calendar.order, calendar.order,
calendar.color, calendar.color,
calendar.timezone calendar.timezone,
calendar.timezone_id
) )
.execute(&self.db) .execute(&self.db)
.await.map_err(crate::Error::from)?; .await.map_err(crate::Error::from)?;
@@ -127,7 +128,7 @@ impl CalendarStore for SqliteStore {
calendar: Calendar, calendar: Calendar,
) -> Result<(), Error> { ) -> Result<(), Error> {
let result = sqlx::query!( let result = sqlx::query!(
r#"UPDATE calendars SET principal = ?, id = ?, displayname = ?, description = ?, "order" = ?, color = ?, timezone = ? r#"UPDATE calendars SET principal = ?, id = ?, displayname = ?, description = ?, "order" = ?, color = ?, timezone = ?, timezone_id = ?
WHERE (principal, id) = (?, ?)"#, WHERE (principal, id) = (?, ?)"#,
calendar.principal, calendar.principal,
calendar.id, calendar.id,
@@ -136,6 +137,7 @@ impl CalendarStore for SqliteStore {
calendar.order, calendar.order,
calendar.color, calendar.color,
calendar.timezone, calendar.timezone,
calendar.timezone_id,
principal, principal,
id id
).execute(&self.db).await.map_err(crate::Error::from)?; ).execute(&self.db).await.map_err(crate::Error::from)?;