From b6a7cf941784b5dbab0b6a50adcc6afb02c25532 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 4 May 2026 02:04:52 +0200 Subject: [PATCH] feat(template-askama): add Askama template adapter for diary entries --- Cargo.lock | 93 +++++++++++++++++++ Cargo.toml | 2 +- crates/adapters/template-askama/Cargo.toml | 12 +++ crates/adapters/template-askama/src/lib.rs | 39 ++++++++ .../template-askama/templates/diary.html | 76 +++++++++++++++ crates/presentation/src/lib.rs | 1 + crates/presentation/src/ports.rs | 5 + 7 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 crates/adapters/template-askama/Cargo.toml create mode 100644 crates/adapters/template-askama/src/lib.rs create mode 100644 crates/adapters/template-askama/templates/diary.html create mode 100644 crates/presentation/src/lib.rs create mode 100644 crates/presentation/src/ports.rs diff --git a/Cargo.lock b/Cargo.lock index 2987355..c2675f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,59 @@ dependencies = [ "uuid", ] +[[package]] +name = "askama" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" +dependencies = [ + "askama_parser", + "basic-toml", + "glob", + "memchr", + "proc-macro2", + "quote", + "rustc-hash", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "askama_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" +dependencies = [ + "askama_derive", +] + +[[package]] +name = "askama_parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" +dependencies = [ + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -154,6 +207,15 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.11.1" @@ -543,6 +605,12 @@ dependencies = [ "wasip3", ] +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1307,6 +1375,12 @@ dependencies = [ name = "rss" version = "0.1.0" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustls" version = "0.23.40" @@ -1790,6 +1864,16 @@ dependencies = [ "syn", ] +[[package]] +name = "template-askama" +version = "0.1.0" +dependencies = [ + "askama", + "domain", + "presentation", + "serde", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -2455,6 +2539,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 7670d1b..1ae8d2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "crates/adapters/auth", "crates/adapters/metadata", "crates/adapters/rss", - "crates/adapters/sqlite", + "crates/adapters/sqlite", "crates/adapters/template-askama", "crates/application", "crates/common", "crates/domain", diff --git a/crates/adapters/template-askama/Cargo.toml b/crates/adapters/template-askama/Cargo.toml new file mode 100644 index 0000000..2057914 --- /dev/null +++ b/crates/adapters/template-askama/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "template-askama" +version = "0.1.0" +edition = "2024" + +[dependencies] +askama = { version = "0.16.0" } + +serde = { workspace = true } + +domain = { workspace = true } +presentation = { workspace = true } diff --git a/crates/adapters/template-askama/src/lib.rs b/crates/adapters/template-askama/src/lib.rs new file mode 100644 index 0000000..1f0739e --- /dev/null +++ b/crates/adapters/template-askama/src/lib.rs @@ -0,0 +1,39 @@ +// crates/adapters/template-askama/src/lib.rs +use askama::Template; +use domain::models::{DiaryEntry, collections::Paginated}; +use presentation::ports::HtmlRenderer; // Assuming you exposed the port + +// The internal Askama template +#[derive(Template)] +#[template(path = "diary.html")] +struct DiaryTemplate<'a> { + entries: &'a [DiaryEntry], + current_offset: u32, + limit: u32, + has_more: bool, +} + +// The public adapter struct +pub struct AskamaHtmlRenderer; + +impl AskamaHtmlRenderer { + pub fn new() -> Self { + Self {} + } +} + +// Implementing the presentation port +impl HtmlRenderer for AskamaHtmlRenderer { + fn render_diary_page(&self, data: &Paginated) -> Result { + let has_more = (data.offset + data.limit) < data.total_count as u32; + + let template = DiaryTemplate { + entries: &data.items, + current_offset: data.offset, + limit: data.limit, + has_more, + }; + + template.render().map_err(|e| e.to_string()) + } +} diff --git a/crates/adapters/template-askama/templates/diary.html b/crates/adapters/template-askama/templates/diary.html new file mode 100644 index 0000000..7a54782 --- /dev/null +++ b/crates/adapters/template-askama/templates/diary.html @@ -0,0 +1,76 @@ + + + + + + My Movie Diary + + + +

Movie Diary

+ + +
+
+ Log a Movie + + +

+ + +

+ + +

+ + +

+ + +
+
+ +
+ + +
+ {% for entry in entries %} +
+ {% if let Some(poster) = entry.movie().poster_path() %} + + Poster + {% endif %} + +

{{ entry.movie().title().value() }} ({{ entry.movie().release_year().value() }})

+

Rating: {{ entry.review().rating().value() }} / 5

+ + {% if let Some(comment) = entry.review().comment() %} +

"{{ comment.value() }}"

+ {% endif %} + +

Watched on: {{ entry.review().watched_at().format("%Y-%m-%d") }}

+
+
+ {% else %} +

No movies logged yet. Go watch something!

+ {% endfor %} +
+ + +
+ {% if current_offset > 0 %} + Previous Page + {% endif %} + {% if has_more %} + Next Page + {% endif %} +
+ + \ No newline at end of file diff --git a/crates/presentation/src/lib.rs b/crates/presentation/src/lib.rs new file mode 100644 index 0000000..40006fc --- /dev/null +++ b/crates/presentation/src/lib.rs @@ -0,0 +1 @@ +pub mod ports; diff --git a/crates/presentation/src/ports.rs b/crates/presentation/src/ports.rs new file mode 100644 index 0000000..79c8d6a --- /dev/null +++ b/crates/presentation/src/ports.rs @@ -0,0 +1,5 @@ +use domain::models::{DiaryEntry, collections::Paginated}; + +pub trait HtmlRenderer: Send + Sync { + fn render_diary_page(&self, data: &Paginated) -> Result; +}