diff --git a/README.md b/README.md index 60ff5d4..fbe8491 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,14 @@ docker run -p 4000:4000 -v YOUR_CONFIG_TOML:/etc/rustical/config.toml -v YOUR_DA ## Configuration You can generate a default `config.toml` with + ```sh rustical gen-config ``` + There, you can customize your username, password, and app tokens. Password hashes can be generated with + ```sh rustical pwhash ``` @@ -40,7 +43,6 @@ Since it's sensitive information, the secure but slow hash algorithm `argon2` is I recommend to generate random app tokens for each CalDAV/CardDAV client. These can use the faster `pbkdf2` algorithm. - ## Todo - [ ] CalDAV @@ -64,7 +66,7 @@ These can use the faster `pbkdf2` algorithm. - [ ] Web UI - [x] Trash bin - [x] Hiding calendars instead of deleting them - - [ ] Restore endpoint + - [x] Restore endpoint - [ ] Packaging - [x] Ensure cargo install works - [x] Docker image diff --git a/crates/frontend/public/templates/pages/user.html b/crates/frontend/public/templates/pages/user.html index b346ac4..016d3fd 100644 --- a/crates/frontend/public/templates/pages/user.html +++ b/crates/frontend/public/templates/pages/user.html @@ -16,8 +16,9 @@ li.collection-list-item { ". color-chip" "title color-chip" "description color-chip" + "restore color-chip" ". color-chip"; - grid-template-rows: 12px auto auto 12px; + grid-template-rows: 12px auto auto auto 12px; grid-template-columns: auto 50px; color: inherit; text-decoration: none; @@ -37,6 +38,10 @@ li.collection-list-item { border-radius: 0 12px 12px 0; } + .restore-form { + grid-area: restore; + } + &:hover { background: #DDD; } @@ -71,6 +76,9 @@ li.collection-list-item { {% if let Some(description) = calendar.description %}{{ description }}{% endif %} +
+ +
@@ -101,6 +109,9 @@ li.collection-list-item { {% if let Some(description) = addressbook.description %}{{ description }}{% endif %} +
+ +
{% endfor %} diff --git a/crates/frontend/src/lib.rs b/crates/frontend/src/lib.rs index 00b20b7..0589323 100644 --- a/crates/frontend/src/lib.rs +++ b/crates/frontend/src/lib.rs @@ -12,7 +12,11 @@ use actix_web::{ use askama::Template; use askama_actix::TemplateToResponse; use assets::{Assets, EmbedService}; -use routes::login::{route_get_login, route_post_login}; +use routes::{ + addressbook::{route_addressbook, route_addressbook_restore}, + calendar::{route_calendar, route_calendar_restore}, + login::{route_get_login, route_post_login}, +}; use rustical_store::{ auth::{AuthenticationMiddleware, AuthenticationProvider, User}, Addressbook, AddressbookStore, Calendar, CalendarStore, @@ -57,44 +61,6 @@ async fn route_user( .to_response() } -#[derive(Template)] -#[template(path = "pages/calendar.html")] -struct CalendarPage { - owner: String, - calendar: Calendar, -} - -async fn route_calendar( - path: Path<(String, String)>, - store: Data, - _user: User, -) -> Result { - let (owner, cal_id) = path.into_inner(); - Ok(CalendarPage { - owner: owner.to_owned(), - calendar: store.get_calendar(&owner, &cal_id).await?, - }) -} - -#[derive(Template)] -#[template(path = "pages/addressbook.html")] -struct AddressbookPage { - owner: String, - addressbook: Addressbook, -} - -async fn route_addressbook( - path: Path<(String, String)>, - store: Data, - _user: User, -) -> Result { - let (owner, addrbook_id) = path.into_inner(); - Ok(AddressbookPage { - owner: owner.to_owned(), - addressbook: store.get_addressbook(&owner, &addrbook_id).await?, - }) -} - async fn route_root(user: Option, req: HttpRequest) -> impl Responder { let redirect_url = match user { Some(user) => req @@ -173,9 +139,17 @@ pub fn configure_frontend< .route(web::method(Method::GET).to(route_calendar::)), ) .service( - web::resource("/user/{user}/addressbook/{calendar}") + web::resource("/user/{user}/calendar/{calendar}/restore") + .route(web::method(Method::POST).to(route_calendar_restore::)), + ) + .service( + web::resource("/user/{user}/addressbook/{addressbook}") .route(web::method(Method::GET).to(route_addressbook::)), ) + .service( + web::resource("/user/{user}/addressbook/{addressbook}/restore") + .route(web::method(Method::POST).to(route_addressbook_restore::)), + ) .service( web::resource("/login") .name("frontend_login") diff --git a/crates/frontend/src/routes/addressbook.rs b/crates/frontend/src/routes/addressbook.rs new file mode 100644 index 0000000..ff1c475 --- /dev/null +++ b/crates/frontend/src/routes/addressbook.rs @@ -0,0 +1,43 @@ +use actix_web::{ + http::{header, StatusCode}, + web::{self, Data, Path}, + HttpRequest, HttpResponse, Responder, +}; +use askama::Template; +use rustical_store::{auth::User, Addressbook, AddressbookStore}; + +#[derive(Template)] +#[template(path = "pages/addressbook.html")] +struct AddressbookPage { + owner: String, + addressbook: Addressbook, +} + +pub async fn route_addressbook( + path: Path<(String, String)>, + store: Data, + _user: User, +) -> Result { + let (owner, addrbook_id) = path.into_inner(); + Ok(AddressbookPage { + owner: owner.to_owned(), + addressbook: store.get_addressbook(&owner, &addrbook_id).await?, + }) +} + +pub async fn route_addressbook_restore( + path: Path<(String, String)>, + req: HttpRequest, + store: Data, + _user: User, +) -> Result { + let (owner, addressbook_id) = path.into_inner(); + store.restore_addressbook(&owner, &addressbook_id).await?; + Ok(match req.headers().get(header::REFERER) { + Some(referer) => web::Redirect::to(referer.to_str().unwrap().to_owned()) + .using_status_code(StatusCode::FOUND) + .respond_to(&req) + .map_into_boxed_body(), + None => HttpResponse::Ok().body("Restored"), + }) +} diff --git a/crates/frontend/src/routes/calendar.rs b/crates/frontend/src/routes/calendar.rs new file mode 100644 index 0000000..2738bde --- /dev/null +++ b/crates/frontend/src/routes/calendar.rs @@ -0,0 +1,43 @@ +use actix_web::{ + http::{header, StatusCode}, + web::{self, Data, Path}, + HttpRequest, HttpResponse, Responder, +}; +use askama::Template; +use rustical_store::{auth::User, Calendar, CalendarStore}; + +#[derive(Template)] +#[template(path = "pages/calendar.html")] +struct CalendarPage { + owner: String, + calendar: Calendar, +} + +pub async fn route_calendar( + path: Path<(String, String)>, + store: Data, + _user: User, +) -> Result { + let (owner, cal_id) = path.into_inner(); + Ok(CalendarPage { + owner: owner.to_owned(), + calendar: store.get_calendar(&owner, &cal_id).await?, + }) +} + +pub async fn route_calendar_restore( + path: Path<(String, String)>, + req: HttpRequest, + store: Data, + _user: User, +) -> Result { + let (owner, cal_id) = path.into_inner(); + store.restore_calendar(&owner, &cal_id).await?; + Ok(match req.headers().get(header::REFERER) { + Some(referer) => web::Redirect::to(referer.to_str().unwrap().to_owned()) + .using_status_code(StatusCode::FOUND) + .respond_to(&req) + .map_into_boxed_body(), + None => HttpResponse::Ok().body("Restored"), + }) +} diff --git a/crates/frontend/src/routes/mod.rs b/crates/frontend/src/routes/mod.rs index 320cbbb..2004a1c 100644 --- a/crates/frontend/src/routes/mod.rs +++ b/crates/frontend/src/routes/mod.rs @@ -1 +1,4 @@ +pub mod addressbook; +pub mod calendar; pub mod login; +