Implement PUT method for addressbook import

This commit is contained in:
Lennart
2025-06-10 23:43:53 +02:00
parent 80cca7b7b2
commit a20e9800bd
8 changed files with 108 additions and 0 deletions

1
Cargo.lock generated
View File

@@ -2724,6 +2724,7 @@ dependencies = [
"derive_more",
"futures-util",
"http",
"ical",
"percent-encoding",
"quick-xml",
"rustical_dav",

View File

@@ -30,3 +30,4 @@ rustical_ical.workspace = true
http.workspace = true
tower-http.workspace = true
percent-encoding.workspace = true
ical.workspace = true

View File

@@ -1,4 +1,5 @@
pub mod mkcol;
// pub mod post;
pub mod get;
pub mod put;
pub mod report;

View File

@@ -0,0 +1,47 @@
use crate::Error;
use crate::addressbook::AddressbookResourceService;
use axum::response::IntoResponse;
use axum::{
extract::{Path, State},
response::Response,
};
use http::StatusCode;
use ical::VcardParser;
use rustical_ical::AddressObject;
use rustical_store::Addressbook;
use rustical_store::{AddressbookStore, SubscriptionStore, auth::User};
use tracing::instrument;
#[instrument(skip(addr_store))]
pub async fn route_put<AS: AddressbookStore, S: SubscriptionStore>(
Path((principal, addressbook_id)): Path<(String, String)>,
State(AddressbookResourceService { addr_store, .. }): State<AddressbookResourceService<AS, S>>,
user: User,
body: String,
) -> Result<Response, Error> {
if !user.is_principal(&principal) {
return Err(Error::Unauthorized);
}
let mut objects = vec![];
for object in VcardParser::new(body.as_bytes()) {
let object = object.map_err(rustical_ical::Error::from)?;
objects.push(AddressObject::try_from(object)?);
}
let addressbook = Addressbook {
id: addressbook_id.clone(),
principal: principal.clone(),
displayname: None,
description: None,
deleted_at: None,
synctoken: Default::default(),
push_topic: uuid::Uuid::new_v4().to_string(),
};
addr_store
.import_addressbook(principal.clone(), addressbook, objects)
.await?;
Ok(StatusCode::CREATED.into_response())
}

View File

@@ -3,6 +3,7 @@ use super::methods::report::route_report_addressbook;
use crate::address_object::AddressObjectResourceService;
use crate::address_object::resource::AddressObjectResource;
use crate::addressbook::methods::get::route_get;
use crate::addressbook::methods::put::route_put;
use crate::addressbook::resource::AddressbookResource;
use crate::{CardDavPrincipalUri, Error};
use async_trait::async_trait;
@@ -129,6 +130,13 @@ impl<AS: AddressbookStore, S: SubscriptionStore> AxumMethods for AddressbookReso
})
}
fn put() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
Some(|state, req| {
let mut service = Handler::with_state(route_put::<AS, S>, state);
Box::pin(Service::call(&mut service, req))
})
}
fn mkcol() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
Some(|state, req| {
let mut service = Handler::with_state(route_mkcol::<AS, S>, state);

View File

@@ -1,6 +1,7 @@
use crate::{CalDateTime, LOCAL_DATE};
use crate::{CalendarObject, Error};
use chrono::Datelike;
use ical::generator::Emitter;
use ical::parser::{
Component,
vcard::{self, component::VcardContact},
@@ -15,6 +16,21 @@ pub struct AddressObject {
vcard: VcardContact,
}
impl TryFrom<VcardContact> for AddressObject {
type Error = Error;
fn try_from(vcard: VcardContact) -> Result<Self, Self::Error> {
let id = vcard
.get_property("UID")
.ok_or(Error::InvalidData("Missing UID".to_owned()))?
.value
.clone()
.ok_or(Error::InvalidData("Missing UID".to_owned()))?;
let vcf = vcard.generate();
Ok(Self { id, vcf, vcard })
}
}
impl AddressObject {
pub fn from_vcf(object_id: String, vcf: String) -> Result<Self, Error> {
let mut parser = vcard::VcardParser::new(BufReader::new(vcf.as_bytes()));

View File

@@ -67,4 +67,11 @@ pub trait AddressbookStore: Send + Sync + 'static {
addressbook_id: &str,
object_id: &str,
) -> Result<(), Error>;
async fn import_addressbook(
&self,
principal: String,
addressbook: Addressbook,
objects: Vec<AddressObject>,
) -> Result<(), Error>;
}

View File

@@ -584,6 +584,33 @@ impl AddressbookStore for SqliteAddressbookStore {
Ok(())
}
#[instrument(skip(objects))]
async fn import_addressbook(
&self,
principal: String,
addressbook: Addressbook,
objects: Vec<AddressObject>,
) -> Result<(), Error> {
let mut tx = self.db.begin().await.map_err(crate::Error::from)?;
let addressbook_id = addressbook.id.clone();
Self::_insert_addressbook(&mut *tx, addressbook).await?;
for object in objects {
Self::_put_object(
&mut *tx,
principal.clone(),
addressbook_id.clone(),
object,
false,
)
.await?;
}
tx.commit().await.map_err(crate::Error::from)?;
Ok(())
}
}
// Logs an operation to an address object