Commit Diff


commit - b863a55d8a947cfcc6296d34edae4d1365a7725f
commit + 017324a1e98ac2da7812e4fcc06fe67d424e4ffb
blob - 6890378b5086a50a0ce0de583373098f9b1c4910
blob + 0c41cb9f7eff65760953f32f5bb282e8150efa89
--- web/template/Cargo.toml
+++ web/template/Cargo.toml
@@ -9,6 +9,7 @@ edition = "2024"
 [dependencies]
 anyhow = "=1.0.100"
 axum = "=0.8.6"
+minijinja = "=2.12.0"
 serde = { version = "=1.0.228", features = ["derive"] }
 tokio = { version = "=1.48.0", features = ["macros", "rt-multi-thread", "signal"] }
 tower-http = { version = "=0.6.6", features = ["timeout", "trace", "fs"] }
blob - d686877bd50f83c76a929f05bec5389e8c050dc9
blob + 20a1974ddf614cffd2d2a597cde1e84df5850396
--- web/template/src/main.rs
+++ web/template/src/main.rs
@@ -14,30 +14,30 @@
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 
-use std::time::Duration;
+use std::sync::Arc;
 
-use axum::{Router, response::Html, routing::get};
+use minijinja::Environment;
 use tokio::net::TcpListener;
-use tower_http::{
-    services::ServeDir, timeout::TimeoutLayer, trace::TraceLayer,
-};
 use tracing::info;
 
 mod helpers;
+mod router;
+mod state;
 
 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
     helpers::init_tracing();
 
-    let app = Router::new()
-        .route("/", get(handler))
-        // TODO(msi): from config folder asssets
-        .nest_service("/assets", ServeDir::new("assets"))
-        .layer((
-            TraceLayer::new_for_http(),
-            TimeoutLayer::new(Duration::from_secs(10)), // TODO(msi): from config
-        ));
+    let mut env = Environment::new();
+    env.add_template("layout", include_str!("../templates/layout.jinja"))?;
+    env.add_template("home", include_str!("../templates/home.jinja"))?;
+    env.add_template("content", include_str!("../templates/content.jinja"))?;
+    env.add_template("about", include_str!("../templates/about.jinja"))?;
 
+    let app_state = Arc::new(state::AppState { env });
+
+    let app = router::route(app_state);
+
     // TODO(msi): from config
     let listener = TcpListener::bind("0.0.0.0:3000").await?;
     info!("listening on http://{}", listener.local_addr().unwrap());
@@ -47,18 +47,3 @@ async fn main() -> anyhow::Result<()> {
 
     Ok(())
 }
-
-const INDEX: &'static str = r#"<html>
-<head>
-<link href="/assets/css/styles.css" rel="stylesheet" type="text/css">
-</head>
-<body>
-<h1><h1>Hello, World {{project-name}} =]</h1>
-<p>Template form https://ijanc.org</p>
-</body>
-</html>
-"#;
-
-async fn handler() -> Html<&'static str> {
-    Html(INDEX)
-}
blob - /dev/null
blob + d1c8ba234bce5e312437dc11de6a28a30a401722 (mode 644)
--- /dev/null
+++ web/template/src/router.rs
@@ -0,0 +1,86 @@
+//
+// Copyright (c) 2025 murilo ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+use std::{sync::Arc, time::Duration};
+
+use axum::{
+    Router, extract::State, http::StatusCode, response::Html, routing::get,
+};
+use minijinja::context;
+use tower_http::{
+    services::ServeDir, timeout::TimeoutLayer, trace::TraceLayer,
+};
+
+use crate::state::AppState;
+
+pub(crate) fn route(app_state: Arc<AppState>) -> Router {
+    Router::new()
+        .route("/", get(handler_home))
+        .route("/content", get(handler_content))
+        .route("/about", get(handler_about))
+        // TODO(msi): from config folder asssets
+        .nest_service("/assets", ServeDir::new("assets"))
+        .layer((
+            TraceLayer::new_for_http(),
+            // TODO(msi): from config
+            TimeoutLayer::new(Duration::from_secs(10)),
+        ))
+        .with_state(app_state)
+}
+
+async fn handler_home(
+    State(state): State<Arc<AppState>>,
+) -> Result<Html<String>, StatusCode> {
+    let template = state.env.get_template("home").unwrap();
+
+    let rendered = template
+        .render(context! {
+            title => "Home",
+            welcome_text => "Hello World!",
+        })
+        .unwrap();
+
+    Ok(Html(rendered))
+}
+
+async fn handler_content(
+    State(state): State<Arc<AppState>>,
+) -> Result<Html<String>, StatusCode> {
+    let template = state.env.get_template("content").unwrap();
+
+    let some_example_entries = vec!["Data 1", "Data 2", "Data 3"];
+
+    let rendered = template
+        .render(context! {
+            title => "Content",
+            entries => some_example_entries,
+        })
+        .unwrap();
+
+    Ok(Html(rendered))
+}
+
+async fn handler_about(
+    State(state): State<Arc<AppState>>,
+) -> Result<Html<String>, StatusCode> {
+    let template = state.env.get_template("about").unwrap();
+
+    let rendered = template.render(context!{
+        title => "About",
+        about_text => "Simple demonstration layout for an axum project with minijinja as templating engine.",
+    }).unwrap();
+
+    Ok(Html(rendered))
+}
blob - /dev/null
blob + d47938337992e352bc0b74cff22e47d95d2faee8 (mode 644)
--- /dev/null
+++ web/template/src/state.rs
@@ -0,0 +1,21 @@
+//
+// Copyright (c) 2025 murilo ijanc' <murilo@ijanc.org>
+//
+// Permission to use, copy, modify, and distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+//
+
+use minijinja::Environment;
+
+pub(crate) struct AppState {
+    pub(crate) env: Environment<'static>,
+}
blob - /dev/null
blob + ba8c97e3ce2e51c04e27ed51b5ba38ce7c7cf63e (mode 644)
--- /dev/null
+++ web/template/templates/about.jinja
@@ -0,0 +1,6 @@
+{% extends "layout" %}
+{% block title %}{{ super() }} | {{ title }} {% endblock %}
+{% block body %}
+<h1>{{ title }}</h1>
+<p>{{ about_text }}</p>
+{% endblock %}
blob - /dev/null
blob + b3fbfa6c79324b0b5505364120fa210f5261ad26 (mode 644)
--- /dev/null
+++ web/template/templates/content.jinja
@@ -0,0 +1,10 @@
+{% extends "layout" %}
+{% block title %}{{ super() }} | {{ title }} {% endblock %}
+{% block body %}
+<h1>{{ title }}</h1>
+{% for data_entry in entries %}
+<ul>
+    <li>{{ data_entry }}</li>
+</ul>
+{% endfor %}
+{% endblock %}
blob - /dev/null
blob + 2d231db34bb54c6d2d78c71cf88c9bd7b371cb24 (mode 644)
--- /dev/null
+++ web/template/templates/home.jinja
@@ -0,0 +1,6 @@
+{% extends "layout" %}
+{% block title %}{{ super() }} | {{ title }} {% endblock %}
+{% block body %}
+<h1>{{ title }}</h1>
+<p>{{ welcome_text }}</p>
+{% endblock %}
blob - /dev/null
blob + 02232bcc6116173cbbcaab69161cdd4ea05e4b02 (mode 644)
--- /dev/null
+++ web/template/templates/layout.jinja
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+  <link href="/assets/css/styles.css" rel="stylesheet" type="text/css">
+  <head><title>{% block title %}Website Name{% endblock %}</title></head>
+  <body>
+    <nav>
+        <ul>
+            <li><a href="/">Home</a></li>
+            <li><a href="/content">Content</a></li>
+            <li><a href="/about">About</a></li>
+        </ul>
+    </nav>
+    <h1><h1>Hello, World web =]</h1>
+    <p>Template form https://ijanc.org</p>
+    {% block body %}{% endblock %}
+  </body>
+</html>