diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..85b5244 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2024 Simon Bruder +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +static/vendor/* filter=lfs diff=lfs merge=lfs -text diff --git a/Cargo.lock b/Cargo.lock index 66661b7..03de86b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,60 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_actix" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4b0dd17cfe203b00ba3853a89fba459ecf24c759b738b244133330607c78e55" +dependencies = [ + "actix-web", + "askama", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -335,6 +389,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "basic-toml" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.5.0" @@ -775,6 +838,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "humantime" version = "2.1.0" @@ -840,6 +912,8 @@ version = "0.0.0" dependencies = [ "actix-files", "actix-web", + "askama", + "askama_actix", "diesel", "diesel_migrations", "env_logger", @@ -854,6 +928,12 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "local-channel" version = "0.1.5" @@ -930,6 +1010,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.4" @@ -951,12 +1037,31 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.0" diff --git a/Cargo.toml b/Cargo.toml index 67c694e..7f6ac1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ license = "AGPL-3.0-or-later" [dependencies] actix-files = "0.6.6" actix-web = "4.8.0" +askama = { version = "0.12.1", features = ["with-actix-web"] } +askama_actix = "0.14.0" diesel = { version = "2.2.1", features = ["postgres", "r2d2", "uuid"] } diesel_migrations = { version = "2.2.0", features = ["postgres"] } env_logger = "0.11.3" diff --git a/src/frontend/item.rs b/src/frontend/item.rs new file mode 100644 index 0000000..b902e81 --- /dev/null +++ b/src/frontend/item.rs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +use actix_web::{error, get, post, web, HttpRequest, Responder}; +use askama_actix::Template; +use uuid::Uuid; + +use crate::manage; +use crate::models::*; +use crate::DbPool; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg.service(show_item) + .service(list_items) + .service(add_item) + .service(add_item_post); +} + +#[derive(Template)] +#[template(path = "item_details.html")] +struct ItemDetails { + req: HttpRequest, + item: Item, +} + +#[get("/item/{id}")] +async fn show_item( + req: HttpRequest, + pool: web::Data, + path: web::Path, +) -> actix_web::Result { + let id = path.into_inner(); + + let item = web::block(move || manage::item::get(&mut pool.get().unwrap(), id)) + .await? + .map_err(error::ErrorInternalServerError)?; + + Ok(ItemDetails { req, item }) +} + +#[derive(Template)] +#[template(path = "item_list.html")] +struct ItemList { + req: HttpRequest, + items: Vec, +} + +#[get("/items")] +async fn list_items( + req: HttpRequest, + pool: web::Data, +) -> actix_web::Result { + let items = web::block(move || manage::item::get_all(&mut pool.get().unwrap())) + .await? + .map_err(error::ErrorInternalServerError)?; + + Ok(ItemList { req, items }) +} + +#[derive(Template)] +#[template(path = "item_add.html")] +struct ItemAddForm { + req: HttpRequest, + data: Option, +} + +#[get("/items/add")] +async fn add_item(req: HttpRequest) -> actix_web::Result { + Ok(ItemAddForm { req, data: None }) +} + +#[post("/items/add")] +async fn add_item_post( + data: web::Form, + pool: web::Data, +) -> actix_web::Result { + let item = web::block(move || manage::item::add(&mut pool.get().unwrap(), data.into_inner())) + .await? + .map_err(error::ErrorInternalServerError)?; + Ok(web::Redirect::to("/item/".to_owned() + &item.id.to_string()).see_other()) +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs new file mode 100644 index 0000000..1698b41 --- /dev/null +++ b/src/frontend/mod.rs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 Simon Bruder +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +mod item; + +use actix_web::{get, web, HttpRequest, Responder}; +use askama_actix::Template; + +pub fn config(cfg: &mut web::ServiceConfig) { + cfg.service(index).configure(item::config); +} + +#[derive(Template)] +#[template(path = "base.html")] +struct Home { + req: HttpRequest, +} + +#[get("/")] +async fn index(req: HttpRequest) -> impl Responder { + Home { req } +} diff --git a/src/lib.rs b/src/lib.rs index 7a2cc21..7c33e03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later pub mod api; +pub mod frontend; pub mod manage; pub mod models; pub mod schema; diff --git a/src/main.rs b/src/main.rs index b555573..efb4536 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,7 @@ async fn main() -> std::io::Result<()> { .app_data(web::Data::new(pool.clone())) .service(web::scope("/api/v1").configure(li7y::api::v1::config)) .service(actix_files::Files::new("/static", &static_root)) + .configure(li7y::frontend::config) }) .bind((address, port))? .run() diff --git a/static/vendor/bootstrap.bundle.min.js b/static/vendor/bootstrap.bundle.min.js new file mode 100644 index 0000000..95f9c69 --- /dev/null +++ b/static/vendor/bootstrap.bundle.min.js @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:371268d77e3e6e8a2094619da603b44afdf5de1ab7050b18d2e55d134a8a4683 +size 80706 diff --git a/static/vendor/bootstrap.min.css b/static/vendor/bootstrap.min.css new file mode 100644 index 0000000..9ba420b --- /dev/null +++ b/static/vendor/bootstrap.min.css @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31a71a86392730579a14f0626280820d0ced224c244aac1a5b204d75b989fe81 +size 232790 diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..24217ad --- /dev/null +++ b/templates/base.html @@ -0,0 +1,59 @@ +{# +SPDX-FileCopyrightText: 2024 Simon Bruder + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% let branding = "li7y" -%} + + + + + {% block title %}{{ branding }}{% endblock %} + + + + + + + + +
+
+
+

{% block page_title %}{% endblock %}

+
+
+ {% block page_actions %}{% endblock %} +
+
+ {% block main %}{% endblock %} +
+ +
+
+

li7y is free software, released under the terms of the AGPL v3

+
+
+ + + + diff --git a/templates/item_add.html b/templates/item_add.html new file mode 100644 index 0000000..cbcea21 --- /dev/null +++ b/templates/item_add.html @@ -0,0 +1,17 @@ +{# +SPDX-FileCopyrightText: 2024 Simon Bruder + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% extends "base.html" %} +{% block title %}{% block page_title %}Add Item{% endblock %} – {{ branding }}{% endblock %} +{% block main %} +
+
+ + +
+ +
+{% endblock %} diff --git a/templates/item_details.html b/templates/item_details.html new file mode 100644 index 0000000..6b902b8 --- /dev/null +++ b/templates/item_details.html @@ -0,0 +1,30 @@ +{# +SPDX-FileCopyrightText: 2024 Simon Bruder + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% extends "base.html" %} +{% block title %}{% block page_title %}{{ item.name }}{% endblock %} – Item Details – {{ branding }}{% endblock %} +{% block main %} + + + + + + + + + + + +
+ UUID + + {{ item.id }} +
+ Name + + {{ item.name }} +
+{% endblock %} diff --git a/templates/item_list.html b/templates/item_list.html new file mode 100644 index 0000000..b6e32e5 --- /dev/null +++ b/templates/item_list.html @@ -0,0 +1,29 @@ +{# +SPDX-FileCopyrightText: 2024 Simon Bruder + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% extends "base.html" %} +{% block title %}{% block page_title %}Item List{% endblock %} – {{ branding }}{% endblock %} +{% block page_actions %} +Add +{% endblock %} +{% block main %} + + + + + + + + + {% for item in items -%} + + + + + {% endfor -%} + +
UUIDName
{{ item.id }}{{ item.name }}
+{% endblock %}