From 99388cf992f7cf57f26b560d115249d7ac78ecc7 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Sun, 27 Apr 2025 18:32:17 +0200 Subject: [PATCH] addressbook_store, add option to not return deleted objects with get_object #61 --- ...304f0be0100e4bffad9cc3f6c1a2c6c4b297.json} | 6 +-- ...99f74bc2712a16257ee2119dd211fdb61f7e.json} | 6 +-- crates/carddav/src/address_object/methods.rs | 12 +++--- crates/carddav/src/address_object/resource.rs | 2 +- .../carddav/src/addressbook/methods/mkcol.rs | 9 ++-- .../carddav/src/addressbook/methods/post.rs | 4 +- .../methods/report/addressbook_multiget.rs | 13 +++--- crates/carddav/src/addressbook/resource.rs | 2 +- crates/frontend/src/routes/addressbook.rs | 2 +- crates/store/src/addressbook_store.rs | 10 ++++- crates/store/src/contact_birthday_store.rs | 8 ++-- crates/store_sqlite/src/addressbook_store.rs | 43 +++++++++++-------- 12 files changed, 71 insertions(+), 46 deletions(-) rename .sqlx/{query-c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b.json => query-130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297.json} (83%) rename .sqlx/{query-f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284.json => query-395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e.json} (70%) diff --git a/.sqlx/query-c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b.json b/.sqlx/query-130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297.json similarity index 83% rename from .sqlx/query-c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b.json rename to .sqlx/query-130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297.json index b5385f8..aee5cba 100644 --- a/.sqlx/query-c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b.json +++ b/.sqlx/query-130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT principal, id, synctoken, displayname, description, deleted_at, push_topic\n FROM addressbooks\n WHERE (principal, id) = (?, ?)", + "query": "SELECT principal, id, synctoken, displayname, description, deleted_at, push_topic\n FROM addressbooks\n WHERE (principal, id) = (?, ?)\n AND ((deleted_at IS NULL) OR ?) ", "describe": { "columns": [ { @@ -40,7 +40,7 @@ } ], "parameters": { - "Right": 2 + "Right": 3 }, "nullable": [ false, @@ -52,5 +52,5 @@ false ] }, - "hash": "c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b" + "hash": "130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297" } diff --git a/.sqlx/query-f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284.json b/.sqlx/query-395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e.json similarity index 70% rename from .sqlx/query-f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284.json rename to .sqlx/query-395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e.json index 83adb82..77e3d81 100644 --- a/.sqlx/query-f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284.json +++ b/.sqlx/query-395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id, vcf FROM addressobjects WHERE (principal, addressbook_id, id) = (?, ?, ?)", + "query": "SELECT id, vcf FROM addressobjects WHERE (principal, addressbook_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) or ?)", "describe": { "columns": [ { @@ -15,12 +15,12 @@ } ], "parameters": { - "Right": 3 + "Right": 4 }, "nullable": [ false, false ] }, - "hash": "f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284" + "hash": "395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e" } diff --git a/crates/carddav/src/address_object/methods.rs b/crates/carddav/src/address_object/methods.rs index c143a6d..6a4cd77 100644 --- a/crates/carddav/src/address_object/methods.rs +++ b/crates/carddav/src/address_object/methods.rs @@ -1,11 +1,11 @@ use super::resource::AddressObjectPathComponents; -use crate::addressbook::resource::AddressbookResource; use crate::Error; +use crate::addressbook::resource::AddressbookResource; +use actix_web::HttpRequest; +use actix_web::HttpResponse; use actix_web::http::header; use actix_web::http::header::HeaderValue; use actix_web::web::{Data, Path}; -use actix_web::HttpRequest; -use actix_web::HttpResponse; use rustical_dav::privileges::UserPrivilege; use rustical_dav::resource::Resource; use rustical_store::auth::User; @@ -30,7 +30,9 @@ pub async fn get_object( return Err(Error::Unauthorized); } - let addressbook = store.get_addressbook(&principal, &addressbook_id).await?; + let addressbook = store + .get_addressbook(&principal, &addressbook_id, false) + .await?; let addressbook_resource = AddressbookResource(addressbook); if !addressbook_resource .get_user_privileges(&user)? @@ -40,7 +42,7 @@ pub async fn get_object( } let object = store - .get_object(&principal, &addressbook_id, &object_id) + .get_object(&principal, &addressbook_id, &object_id, false) .await?; Ok(HttpResponse::Ok() diff --git a/crates/carddav/src/address_object/resource.rs b/crates/carddav/src/address_object/resource.rs index ae5ba62..88021bf 100644 --- a/crates/carddav/src/address_object/resource.rs +++ b/crates/carddav/src/address_object/resource.rs @@ -144,7 +144,7 @@ impl ResourceService for AddressObjectResourceService ) -> Result { let object = self .addr_store - .get_object(principal, addressbook_id, object_id) + .get_object(principal, addressbook_id, object_id, false) .await?; Ok(AddressObjectResource { object, diff --git a/crates/carddav/src/addressbook/methods/mkcol.rs b/crates/carddav/src/addressbook/methods/mkcol.rs index e8cf323..732c2e9 100644 --- a/crates/carddav/src/addressbook/methods/mkcol.rs +++ b/crates/carddav/src/addressbook/methods/mkcol.rs @@ -1,7 +1,7 @@ use crate::Error; use actix_web::web::Path; -use actix_web::{web::Data, HttpResponse}; -use rustical_store::{auth::User, Addressbook, AddressbookStore}; +use actix_web::{HttpResponse, web::Data}; +use rustical_store::{Addressbook, AddressbookStore, auth::User}; use rustical_xml::{XmlDeserialize, XmlDocument, XmlRootTag}; use tracing::instrument; use tracing_actix_web::RootSpan; @@ -65,7 +65,10 @@ pub async fn route_mkcol( push_topic: uuid::Uuid::new_v4().to_string(), }; - match store.get_addressbook(&principal, &addressbook_id).await { + match store + .get_addressbook(&principal, &addressbook_id, true) + .await + { Err(rustical_store::Error::NotFound) => { // No conflict, no worries } diff --git a/crates/carddav/src/addressbook/methods/post.rs b/crates/carddav/src/addressbook/methods/post.rs index b66c830..94dc176 100644 --- a/crates/carddav/src/addressbook/methods/post.rs +++ b/crates/carddav/src/addressbook/methods/post.rs @@ -24,7 +24,9 @@ pub async fn route_post( return Err(Error::Unauthorized); } - let addressbook = store.get_addressbook(&principal, &addressbook_id).await?; + let addressbook = store + .get_addressbook(&principal, &addressbook_id, false) + .await?; let request = PushRegister::parse_str(&body)?; let sub_id = uuid::Uuid::new_v4().to_string(); diff --git a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs index 1a2293f..e8889ae 100644 --- a/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs +++ b/crates/carddav/src/addressbook/methods/report/addressbook_multiget.rs @@ -1,17 +1,17 @@ use crate::{ - address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, Error, + address_object::resource::{AddressObjectPropWrapper, AddressObjectResource}, }; use actix_web::{ + HttpRequest, dev::{Path, ResourceDef}, http::StatusCode, - HttpRequest, }; use rustical_dav::{ resource::Resource, - xml::{multistatus::ResponseElement, MultistatusElement, PropElement, PropfindType}, + xml::{MultistatusElement, PropElement, PropfindType, multistatus::ResponseElement}, }; -use rustical_store::{auth::User, AddressObject, AddressbookStore}; +use rustical_store::{AddressObject, AddressbookStore, auth::User}; use rustical_xml::XmlDeserialize; #[derive(XmlDeserialize, Clone, Debug, PartialEq)] @@ -42,7 +42,10 @@ pub async fn get_objects_addressbook_multiget( not_found.push(href.to_owned()); }; let object_id = path.get("object_id").unwrap(); - match store.get_object(principal, addressbook_id, object_id).await { + match store + .get_object(principal, addressbook_id, object_id, false) + .await + { Ok(object) => result.push(object), Err(rustical_store::Error::NotFound) => not_found.push(href.to_owned()), // TODO: Maybe add error handling on a per-object basis diff --git a/crates/carddav/src/addressbook/resource.rs b/crates/carddav/src/addressbook/resource.rs index 80107ef..1488448 100644 --- a/crates/carddav/src/addressbook/resource.rs +++ b/crates/carddav/src/addressbook/resource.rs @@ -211,7 +211,7 @@ impl ResourceService ) -> Result { let addressbook = self .addr_store - .get_addressbook(principal, addressbook_id) + .get_addressbook(principal, addressbook_id, false) .await .map_err(|_e| Error::NotFound)?; Ok(addressbook.into()) diff --git a/crates/frontend/src/routes/addressbook.rs b/crates/frontend/src/routes/addressbook.rs index 700087e..1dddac4 100644 --- a/crates/frontend/src/routes/addressbook.rs +++ b/crates/frontend/src/routes/addressbook.rs @@ -24,7 +24,7 @@ pub async fn route_addressbook( return Ok(HttpResponse::Unauthorized().body("Unauthorized")); } Ok(AddressbookPage { - addressbook: store.get_addressbook(&owner, &addrbook_id).await?, + addressbook: store.get_addressbook(&owner, &addrbook_id, true).await?, } .respond_to(&req)) } diff --git a/crates/store/src/addressbook_store.rs b/crates/store/src/addressbook_store.rs index 9c4deb6..54f182c 100644 --- a/crates/store/src/addressbook_store.rs +++ b/crates/store/src/addressbook_store.rs @@ -1,12 +1,17 @@ use crate::{ - addressbook::{AddressObject, Addressbook}, Error, + addressbook::{AddressObject, Addressbook}, }; use async_trait::async_trait; #[async_trait] pub trait AddressbookStore: Send + Sync + 'static { - async fn get_addressbook(&self, principal: &str, id: &str) -> Result; + async fn get_addressbook( + &self, + principal: &str, + id: &str, + show_deleted: bool, + ) -> Result; async fn get_addressbooks(&self, principal: &str) -> Result, Error>; async fn get_deleted_addressbooks(&self, principal: &str) -> Result, Error>; @@ -42,6 +47,7 @@ pub trait AddressbookStore: Send + Sync + 'static { principal: &str, addressbook_id: &str, object_id: &str, + show_deleted: bool, ) -> Result; async fn put_object( &self, diff --git a/crates/store/src/contact_birthday_store.rs b/crates/store/src/contact_birthday_store.rs index 117d015..347f676 100644 --- a/crates/store/src/contact_birthday_store.rs +++ b/crates/store/src/contact_birthday_store.rs @@ -1,8 +1,8 @@ use std::{collections::HashMap, sync::Arc}; use crate::{ - calendar::CalendarObjectType, AddressObject, Addressbook, AddressbookStore, Calendar, - CalendarObject, CalendarStore, Error, + AddressObject, Addressbook, AddressbookStore, Calendar, CalendarObject, CalendarStore, Error, + calendar::CalendarObjectType, }; use async_trait::async_trait; use derive_more::derive::Constructor; @@ -39,7 +39,7 @@ fn birthday_calendar(addressbook: Addressbook) -> Calendar { #[async_trait] impl CalendarStore for ContactBirthdayStore { async fn get_calendar(&self, principal: &str, id: &str) -> Result { - let addressbook = self.0.get_addressbook(principal, id).await?; + let addressbook = self.0.get_addressbook(principal, id, false).await?; Ok(birthday_calendar(addressbook)) } async fn get_calendars(&self, principal: &str) -> Result, Error> { @@ -122,7 +122,7 @@ impl CalendarStore for ContactBirthdayStore { ) -> Result { let (addressobject_id, date_type) = object_id.rsplit_once("-").ok_or(Error::NotFound)?; self.0 - .get_object(principal, cal_id, addressobject_id) + .get_object(principal, cal_id, addressobject_id, false) .await? .get_significant_dates()? .remove(date_type) diff --git a/crates/store_sqlite/src/addressbook_store.rs b/crates/store_sqlite/src/addressbook_store.rs index a7f4b87..36729b1 100644 --- a/crates/store_sqlite/src/addressbook_store.rs +++ b/crates/store_sqlite/src/addressbook_store.rs @@ -2,8 +2,8 @@ use super::ChangeOperation; use async_trait::async_trait; use derive_more::derive::Constructor; use rustical_store::{ - synctoken::format_synctoken, AddressObject, Addressbook, AddressbookStore, CollectionOperation, - CollectionOperationDomain, CollectionOperationType, Error, + AddressObject, Addressbook, AddressbookStore, CollectionOperation, CollectionOperationDomain, + CollectionOperationType, Error, synctoken::format_synctoken, }; use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction}; use tokio::sync::mpsc::Sender; @@ -34,14 +34,17 @@ impl SqliteAddressbookStore { executor: E, principal: &str, id: &str, + show_deleted: bool, ) -> Result { let addressbook = sqlx::query_as!( Addressbook, r#"SELECT principal, id, synctoken, displayname, description, deleted_at, push_topic FROM addressbooks - WHERE (principal, id) = (?, ?)"#, + WHERE (principal, id) = (?, ?) + AND ((deleted_at IS NULL) OR ?) "#, principal, - id + id, + show_deleted ) .fetch_one(executor) .await @@ -208,7 +211,8 @@ impl SqliteAddressbookStore { .unwrap_or(0); for Row { object_id, .. } in changes { - match Self::_get_object(&mut *conn, principal, addressbook_id, &object_id).await { + match Self::_get_object(&mut *conn, principal, addressbook_id, &object_id, false).await + { Ok(object) => objects.push(object), Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id), Err(err) => return Err(err), @@ -241,13 +245,15 @@ impl SqliteAddressbookStore { principal: &str, addressbook_id: &str, object_id: &str, + show_deleted: bool, ) -> Result { Ok(sqlx::query_as!( AddressObjectRow, - "SELECT id, vcf FROM addressobjects WHERE (principal, addressbook_id, id) = (?, ?, ?)", + "SELECT id, vcf FROM addressobjects WHERE (principal, addressbook_id, id) = (?, ?, ?) AND ((deleted_at IS NULL) or ?)", principal, addressbook_id, - object_id + object_id, + show_deleted ) .fetch_one(executor) .await @@ -346,8 +352,9 @@ impl AddressbookStore for SqliteAddressbookStore { &self, principal: &str, id: &str, + show_deleted: bool, ) -> Result { - Self::_get_addressbook(&self.db, principal, id).await + Self::_get_addressbook(&self.db, principal, id, show_deleted).await } #[instrument] @@ -393,11 +400,12 @@ impl AddressbookStore for SqliteAddressbookStore { ) -> Result<(), rustical_store::Error> { let mut tx = self.db.begin().await.map_err(crate::Error::from)?; - let addressbook = match Self::_get_addressbook(&mut *tx, principal, addressbook_id).await { - Ok(addressbook) => Some(addressbook), - Err(Error::NotFound) => None, - Err(err) => return Err(err), - }; + let addressbook = + match Self::_get_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await { + Ok(addressbook) => Some(addressbook), + Err(Error::NotFound) => None, + Err(err) => return Err(err), + }; Self::_delete_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await?; tx.commit().await.map_err(crate::Error::from)?; @@ -450,8 +458,9 @@ impl AddressbookStore for SqliteAddressbookStore { principal: &str, addressbook_id: &str, object_id: &str, + show_deleted: bool, ) -> Result { - Self::_get_object(&self.db, principal, addressbook_id, object_id).await + Self::_get_object(&self.db, principal, addressbook_id, object_id, show_deleted).await } #[instrument] @@ -491,7 +500,7 @@ impl AddressbookStore for SqliteAddressbookStore { r#type: CollectionOperationType::Object, domain: CollectionOperationDomain::Addressbook, topic: self - .get_addressbook(&principal, &addressbook_id) + .get_addressbook(&principal, &addressbook_id, false) .await? .push_topic, sync_token: Some(synctoken), @@ -531,7 +540,7 @@ impl AddressbookStore for SqliteAddressbookStore { r#type: CollectionOperationType::Object, domain: CollectionOperationDomain::Addressbook, topic: self - .get_addressbook(principal, addressbook_id) + .get_addressbook(principal, addressbook_id, false) .await? .push_topic, sync_token: Some(synctoken), @@ -566,7 +575,7 @@ impl AddressbookStore for SqliteAddressbookStore { r#type: CollectionOperationType::Object, domain: CollectionOperationDomain::Addressbook, topic: self - .get_addressbook(principal, addressbook_id) + .get_addressbook(principal, addressbook_id, false) .await? .push_topic, sync_token: Some(synctoken),