lift restriction on object_id and UID having to match

addresses #135
This commit is contained in:
Lennart
2025-11-01 21:48:37 +01:00
parent db144ebcae
commit 76b4194b94
21 changed files with 234 additions and 103 deletions

View File

@@ -0,0 +1,32 @@
{
"db_name": "SQLite",
"query": "SELECT id, uid, ics FROM calendarobjects\n WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL\n AND (last_occurence IS NULL OR ? IS NULL OR last_occurence >= date(?))\n AND (first_occurence IS NULL OR ? IS NULL OR first_occurence <= date(?))\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "uid",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "ics",
"ordinal": 2,
"type_info": "Text"
}
],
"parameters": {
"Right": 6
},
"nullable": [
false,
false,
false
]
},
"hash": "3a29efff3d3f6e1e05595d1a2d095af5fc963572c90bd10a6616af78757f8c39"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "REPLACE INTO calendarobjects (principal, cal_id, id, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, date(?), date(?), ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 8
},
"nullable": []
},
"hash": "3e1cca532372e891ab3e604ecb79311d8cd64108d4f238db4c79e9467a3b6d2e"
}

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT id, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)", "query": "SELECT id, uid, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -9,18 +9,24 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "ics", "name": "uid",
"ordinal": 1, "ordinal": 1,
"type_info": "Text" "type_info": "Text"
},
{
"name": "ics",
"ordinal": 2,
"type_info": "Text"
} }
], ],
"parameters": { "parameters": {
"Right": 4 "Right": 4
}, },
"nullable": [ "nullable": [
false,
false, false,
false false
] ]
}, },
"hash": "543838c030550cb09d1af08adfeade8b7ce3575d92fddbc6e9582d141bc9e49d" "hash": "505ebe8e64ac709b230dce7150240965e45442aca6c5f3b3115738ef508939ed"
} }

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO calendarobjects (principal, cal_id, id, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, date(?), date(?), ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 8
},
"nullable": []
},
"hash": "6327bee90e5df01536a0ddb15adcc37af3027f6902aa3786365c5ab2fbf06bda"
}

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT id, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL", "query": "SELECT id, uid, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -9,18 +9,24 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "ics", "name": "uid",
"ordinal": 1, "ordinal": 1,
"type_info": "Text" "type_info": "Text"
},
{
"name": "ics",
"ordinal": 2,
"type_info": "Text"
} }
], ],
"parameters": { "parameters": {
"Right": 2 "Right": 2
}, },
"nullable": [ "nullable": [
false,
false, false,
false false
] ]
}, },
"hash": "54c9c0e36a52e6963f11c6aa27f13aafb4204b8aa34b664fd825bd447db80e86" "hash": "804ed2a4a7032e9605d1871297498f5a96de0fc816ce660c705fb28318be0d42"
} }

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "REPLACE INTO calendarobjects (principal, cal_id, id, uid, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, ?, date(?), date(?), ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "a68a1b96189b854a7ba2a3cd866ba583af5ad84bc1cd8b20cb805e9ce3bad820"
}

View File

@@ -1,26 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT id, ics FROM calendarobjects\n WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL\n AND (last_occurence IS NULL OR ? IS NULL OR last_occurence >= date(?))\n AND (first_occurence IS NULL OR ? IS NULL OR first_occurence <= date(?))\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "ics",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 6
},
"nullable": [
false,
false
]
},
"hash": "c550dbf3d5ce7069f28d767ea9045e477ef8d29d6186851760757a06dec42339"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO calendarobjects (principal, cal_id, id, uid, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, ?, date(?), date(?), ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "d498a758ed707408b00b7d2675250ea739a681ce1f009f05e97f2e101bd7e556"
}

View File

@@ -82,7 +82,7 @@ pub async fn route_import<C: CalendarStore, S: SubscriptionStore>(
let objects = expanded_cals let objects = expanded_cals
.into_iter() .into_iter()
.map(|cal| cal.generate()) .map(|cal| cal.generate())
.map(CalendarObject::from_ics) .map(|ics| CalendarObject::from_ics(ics, None))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let new_cal = Calendar { let new_cal = Calendar {
principal, principal,

View File

@@ -61,7 +61,7 @@ fn objects_response(
) -> Result<MultistatusElement<CalendarObjectPropWrapper, String>, Error> { ) -> Result<MultistatusElement<CalendarObjectPropWrapper, String>, Error> {
let mut responses = Vec::new(); let mut responses = Vec::new();
for object in objects { for object in objects {
let path = format!("{}/{}.ics", path, object.get_uid()); let path = format!("{}/{}.ics", path, object.get_id());
responses.push( responses.push(
CalendarObjectResource { CalendarObjectResource {
object, object,

View File

@@ -33,7 +33,7 @@ pub async fn handle_sync_collection<C: CalendarStore>(
let mut responses = Vec::new(); let mut responses = Vec::new();
for object in new_objects { for object in new_objects {
let path = format!("{}/{}.ics", path, object.get_uid()); let path = format!("{}/{}.ics", path, object.get_id());
responses.push( responses.push(
CalendarObjectResource { CalendarObjectResource {
object, object,

View File

@@ -11,7 +11,7 @@ use rustical_ical::CalendarObject;
use rustical_store::CalendarStore; use rustical_store::CalendarStore;
use rustical_store::auth::Principal; use rustical_store::auth::Principal;
use std::str::FromStr; use std::str::FromStr;
use tracing::{debug, error, instrument}; use tracing::{debug, instrument};
#[instrument(skip(cal_store))] #[instrument(skip(cal_store))]
pub async fn get_event<C: CalendarStore>( pub async fn get_event<C: CalendarStore>(
@@ -78,18 +78,10 @@ pub async fn put_event<C: CalendarStore>(
true true
}; };
let Ok(object) = CalendarObject::from_ics(body.clone()) else { let Ok(object) = CalendarObject::from_ics(body.clone(), Some(object_id)) else {
debug!("invalid calendar data:\n{body}"); debug!("invalid calendar data:\n{body}");
return Err(Error::PreconditionFailed(Precondition::ValidCalendarData)); return Err(Error::PreconditionFailed(Precondition::ValidCalendarData));
}; };
if object.get_uid() != object_id {
error!(
"Calendar object UID and file name not matching: UID={}, filename={}",
object.get_uid(),
object_id
);
return Err(Error::PreconditionFailed(Precondition::MatchingUid));
}
cal_store cal_store
.put_object(principal, calendar_id, object, overwrite) .put_object(principal, calendar_id, object, overwrite)
.await?; .await?;

View File

@@ -21,7 +21,7 @@ pub struct CalendarObjectResource {
impl ResourceName for CalendarObjectResource { impl ResourceName for CalendarObjectResource {
fn get_name(&self) -> String { fn get_name(&self) -> String {
format!("{}.ics", self.object.get_uid()) format!("{}.ics", self.object.get_id())
} }
} }

View File

@@ -12,8 +12,6 @@ pub enum Precondition {
#[error("valid-calendar-data")] #[error("valid-calendar-data")]
#[xml(ns = "rustical_dav::namespace::NS_CALDAV")] #[xml(ns = "rustical_dav::namespace::NS_CALDAV")]
ValidCalendarData, ValidCalendarData,
#[error("matching-uid")]
MatchingUid,
} }
impl IntoResponse for Precondition { impl IntoResponse for Precondition {

View File

@@ -97,8 +97,9 @@ impl AddressObject {
let uid = format!("{}-anniversary", self.get_id()); let uid = format!("{}-anniversary", self.get_id());
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default(); let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
Some(CalendarObject::from_ics(format!( Some(CalendarObject::from_ics(
r"BEGIN:VCALENDAR format!(
r"BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
CALSCALE:GREGORIAN CALSCALE:GREGORIAN
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
@@ -116,7 +117,9 @@ DESCRIPTION:💍 {fullname}{year_suffix}
END:VALARM END:VALARM
END:VEVENT END:VEVENT
END:VCALENDAR", END:VCALENDAR",
))?) ),
None,
)?)
} else { } else {
None None
}, },
@@ -136,8 +139,9 @@ END:VCALENDAR",
let uid = format!("{}-birthday", self.get_id()); let uid = format!("{}-birthday", self.get_id());
let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default(); let year_suffix = year.map(|year| format!(" ({year})")).unwrap_or_default();
Some(CalendarObject::from_ics(format!( Some(CalendarObject::from_ics(
r"BEGIN:VCALENDAR format!(
r"BEGIN:VCALENDAR
VERSION:2.0 VERSION:2.0
CALSCALE:GREGORIAN CALSCALE:GREGORIAN
PRODID:-//github.com/lennart-k/rustical birthday calendar//EN PRODID:-//github.com/lennart-k/rustical birthday calendar//EN
@@ -155,7 +159,9 @@ DESCRIPTION:🎂 {fullname}{year_suffix}
END:VALARM END:VALARM
END:VEVENT END:VEVENT
END:VCALENDAR", END:VCALENDAR",
))?) ),
None,
)?)
} else { } else {
None None
}, },

View File

@@ -251,7 +251,7 @@ END:VEVENT\r\n",
#[test] #[test]
fn test_expand_recurrence() { fn test_expand_recurrence() {
let event = CalendarObject::from_ics(ICS.to_string()).unwrap(); let event = CalendarObject::from_ics(ICS.to_string(), None).unwrap();
let crate::CalendarObjectComponent::Event(event, overrides) = event.get_data() else { let crate::CalendarObjectComponent::Event(event, overrides) = event.get_data() else {
panic!() panic!()
}; };

View File

@@ -64,6 +64,19 @@ pub enum CalendarObjectComponent {
Journal(IcalJournal, Vec<IcalJournal>), Journal(IcalJournal, Vec<IcalJournal>),
} }
impl CalendarObjectComponent {
#[must_use]
pub fn get_uid(&self) -> &str {
match &self {
// We've made sure before that the first component exists and all components share the
// same UID
Self::Todo(todo, _) => todo.get_uid(),
Self::Event(event, _) => event.event.get_uid(),
Self::Journal(journal, _) => journal.get_uid(),
}
}
}
impl From<&CalendarObjectComponent> for CalendarObjectType { impl From<&CalendarObjectComponent> for CalendarObjectType {
fn from(value: &CalendarObjectComponent) -> Self { fn from(value: &CalendarObjectComponent) -> Self {
match value { match value {
@@ -141,12 +154,13 @@ impl CalendarObjectComponent {
pub struct CalendarObject { pub struct CalendarObject {
data: CalendarObjectComponent, data: CalendarObjectComponent,
properties: Vec<Property>, properties: Vec<Property>,
id: String,
ics: String, ics: String,
vtimezones: HashMap<String, IcalTimeZone>, vtimezones: HashMap<String, IcalTimeZone>,
} }
impl CalendarObject { impl CalendarObject {
pub fn from_ics(ics: String) -> Result<Self, Error> { pub fn from_ics(ics: String, id: Option<String>) -> Result<Self, Error> {
let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes())); let mut parser = ical::IcalParser::new(BufReader::new(ics.as_bytes()));
let cal = parser.next().ok_or(Error::MissingCalendar)??; let cal = parser.next().ok_or(Error::MissingCalendar)??;
if parser.next().is_some() { if parser.next().is_some() {
@@ -202,6 +216,7 @@ impl CalendarObject {
}; };
Ok(Self { Ok(Self {
id: id.unwrap_or_else(|| data.get_uid().to_owned()),
data, data,
properties: cal.properties, properties: cal.properties,
ics, ics,
@@ -221,13 +236,12 @@ impl CalendarObject {
#[must_use] #[must_use]
pub fn get_uid(&self) -> &str { pub fn get_uid(&self) -> &str {
match &self.data { self.data.get_uid()
// We've made sure before that the first component exists and all components share the }
// same UID
CalendarObjectComponent::Todo(todo, _) => todo.get_uid(), #[must_use]
CalendarObjectComponent::Event(event, _) => event.event.get_uid(), pub fn get_id(&self) -> &str {
CalendarObjectComponent::Journal(journal, _) => journal.get_uid(), &self.id
}
} }
#[must_use] #[must_use]

View File

@@ -25,6 +25,6 @@ END:VCALENDAR
#[test] #[test]
fn parse_calendar_object() { fn parse_calendar_object() {
let object = CalendarObject::from_ics(MULTI_VEVENT.to_string()).unwrap(); let object = CalendarObject::from_ics(MULTI_VEVENT.to_string(), None).unwrap();
object.expand_recurrence(None, None).unwrap(); object.expand_recurrence(None, None).unwrap();
} }

View File

@@ -0,0 +1,47 @@
DROP INDEX idx_calobjs_uid;
ALTER TABLE calendarobjects RENAME TO calendarobjects_old;
CREATE TABLE calendarobjects (
principal TEXT NOT NULL,
cal_id TEXT NOT NULL,
id TEXT NOT NULL, -- filename
ics TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME,
-- For more efficient calendar-queries
first_occurence DATE,
last_occurence DATE,
etag TEXT,
object_type INTEGER NOT NULL, -- VEVENT(0)/VTODO(1)/VJOURNAL(2)
CONSTRAINT pk_calendarobject_id PRIMARY KEY (principal, cal_id, id),
CONSTRAINT fk_calendarobject_calendar FOREIGN KEY (principal, cal_id)
REFERENCES calendars (principal, id) ON DELETE CASCADE
);
INSERT INTO calendarobjects (
principal,
cal_id,
id,
ics,
updated_at,
deleted_at,
first_occurence,
last_occurence,
etag,
object_type
) SELECT
principal,
cal_id,
id,
ics,
updated_at,
deleted_at,
first_occurence,
last_occurence,
etag,
object_type
FROM calendarobjects_old;
DROP TABLE calendarobjects_old;

View File

@@ -0,0 +1,53 @@
-- Adds the column "uid" and populates it with data from "id"
ALTER TABLE calendarobjects RENAME TO calendarobjects_old;
CREATE TABLE calendarobjects (
principal TEXT NOT NULL,
cal_id TEXT NOT NULL,
id TEXT NOT NULL, -- filename
"uid" TEXT NOT NULL, -- global identifier
ics TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME,
-- For more efficient calendar-queries
first_occurence DATE,
last_occurence DATE,
etag TEXT,
object_type INTEGER NOT NULL, -- VEVENT(0)/VTODO(1)/VJOURNAL(2)
CONSTRAINT pk_calendarobject_id PRIMARY KEY (principal, cal_id, id),
CONSTRAINT uq_calendarobject_uid UNIQUE (principal, cal_id, "uid"),
CONSTRAINT fk_calendarobject_calendar FOREIGN KEY (principal, cal_id)
REFERENCES calendars (principal, id) ON DELETE CASCADE
);
CREATE INDEX idx_calobjs_uid ON calendarobjects (principal, cal_id, "uid");
INSERT INTO calendarobjects (
principal,
cal_id,
id,
"uid",
ics,
updated_at,
deleted_at,
first_occurence,
last_occurence,
etag,
object_type
) SELECT
principal,
cal_id,
id,
id AS "uid",
ics,
updated_at,
deleted_at,
first_occurence,
last_occurence,
etag,
object_type
FROM calendarobjects_old;
DROP TABLE calendarobjects_old;

View File

@@ -17,19 +17,20 @@ use tracing::{error, instrument};
struct CalendarObjectRow { struct CalendarObjectRow {
id: String, id: String,
ics: String, ics: String,
uid: String,
} }
impl TryFrom<CalendarObjectRow> for CalendarObject { impl TryFrom<CalendarObjectRow> for CalendarObject {
type Error = rustical_store::Error; type Error = rustical_store::Error;
fn try_from(value: CalendarObjectRow) -> Result<Self, Self::Error> { fn try_from(value: CalendarObjectRow) -> Result<Self, Self::Error> {
let object = Self::from_ics(value.ics)?; let object = Self::from_ics(value.ics, Some(value.id))?;
if object.get_uid() != value.id { if object.get_uid() != value.uid {
return Err(rustical_store::Error::IcalError( return Err(rustical_store::Error::IcalError(
rustical_ical::Error::InvalidData(format!( rustical_ical::Error::InvalidData(format!(
"object_id={} and UID={} don't match", "uid={} and UID={} don't match",
object.get_uid(), value.uid,
value.id object.get_uid()
)), )),
)); ));
} }
@@ -281,7 +282,7 @@ impl SqliteCalendarStore {
) -> Result<Vec<CalendarObject>, Error> { ) -> Result<Vec<CalendarObject>, Error> {
sqlx::query_as!( sqlx::query_as!(
CalendarObjectRow, CalendarObjectRow,
"SELECT id, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL", "SELECT id, uid, ics FROM calendarobjects WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL",
principal, principal,
cal_id cal_id
) )
@@ -306,7 +307,7 @@ impl SqliteCalendarStore {
sqlx::query_as!( sqlx::query_as!(
CalendarObjectRow, CalendarObjectRow,
r"SELECT id, ics FROM calendarobjects r"SELECT id, uid, ics FROM calendarobjects
WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL WHERE principal = ? AND cal_id = ? AND deleted_at IS NULL
AND (last_occurence IS NULL OR ? IS NULL OR last_occurence >= date(?)) AND (last_occurence IS NULL OR ? IS NULL OR last_occurence >= date(?))
AND (first_occurence IS NULL OR ? IS NULL OR first_occurence <= date(?)) AND (first_occurence IS NULL OR ? IS NULL OR first_occurence <= date(?))
@@ -335,7 +336,7 @@ impl SqliteCalendarStore {
) -> Result<CalendarObject, Error> { ) -> Result<CalendarObject, Error> {
sqlx::query_as!( sqlx::query_as!(
CalendarObjectRow, CalendarObjectRow,
"SELECT id, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)", "SELECT id, uid, ics FROM calendarobjects WHERE (principal, cal_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) OR ?)",
principal, principal,
cal_id, cal_id,
object_id, object_id,
@@ -355,7 +356,7 @@ impl SqliteCalendarStore {
object: CalendarObject, object: CalendarObject,
overwrite: bool, overwrite: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
let (object_id, ics) = (object.get_uid(), object.get_ics()); let (object_id, uid, ics) = (object.get_id(), object.get_uid(), object.get_ics());
let first_occurence = object let first_occurence = object
.get_first_occurence() .get_first_occurence()
@@ -374,10 +375,11 @@ impl SqliteCalendarStore {
(if overwrite { (if overwrite {
sqlx::query!( sqlx::query!(
"REPLACE INTO calendarobjects (principal, cal_id, id, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, date(?), date(?), ?, ?)", "REPLACE INTO calendarobjects (principal, cal_id, id, uid, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, ?, date(?), date(?), ?, ?)",
principal, principal,
cal_id, cal_id,
object_id, object_id,
uid,
ics, ics,
first_occurence, first_occurence,
last_occurence, last_occurence,
@@ -387,10 +389,11 @@ impl SqliteCalendarStore {
} else { } else {
// If the object already exists a database error is thrown and handled in error.rs // If the object already exists a database error is thrown and handled in error.rs
sqlx::query!( sqlx::query!(
"INSERT INTO calendarobjects (principal, cal_id, id, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, date(?), date(?), ?, ?)", "INSERT INTO calendarobjects (principal, cal_id, id, uid, ics, first_occurence, last_occurence, etag, object_type) VALUES (?, ?, ?, ?, ?, date(?), date(?), ?, ?)",
principal, principal,
cal_id, cal_id,
object_id, object_id,
uid,
ics, ics,
first_occurence, first_occurence,
last_occurence, last_occurence,
@@ -410,7 +413,7 @@ impl SqliteCalendarStore {
executor: E, executor: E,
principal: &str, principal: &str,
cal_id: &str, cal_id: &str,
id: &str, object_id: &str,
use_trashbin: bool, use_trashbin: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
if use_trashbin { if use_trashbin {
@@ -418,7 +421,7 @@ impl SqliteCalendarStore {
"UPDATE calendarobjects SET deleted_at = datetime(), updated_at = datetime() WHERE (principal, cal_id, id) = (?, ?, ?)", "UPDATE calendarobjects SET deleted_at = datetime(), updated_at = datetime() WHERE (principal, cal_id, id) = (?, ?, ?)",
principal, principal,
cal_id, cal_id,
id object_id
) )
.execute(executor) .execute(executor)
.await.map_err(crate::Error::from)?; .await.map_err(crate::Error::from)?;
@@ -426,7 +429,7 @@ impl SqliteCalendarStore {
sqlx::query!( sqlx::query!(
"DELETE FROM calendarobjects WHERE cal_id = ? AND id = ?", "DELETE FROM calendarobjects WHERE cal_id = ? AND id = ?",
cal_id, cal_id,
id object_id
) )
.execute(executor) .execute(executor)
.await .await
@@ -678,7 +681,7 @@ impl CalendarStore for SqliteCalendarStore {
.await .await
.map_err(crate::Error::from)?; .map_err(crate::Error::from)?;
let object_id = object.get_uid().to_owned(); let object_id = object.get_id().to_owned();
let calendar = Self::_get_calendar(&mut *tx, &principal, &cal_id, true).await?; let calendar = Self::_get_calendar(&mut *tx, &principal, &cal_id, true).await?;
if calendar.subscription_url.is_some() { if calendar.subscription_url.is_some() {