addressbook_store, add option to not return deleted objects with get_object

#61
This commit is contained in:
Lennart
2025-04-27 18:32:17 +02:00
parent df5f19faab
commit 99388cf992
12 changed files with 71 additions and 46 deletions

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "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": { "describe": {
"columns": [ "columns": [
{ {
@@ -40,7 +40,7 @@
} }
], ],
"parameters": { "parameters": {
"Right": 2 "Right": 3
}, },
"nullable": [ "nullable": [
false, false,
@@ -52,5 +52,5 @@
false false
] ]
}, },
"hash": "c736a652873a4795bb7a9bd96db5b41ec7e5bfba5e00aea8f7a0fa406a8d518b" "hash": "130986d03d4d78ceeb15aff6f4d6304f0be0100e4bffad9cc3f6c1a2c6c4b297"
} }

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "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": { "describe": {
"columns": [ "columns": [
{ {
@@ -15,12 +15,12 @@
} }
], ],
"parameters": { "parameters": {
"Right": 3 "Right": 4
}, },
"nullable": [ "nullable": [
false, false,
false false
] ]
}, },
"hash": "f757da416f4db119ce88e964c44adf0227cc6e7130a1572e36cf2f4795d46284" "hash": "395e40a7b3333b79bc2ad50a123d99f74bc2712a16257ee2119dd211fdb61f7e"
} }

View File

@@ -1,11 +1,11 @@
use super::resource::AddressObjectPathComponents; use super::resource::AddressObjectPathComponents;
use crate::addressbook::resource::AddressbookResource;
use crate::Error; 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;
use actix_web::http::header::HeaderValue; use actix_web::http::header::HeaderValue;
use actix_web::web::{Data, Path}; use actix_web::web::{Data, Path};
use actix_web::HttpRequest;
use actix_web::HttpResponse;
use rustical_dav::privileges::UserPrivilege; use rustical_dav::privileges::UserPrivilege;
use rustical_dav::resource::Resource; use rustical_dav::resource::Resource;
use rustical_store::auth::User; use rustical_store::auth::User;
@@ -30,7 +30,9 @@ pub async fn get_object<AS: AddressbookStore>(
return Err(Error::Unauthorized); 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); let addressbook_resource = AddressbookResource(addressbook);
if !addressbook_resource if !addressbook_resource
.get_user_privileges(&user)? .get_user_privileges(&user)?
@@ -40,7 +42,7 @@ pub async fn get_object<AS: AddressbookStore>(
} }
let object = store let object = store
.get_object(&principal, &addressbook_id, &object_id) .get_object(&principal, &addressbook_id, &object_id, false)
.await?; .await?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()

View File

@@ -144,7 +144,7 @@ impl<AS: AddressbookStore> ResourceService for AddressObjectResourceService<AS>
) -> Result<Self::Resource, Self::Error> { ) -> Result<Self::Resource, Self::Error> {
let object = self let object = self
.addr_store .addr_store
.get_object(principal, addressbook_id, object_id) .get_object(principal, addressbook_id, object_id, false)
.await?; .await?;
Ok(AddressObjectResource { Ok(AddressObjectResource {
object, object,

View File

@@ -1,7 +1,7 @@
use crate::Error; use crate::Error;
use actix_web::web::Path; use actix_web::web::Path;
use actix_web::{web::Data, HttpResponse}; use actix_web::{HttpResponse, web::Data};
use rustical_store::{auth::User, Addressbook, AddressbookStore}; use rustical_store::{Addressbook, AddressbookStore, auth::User};
use rustical_xml::{XmlDeserialize, XmlDocument, XmlRootTag}; use rustical_xml::{XmlDeserialize, XmlDocument, XmlRootTag};
use tracing::instrument; use tracing::instrument;
use tracing_actix_web::RootSpan; use tracing_actix_web::RootSpan;
@@ -65,7 +65,10 @@ pub async fn route_mkcol<AS: AddressbookStore>(
push_topic: uuid::Uuid::new_v4().to_string(), 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) => { Err(rustical_store::Error::NotFound) => {
// No conflict, no worries // No conflict, no worries
} }

View File

@@ -24,7 +24,9 @@ pub async fn route_post<A: AddressbookStore, S: SubscriptionStore>(
return Err(Error::Unauthorized); 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 request = PushRegister::parse_str(&body)?;
let sub_id = uuid::Uuid::new_v4().to_string(); let sub_id = uuid::Uuid::new_v4().to_string();

View File

@@ -1,17 +1,17 @@
use crate::{ use crate::{
address_object::resource::{AddressObjectPropWrapper, AddressObjectResource},
Error, Error,
address_object::resource::{AddressObjectPropWrapper, AddressObjectResource},
}; };
use actix_web::{ use actix_web::{
HttpRequest,
dev::{Path, ResourceDef}, dev::{Path, ResourceDef},
http::StatusCode, http::StatusCode,
HttpRequest,
}; };
use rustical_dav::{ use rustical_dav::{
resource::Resource, 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; use rustical_xml::XmlDeserialize;
#[derive(XmlDeserialize, Clone, Debug, PartialEq)] #[derive(XmlDeserialize, Clone, Debug, PartialEq)]
@@ -42,7 +42,10 @@ pub async fn get_objects_addressbook_multiget<AS: AddressbookStore>(
not_found.push(href.to_owned()); not_found.push(href.to_owned());
}; };
let object_id = path.get("object_id").unwrap(); 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), Ok(object) => result.push(object),
Err(rustical_store::Error::NotFound) => not_found.push(href.to_owned()), Err(rustical_store::Error::NotFound) => not_found.push(href.to_owned()),
// TODO: Maybe add error handling on a per-object basis // TODO: Maybe add error handling on a per-object basis

View File

@@ -211,7 +211,7 @@ impl<AS: AddressbookStore, S: SubscriptionStore> ResourceService
) -> Result<Self::Resource, Error> { ) -> Result<Self::Resource, Error> {
let addressbook = self let addressbook = self
.addr_store .addr_store
.get_addressbook(principal, addressbook_id) .get_addressbook(principal, addressbook_id, false)
.await .await
.map_err(|_e| Error::NotFound)?; .map_err(|_e| Error::NotFound)?;
Ok(addressbook.into()) Ok(addressbook.into())

View File

@@ -24,7 +24,7 @@ pub async fn route_addressbook<AS: AddressbookStore>(
return Ok(HttpResponse::Unauthorized().body("Unauthorized")); return Ok(HttpResponse::Unauthorized().body("Unauthorized"));
} }
Ok(AddressbookPage { Ok(AddressbookPage {
addressbook: store.get_addressbook(&owner, &addrbook_id).await?, addressbook: store.get_addressbook(&owner, &addrbook_id, true).await?,
} }
.respond_to(&req)) .respond_to(&req))
} }

View File

@@ -1,12 +1,17 @@
use crate::{ use crate::{
addressbook::{AddressObject, Addressbook},
Error, Error,
addressbook::{AddressObject, Addressbook},
}; };
use async_trait::async_trait; use async_trait::async_trait;
#[async_trait] #[async_trait]
pub trait AddressbookStore: Send + Sync + 'static { pub trait AddressbookStore: Send + Sync + 'static {
async fn get_addressbook(&self, principal: &str, id: &str) -> Result<Addressbook, Error>; async fn get_addressbook(
&self,
principal: &str,
id: &str,
show_deleted: bool,
) -> Result<Addressbook, Error>;
async fn get_addressbooks(&self, principal: &str) -> Result<Vec<Addressbook>, Error>; async fn get_addressbooks(&self, principal: &str) -> Result<Vec<Addressbook>, Error>;
async fn get_deleted_addressbooks(&self, principal: &str) -> Result<Vec<Addressbook>, Error>; async fn get_deleted_addressbooks(&self, principal: &str) -> Result<Vec<Addressbook>, Error>;
@@ -42,6 +47,7 @@ pub trait AddressbookStore: Send + Sync + 'static {
principal: &str, principal: &str,
addressbook_id: &str, addressbook_id: &str,
object_id: &str, object_id: &str,
show_deleted: bool,
) -> Result<AddressObject, Error>; ) -> Result<AddressObject, Error>;
async fn put_object( async fn put_object(
&self, &self,

View File

@@ -1,8 +1,8 @@
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use crate::{ use crate::{
calendar::CalendarObjectType, AddressObject, Addressbook, AddressbookStore, Calendar, AddressObject, Addressbook, AddressbookStore, Calendar, CalendarObject, CalendarStore, Error,
CalendarObject, CalendarStore, Error, calendar::CalendarObjectType,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use derive_more::derive::Constructor; use derive_more::derive::Constructor;
@@ -39,7 +39,7 @@ fn birthday_calendar(addressbook: Addressbook) -> Calendar {
#[async_trait] #[async_trait]
impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> { impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
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 addressbook = self.0.get_addressbook(principal, id).await?; let addressbook = self.0.get_addressbook(principal, id, false).await?;
Ok(birthday_calendar(addressbook)) Ok(birthday_calendar(addressbook))
} }
async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> { async fn get_calendars(&self, principal: &str) -> Result<Vec<Calendar>, Error> {
@@ -122,7 +122,7 @@ impl<AS: AddressbookStore> CalendarStore for ContactBirthdayStore<AS> {
) -> Result<CalendarObject, Error> { ) -> Result<CalendarObject, Error> {
let (addressobject_id, date_type) = object_id.rsplit_once("-").ok_or(Error::NotFound)?; let (addressobject_id, date_type) = object_id.rsplit_once("-").ok_or(Error::NotFound)?;
self.0 self.0
.get_object(principal, cal_id, addressobject_id) .get_object(principal, cal_id, addressobject_id, false)
.await? .await?
.get_significant_dates()? .get_significant_dates()?
.remove(date_type) .remove(date_type)

View File

@@ -2,8 +2,8 @@ use super::ChangeOperation;
use async_trait::async_trait; use async_trait::async_trait;
use derive_more::derive::Constructor; use derive_more::derive::Constructor;
use rustical_store::{ use rustical_store::{
synctoken::format_synctoken, AddressObject, Addressbook, AddressbookStore, CollectionOperation, AddressObject, Addressbook, AddressbookStore, CollectionOperation, CollectionOperationDomain,
CollectionOperationDomain, CollectionOperationType, Error, CollectionOperationType, Error, synctoken::format_synctoken,
}; };
use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction}; use sqlx::{Acquire, Executor, Sqlite, SqlitePool, Transaction};
use tokio::sync::mpsc::Sender; use tokio::sync::mpsc::Sender;
@@ -34,14 +34,17 @@ impl SqliteAddressbookStore {
executor: E, executor: E,
principal: &str, principal: &str,
id: &str, id: &str,
show_deleted: bool,
) -> Result<Addressbook, rustical_store::Error> { ) -> Result<Addressbook, rustical_store::Error> {
let addressbook = sqlx::query_as!( let addressbook = sqlx::query_as!(
Addressbook, Addressbook,
r#"SELECT principal, id, synctoken, displayname, description, deleted_at, push_topic r#"SELECT principal, id, synctoken, displayname, description, deleted_at, push_topic
FROM addressbooks FROM addressbooks
WHERE (principal, id) = (?, ?)"#, WHERE (principal, id) = (?, ?)
AND ((deleted_at IS NULL) OR ?) "#,
principal, principal,
id id,
show_deleted
) )
.fetch_one(executor) .fetch_one(executor)
.await .await
@@ -208,7 +211,8 @@ impl SqliteAddressbookStore {
.unwrap_or(0); .unwrap_or(0);
for Row { object_id, .. } in changes { 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), Ok(object) => objects.push(object),
Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id), Err(rustical_store::Error::NotFound) => deleted_objects.push(object_id),
Err(err) => return Err(err), Err(err) => return Err(err),
@@ -241,13 +245,15 @@ impl SqliteAddressbookStore {
principal: &str, principal: &str,
addressbook_id: &str, addressbook_id: &str,
object_id: &str, object_id: &str,
show_deleted: bool,
) -> Result<AddressObject, rustical_store::Error> { ) -> Result<AddressObject, rustical_store::Error> {
Ok(sqlx::query_as!( Ok(sqlx::query_as!(
AddressObjectRow, 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, principal,
addressbook_id, addressbook_id,
object_id object_id,
show_deleted
) )
.fetch_one(executor) .fetch_one(executor)
.await .await
@@ -346,8 +352,9 @@ impl AddressbookStore for SqliteAddressbookStore {
&self, &self,
principal: &str, principal: &str,
id: &str, id: &str,
show_deleted: bool,
) -> Result<Addressbook, rustical_store::Error> { ) -> Result<Addressbook, rustical_store::Error> {
Self::_get_addressbook(&self.db, principal, id).await Self::_get_addressbook(&self.db, principal, id, show_deleted).await
} }
#[instrument] #[instrument]
@@ -393,11 +400,12 @@ impl AddressbookStore for SqliteAddressbookStore {
) -> Result<(), rustical_store::Error> { ) -> Result<(), rustical_store::Error> {
let mut tx = self.db.begin().await.map_err(crate::Error::from)?; let mut tx = self.db.begin().await.map_err(crate::Error::from)?;
let addressbook = match Self::_get_addressbook(&mut *tx, principal, addressbook_id).await { let addressbook =
Ok(addressbook) => Some(addressbook), match Self::_get_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await {
Err(Error::NotFound) => None, Ok(addressbook) => Some(addressbook),
Err(err) => return Err(err), Err(Error::NotFound) => None,
}; Err(err) => return Err(err),
};
Self::_delete_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await?; Self::_delete_addressbook(&mut *tx, principal, addressbook_id, use_trashbin).await?;
tx.commit().await.map_err(crate::Error::from)?; tx.commit().await.map_err(crate::Error::from)?;
@@ -450,8 +458,9 @@ impl AddressbookStore for SqliteAddressbookStore {
principal: &str, principal: &str,
addressbook_id: &str, addressbook_id: &str,
object_id: &str, object_id: &str,
show_deleted: bool,
) -> Result<AddressObject, rustical_store::Error> { ) -> Result<AddressObject, rustical_store::Error> {
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] #[instrument]
@@ -491,7 +500,7 @@ impl AddressbookStore for SqliteAddressbookStore {
r#type: CollectionOperationType::Object, r#type: CollectionOperationType::Object,
domain: CollectionOperationDomain::Addressbook, domain: CollectionOperationDomain::Addressbook,
topic: self topic: self
.get_addressbook(&principal, &addressbook_id) .get_addressbook(&principal, &addressbook_id, false)
.await? .await?
.push_topic, .push_topic,
sync_token: Some(synctoken), sync_token: Some(synctoken),
@@ -531,7 +540,7 @@ impl AddressbookStore for SqliteAddressbookStore {
r#type: CollectionOperationType::Object, r#type: CollectionOperationType::Object,
domain: CollectionOperationDomain::Addressbook, domain: CollectionOperationDomain::Addressbook,
topic: self topic: self
.get_addressbook(principal, addressbook_id) .get_addressbook(principal, addressbook_id, false)
.await? .await?
.push_topic, .push_topic,
sync_token: Some(synctoken), sync_token: Some(synctoken),
@@ -566,7 +575,7 @@ impl AddressbookStore for SqliteAddressbookStore {
r#type: CollectionOperationType::Object, r#type: CollectionOperationType::Object,
domain: CollectionOperationDomain::Addressbook, domain: CollectionOperationDomain::Addressbook,
topic: self topic: self
.get_addressbook(principal, addressbook_id) .get_addressbook(principal, addressbook_id, false)
.await? .await?
.push_topic, .push_topic,
sync_token: Some(synctoken), sync_token: Some(synctoken),