mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 01:12:24 +00:00
Implement PUT method for addressbook import
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2724,6 +2724,7 @@ dependencies = [
|
|||||||
"derive_more",
|
"derive_more",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
"ical",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rustical_dav",
|
"rustical_dav",
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ rustical_ical.workspace = true
|
|||||||
http.workspace = true
|
http.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
percent-encoding.workspace = true
|
percent-encoding.workspace = true
|
||||||
|
ical.workspace = true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod mkcol;
|
pub mod mkcol;
|
||||||
// pub mod post;
|
// pub mod post;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
|
pub mod put;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
|
|||||||
47
crates/carddav/src/addressbook/methods/put.rs
Normal file
47
crates/carddav/src/addressbook/methods/put.rs
Normal 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())
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ use super::methods::report::route_report_addressbook;
|
|||||||
use crate::address_object::AddressObjectResourceService;
|
use crate::address_object::AddressObjectResourceService;
|
||||||
use crate::address_object::resource::AddressObjectResource;
|
use crate::address_object::resource::AddressObjectResource;
|
||||||
use crate::addressbook::methods::get::route_get;
|
use crate::addressbook::methods::get::route_get;
|
||||||
|
use crate::addressbook::methods::put::route_put;
|
||||||
use crate::addressbook::resource::AddressbookResource;
|
use crate::addressbook::resource::AddressbookResource;
|
||||||
use crate::{CardDavPrincipalUri, Error};
|
use crate::{CardDavPrincipalUri, Error};
|
||||||
use async_trait::async_trait;
|
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>>> {
|
fn mkcol() -> Option<fn(Self, Request) -> BoxFuture<'static, Result<Response, Infallible>>> {
|
||||||
Some(|state, req| {
|
Some(|state, req| {
|
||||||
let mut service = Handler::with_state(route_mkcol::<AS, S>, state);
|
let mut service = Handler::with_state(route_mkcol::<AS, S>, state);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{CalDateTime, LOCAL_DATE};
|
use crate::{CalDateTime, LOCAL_DATE};
|
||||||
use crate::{CalendarObject, Error};
|
use crate::{CalendarObject, Error};
|
||||||
use chrono::Datelike;
|
use chrono::Datelike;
|
||||||
|
use ical::generator::Emitter;
|
||||||
use ical::parser::{
|
use ical::parser::{
|
||||||
Component,
|
Component,
|
||||||
vcard::{self, component::VcardContact},
|
vcard::{self, component::VcardContact},
|
||||||
@@ -15,6 +16,21 @@ pub struct AddressObject {
|
|||||||
vcard: VcardContact,
|
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 {
|
impl AddressObject {
|
||||||
pub fn from_vcf(object_id: String, vcf: String) -> Result<Self, Error> {
|
pub fn from_vcf(object_id: String, vcf: String) -> Result<Self, Error> {
|
||||||
let mut parser = vcard::VcardParser::new(BufReader::new(vcf.as_bytes()));
|
let mut parser = vcard::VcardParser::new(BufReader::new(vcf.as_bytes()));
|
||||||
|
|||||||
@@ -67,4 +67,11 @@ pub trait AddressbookStore: Send + Sync + 'static {
|
|||||||
addressbook_id: &str,
|
addressbook_id: &str,
|
||||||
object_id: &str,
|
object_id: &str,
|
||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
async fn import_addressbook(
|
||||||
|
&self,
|
||||||
|
principal: String,
|
||||||
|
addressbook: Addressbook,
|
||||||
|
objects: Vec<AddressObject>,
|
||||||
|
) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -584,6 +584,33 @@ impl AddressbookStore for SqliteAddressbookStore {
|
|||||||
|
|
||||||
Ok(())
|
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
|
// Logs an operation to an address object
|
||||||
|
|||||||
Reference in New Issue
Block a user