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;
+