mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 14:42:30 +00:00
caldav: add support for calendar subscriptions
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT principal, id, synctoken, \"order\", displayname, description, color, timezone, deleted_at\n FROM calendars\n WHERE (principal, id) = (?, ?)",
|
||||
"query": "SELECT principal, id, synctoken, \"order\", displayname, description, color, timezone, deleted_at, subscription_url\n FROM calendars\n WHERE (principal, id) = (?, ?)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -47,6 +47,11 @@
|
||||
"name": "deleted_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "subscription_url",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -60,9 +65,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "b66f33bc98029e9bc6427e61d15484a776e8eccb2b72a2fb2d4a5edea90067e5"
|
||||
"hash": "38e44b7a271d9be098f00b233100fbce750a4e167ee547abccbefd36a089f399"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, deleted_at\n FROM calendars\n WHERE principal = ? AND deleted_at IS NOT NULL",
|
||||
"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",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -47,6 +47,11 @@
|
||||
"name": "deleted_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "subscription_url",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -60,9 +65,10 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "52fcb1f2472a6e14da26a9733b3b9e8a79354f287b3c347bc343f9747f01f134"
|
||||
"hash": "7d27bdb54fbc8e65e5482fa9bc974e57c1fc7abcc5c7ca6bbb4944554381285c"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT principal, id, synctoken, displayname, \"order\", description, color, timezone, deleted_at\n FROM calendars\n WHERE principal = ? AND deleted_at IS NULL",
|
||||
"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",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -47,6 +47,11 @@
|
||||
"name": "deleted_at",
|
||||
"ordinal": 8,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "subscription_url",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -60,9 +65,10 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f387e6ef026d8314e78c0672652fb3d6876598ecde4db98d7a303c9e4c676376"
|
||||
"hash": "97d66b4b76bca677badd9087a6bab978a54d36e4bb12ea77a6d52b9dfa08312c"
|
||||
}
|
||||
@@ -79,6 +79,7 @@ pub async fn route_mkcalendar<C: CalendarStore + ?Sized>(
|
||||
description: request.calendar_description,
|
||||
deleted_at: None,
|
||||
synctoken: 0,
|
||||
subscription_url: None,
|
||||
};
|
||||
|
||||
match store.get_calendar(&principal, &cal_id).await {
|
||||
|
||||
@@ -15,6 +15,7 @@ use async_trait::async_trait;
|
||||
use derive_more::derive::{From, Into};
|
||||
use rustical_dav::privileges::UserPrivilegeSet;
|
||||
use rustical_dav::resource::{Resource, ResourceService};
|
||||
use rustical_dav::xml::HrefElement;
|
||||
use rustical_store::auth::User;
|
||||
use rustical_store::{Calendar, CalendarStore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -43,6 +44,7 @@ pub enum CalendarPropName {
|
||||
SupportedReportSet,
|
||||
SyncToken,
|
||||
Getctag,
|
||||
Source,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, PartialEq)]
|
||||
@@ -82,6 +84,8 @@ pub enum CalendarProp {
|
||||
// CalendarServer
|
||||
#[serde(rename = "CS:getctag", alias = "getctag")]
|
||||
Getctag(String),
|
||||
#[serde(rename = "CS:source", alias = "source")]
|
||||
Source(Option<HrefElement>),
|
||||
|
||||
#[serde(other)]
|
||||
#[default]
|
||||
@@ -97,8 +101,12 @@ impl Resource for CalendarResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
&["collection", "C:calendar"]
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
if self.0.subscription_url.is_none() {
|
||||
&["collection", "C:calendar"]
|
||||
} else {
|
||||
&["collection", "CS:subscribed"]
|
||||
}
|
||||
}
|
||||
|
||||
fn get_prop(
|
||||
@@ -144,6 +152,9 @@ impl Resource for CalendarResource {
|
||||
}
|
||||
CalendarPropName::SyncToken => CalendarProp::SyncToken(self.0.format_synctoken()),
|
||||
CalendarPropName::Getctag => CalendarProp::Getctag(self.0.format_synctoken()),
|
||||
CalendarPropName::Source => {
|
||||
CalendarProp::Source(self.0.subscription_url.to_owned().map(HrefElement::from))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -178,6 +189,8 @@ impl Resource for CalendarResource {
|
||||
CalendarProp::SupportedReportSet(_) => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarProp::SyncToken(_) => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarProp::Getctag(_) => Err(rustical_dav::Error::PropReadOnly),
|
||||
// Converting between a calendar subscription calendar and a normal one would be weird
|
||||
CalendarProp::Source(_) => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarProp::Invalid => Err(rustical_dav::Error::PropReadOnly),
|
||||
}
|
||||
}
|
||||
@@ -213,6 +226,8 @@ impl Resource for CalendarResource {
|
||||
CalendarPropName::SupportedReportSet => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarPropName::SyncToken => Err(rustical_dav::Error::PropReadOnly),
|
||||
CalendarPropName::Getctag => Err(rustical_dav::Error::PropReadOnly),
|
||||
// Converting a calendar subscription calendar into a normal one would be weird
|
||||
CalendarPropName::Source => Err(rustical_dav::Error::PropReadOnly),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +241,7 @@ impl Resource for CalendarResource {
|
||||
}
|
||||
|
||||
fn get_user_privileges(&self, user: &User) -> Result<UserPrivilegeSet, Self::Error> {
|
||||
// TODO: read-only for subscription
|
||||
Ok(UserPrivilegeSet::owner_only(self.0.principal == user.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl Resource for CalendarObjectResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl Resource for PrincipalResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&["collection", "principal"]
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ impl Resource for AddressObjectResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&[]
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ impl Resource for AddressbookResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&["collection", "CARD:addressbook"]
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl Resource for PrincipalResource {
|
||||
type Error = Error;
|
||||
type PrincipalResource = PrincipalResource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&["collection", "principal"]
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ pub trait Resource: Clone + 'static {
|
||||
type Error: ResponseError + From<crate::Error>;
|
||||
type PrincipalResource: Resource;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str];
|
||||
fn get_resourcetype(&self) -> &'static [&'static str];
|
||||
|
||||
fn list_props() -> Vec<&'static str> {
|
||||
[Self::PropName::VARIANTS, CommonPropertiesPropName::VARIANTS].concat()
|
||||
@@ -84,7 +84,7 @@ pub trait Resource: Clone + 'static {
|
||||
) -> Result<CommonPropertiesProp, Self::Error> {
|
||||
Ok(match prop {
|
||||
CommonPropertiesPropName::Resourcetype => {
|
||||
CommonPropertiesProp::Resourcetype(Resourcetype(Self::get_resourcetype()))
|
||||
CommonPropertiesProp::Resourcetype(Resourcetype(self.get_resourcetype()))
|
||||
}
|
||||
CommonPropertiesPropName::CurrentUserPrincipal => {
|
||||
CommonPropertiesProp::CurrentUserPrincipal(
|
||||
|
||||
@@ -35,7 +35,7 @@ impl<PR: Resource> Resource for RootResource<PR> {
|
||||
type Error = PR::Error;
|
||||
type PrincipalResource = PR;
|
||||
|
||||
fn get_resourcetype() -> &'static [&'static str] {
|
||||
fn get_resourcetype(&self) -> &'static [&'static str] {
|
||||
&["collection"]
|
||||
}
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ pub struct MultistatusElement<PropType: Serialize, MemberPropType: Serialize> {
|
||||
pub ns_caldav: &'static str,
|
||||
#[serde(rename = "@xmlns:IC")]
|
||||
pub ns_ical: &'static str,
|
||||
#[serde(rename = "@xmlns:CS")]
|
||||
pub ns_calendarserver: &'static str,
|
||||
#[serde(rename = "@xmlns:CARD")]
|
||||
pub ns_carddav: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -97,6 +99,7 @@ impl<T1: Serialize, T2: Serialize> Default for MultistatusElement<T1, T2> {
|
||||
ns_dav: Namespace::Dav.as_str(),
|
||||
ns_caldav: Namespace::CalDAV.as_str(),
|
||||
ns_ical: Namespace::ICal.as_str(),
|
||||
ns_calendarserver: Namespace::CServer.as_str(),
|
||||
ns_carddav: Namespace::CardDAV.as_str(),
|
||||
sync_token: None,
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct Calendar {
|
||||
pub timezone: Option<String>,
|
||||
pub deleted_at: Option<NaiveDateTime>,
|
||||
pub synctoken: i64,
|
||||
pub subscription_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
|
||||
@@ -8,6 +8,7 @@ CREATE TABLE calendars (
|
||||
color TEXT,
|
||||
timezone TEXT,
|
||||
deleted_at DATETIME,
|
||||
subscription_url TEXT,
|
||||
PRIMARY KEY (principal, id)
|
||||
);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl CalendarStore for SqliteStore {
|
||||
async fn get_calendar(&self, principal: &str, id: &str) -> Result<Calendar, Error> {
|
||||
let cal = sqlx::query_as!(
|
||||
Calendar,
|
||||
r#"SELECT principal, id, synctoken, "order", displayname, description, color, timezone, deleted_at
|
||||
r#"SELECT principal, id, synctoken, "order", displayname, description, color, timezone, deleted_at, subscription_url
|
||||
FROM calendars
|
||||
WHERE (principal, id) = (?, ?)"#,
|
||||
principal,
|
||||
@@ -77,7 +77,7 @@ impl CalendarStore for SqliteStore {
|
||||
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||
let cals = sqlx::query_as!(
|
||||
Calendar,
|
||||
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at
|
||||
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at, subscription_url
|
||||
FROM calendars
|
||||
WHERE principal = ? AND deleted_at IS NULL"#,
|
||||
principal
|
||||
@@ -91,7 +91,7 @@ impl CalendarStore for SqliteStore {
|
||||
async fn get_deleted_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
|
||||
let cals = sqlx::query_as!(
|
||||
Calendar,
|
||||
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at
|
||||
r#"SELECT principal, id, synctoken, displayname, "order", description, color, timezone, deleted_at, subscription_url
|
||||
FROM calendars
|
||||
WHERE principal = ? AND deleted_at IS NOT NULL"#,
|
||||
principal
|
||||
@@ -236,6 +236,7 @@ impl CalendarStore for SqliteStore {
|
||||
object: CalendarObject,
|
||||
overwrite: bool,
|
||||
) -> Result<(), Error> {
|
||||
// TODO: Prevent objects from being commited to a subscription calendar
|
||||
let mut tx = self.db.begin().await.map_err(crate::Error::from)?;
|
||||
|
||||
let (object_id, ics) = (object.get_id(), object.get_ics());
|
||||
@@ -255,7 +256,7 @@ impl CalendarStore for SqliteStore {
|
||||
principal,
|
||||
cal_id,
|
||||
object_id,
|
||||
ics
|
||||
ics,
|
||||
)
|
||||
})
|
||||
.execute(&mut *tx)
|
||||
|
||||
Reference in New Issue
Block a user