diff --git a/Cargo.lock b/Cargo.lock index 27756c8..94dc866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.5", ] diff --git a/README.md b/README.md index fd2360d..15838c5 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ a calendar server - [ ] CardDAV - [ ] Locking - [ ] Web UI -- [ ] Hiding calendars instead of deleting them - [ ] Testing such that I'm confident enough to use it myself :) - [ ] WebDAV sync extension [RFC 6578](https://www.rfc-editor.org/rfc/rfc6578) - [ ] implement getctag [see](https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-ctag.txt) - [ ] Ensure proper routing -- [ ] Trash bin +- [x] Trash bin + - [x] Hiding calendars instead of deleting them + - [ ] Restore endpoint diff --git a/crates/caldav/src/calendar/methods/delete.rs b/crates/caldav/src/calendar/methods/delete.rs index 6620b39..e06dd26 100644 --- a/crates/caldav/src/calendar/methods/delete.rs +++ b/crates/caldav/src/calendar/methods/delete.rs @@ -1,5 +1,6 @@ use crate::CalDavContext; use crate::Error; +use actix_web::HttpRequest; use actix_web::{ web::{Data, Path}, HttpResponse, @@ -11,12 +12,25 @@ pub async fn route_delete_calendar>, path: Path<(String, String)>, auth: AuthInfoExtractor, + req: HttpRequest, ) -> Result { let (principal, cid) = path.into_inner(); if principal != auth.inner.user_id { return Err(Error::Unauthorized); } - context.store.write().await.delete_calendar(&cid).await?; + + let no_trash = req + .headers() + .get("X-No-Trashbin") + .map(|val| matches!(val.to_str(), Ok("1"))) + .unwrap_or(false); + + context + .store + .write() + .await + .delete_calendar(&cid, !no_trash) + .await?; Ok(HttpResponse::Ok().body("")) } diff --git a/crates/caldav/src/calendar/methods/mkcalendar.rs b/crates/caldav/src/calendar/methods/mkcalendar.rs index 807183f..d542439 100644 --- a/crates/caldav/src/calendar/methods/mkcalendar.rs +++ b/crates/caldav/src/calendar/methods/mkcalendar.rs @@ -75,7 +75,7 @@ pub async fn route_mkcol_calendar( context: Data>, path: Path<(String, String, String)>, auth: AuthInfoExtractor, + req: HttpRequest, ) -> Result { let _user = auth.inner.user_id; // TODO: verify whether user is authorized @@ -16,7 +18,18 @@ pub async fn delete_event( if cid.ends_with(".ics") { cid.truncate(cid.len() - 4); } - context.store.write().await.delete_event(&cid, &uid).await?; + let no_trash = req + .headers() + .get("X-No-Trashbin") + .map(|val| matches!(val.to_str(), Ok("1"))) + .unwrap_or(false); + + context + .store + .write() + .await + .delete_event(&cid, &uid, !no_trash) + .await?; Ok(HttpResponse::Ok().body("")) } diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index bdb8232..2e03ba8 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -24,7 +24,7 @@ sqlx = { version = "0.7", features = [ tokio = { version = "1.35", features = ["sync", "full"] } toml = "0.8" ical = { version = "0.11", features = ["generator", "serde"] } -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } regex = "1.10" lazy_static = "1.4" rstest = "0.21" diff --git a/crates/store/migrations/20240601182759_init.sql b/crates/store/migrations/20240621161002_init.sql similarity index 82% rename from crates/store/migrations/20240601182759_init.sql rename to crates/store/migrations/20240621161002_init.sql index 92089c1..1c2078a 100644 --- a/crates/store/migrations/20240601182759_init.sql +++ b/crates/store/migrations/20240621161002_init.sql @@ -5,13 +5,15 @@ CREATE TABLE calendars ( description TEXT, 'order' INT DEFAULT 0 NOT NULL, color TEXT, - timezone TEXT NOT NULL + timezone TEXT NOT NULL, + deleted_at DATETIME ); CREATE TABLE events ( uid TEXT NOT NULL, cid TEXT NOT NULL, ics TEXT NOT NULL, + deleted_at DATETIME, PRIMARY KEY (cid, uid), FOREIGN KEY (cid) REFERENCES calendars(id) ); diff --git a/crates/store/src/calendar.rs b/crates/store/src/calendar.rs index 4bb93d5..bc55f46 100644 --- a/crates/store/src/calendar.rs +++ b/crates/store/src/calendar.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; #[derive(Debug, Default, Clone, Deserialize, Serialize)] @@ -9,4 +10,5 @@ pub struct Calendar { pub description: Option, pub color: Option, pub timezone: Option, + pub deleted_at: Option, } diff --git a/crates/store/src/store.rs b/crates/store/src/store.rs index 52b55e7..10a42d6 100644 --- a/crates/store/src/store.rs +++ b/crates/store/src/store.rs @@ -10,10 +10,13 @@ pub trait CalendarStore: Send + Sync + 'static { async fn get_calendars(&self, owner: &str) -> Result, Error>; async fn update_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error>; async fn insert_calendar(&mut self, cid: String, calendar: Calendar) -> Result<(), Error>; - async fn delete_calendar(&mut self, cid: &str) -> Result<(), Error>; + async fn delete_calendar(&mut self, cid: &str, use_trashbin: bool) -> Result<(), Error>; + async fn restore_calendar(&mut self, cid: &str) -> Result<(), Error>; async fn get_events(&self, cid: &str) -> Result, Error>; async fn get_event(&self, cid: &str, uid: &str) -> Result; async fn put_event(&mut self, cid: String, uid: String, ics: String) -> Result<(), Error>; - async fn delete_event(&mut self, cid: &str, uid: &str) -> Result<(), Error>; + async fn delete_event(&mut self, cid: &str, uid: &str, use_trashbin: bool) + -> Result<(), Error>; + async fn restore_event(&mut self, cid: &str, uid: &str) -> Result<(), Error>; }