mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 19:22:26 +00:00
32
.sqlx/query-3a29efff3d3f6e1e05595d1a2d095af5fc963572c90bd10a6616af78757f8c39.json
generated
Normal file
32
.sqlx/query-3a29efff3d3f6e1e05595d1a2d095af5fc963572c90bd10a6616af78757f8c39.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
12
.sqlx/query-a68a1b96189b854a7ba2a3cd866ba583af5ad84bc1cd8b20cb805e9ce3bad820.json
generated
Normal file
12
.sqlx/query-a68a1b96189b854a7ba2a3cd866ba583af5ad84bc1cd8b20cb805e9ce3bad820.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
12
.sqlx/query-d498a758ed707408b00b7d2675250ea739a681ce1f009f05e97f2e101bd7e556.json
generated
Normal file
12
.sqlx/query-d498a758ed707408b00b7d2675250ea739a681ce1f009f05e97f2e101bd7e556.json
generated
Normal 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"
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ 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(
|
||||||
|
format!(
|
||||||
r"BEGIN:VCALENDAR
|
r"BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
CALSCALE:GREGORIAN
|
CALSCALE:GREGORIAN
|
||||||
@@ -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,7 +139,8 @@ 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(
|
||||||
|
format!(
|
||||||
r"BEGIN:VCALENDAR
|
r"BEGIN:VCALENDAR
|
||||||
VERSION:2.0
|
VERSION:2.0
|
||||||
CALSCALE:GREGORIAN
|
CALSCALE:GREGORIAN
|
||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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!()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
CalendarObjectComponent::Event(event, _) => event.event.get_uid(),
|
|
||||||
CalendarObjectComponent::Journal(journal, _) => journal.get_uid(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_id(&self) -> &str {
|
||||||
|
&self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user