fix: recursive bloat in html

the meta, title and style tags are being replicated in the list on '/'
This commit is contained in:
itsscb 2025-06-21 23:59:44 +02:00
parent 76d27dfcec
commit aaaed7ce62
9 changed files with 244 additions and 34 deletions

View File

@ -10,6 +10,7 @@ chrono = { version = "0.4.41", features = ["serde"] }
serde = { version = "1.0.219", features = ["derive", "rc"] }
serde_json = "1.0.140"
tokio = { version = "1.45.1", features = ["full", "tracing"] }
tower-http = { version = "0.6.6", features = ["trace"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
uuid = { version = "1.17.0", features = ["serde", "v4"] }

View File

@ -3,39 +3,75 @@ use askama::Template;
use axum::{
Router,
extract::{Path, State},
http::HeaderMap,
http::StatusCode,
response::{Html, IntoResponse},
routing::get,
serve,
};
use person::PersonID;
use stammbaum::Stammbaum;
use person::{PersonID, PersonTemplate, PersonTemplateFull};
use stammbaum::{Stammbaum, StammbaumFull};
use std::io::BufReader;
use tokio::runtime::Runtime;
use tower_http::trace::TraceLayer;
use tracing::{error, instrument, warn};
mod person;
mod stammbaum;
#[instrument(skip(stammbaum))]
async fn get_person(
Path(id): Path<String>,
headers: HeaderMap,
State(stammbaum): State<Stammbaum>,
) -> Result<impl IntoResponse, impl IntoResponse> {
let id = match PersonID::try_from(id) {
Ok(id) => id,
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
Err(e) => {
error!(error = ?e);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
match stammbaum.get(id) {
None => Err(StatusCode::NOT_FOUND),
Some(p) => Ok(Html(
p.render().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?,
)),
None => {
warn!(id = ?id, "not_found");
Err(StatusCode::NOT_FOUND)
}
Some(p) => match headers.get("HX-Request") {
Some(_) => Ok(Html(PersonTemplate::from(p.to_owned()).render().map_err(
|e| {
error!(error=?e, id = ?id);
StatusCode::INTERNAL_SERVER_ERROR
},
)?)),
_ => Ok(Html(
PersonTemplateFull::from(p.to_owned())
.render()
.map_err(|e| {
error!(error=?e, id = ?id);
StatusCode::INTERNAL_SERVER_ERROR
})?,
)),
},
}
}
#[instrument]
async fn list_stammbaum(
headers: HeaderMap,
State(stammbaum): State<Stammbaum>,
) -> Result<impl IntoResponse, impl IntoResponse> {
Ok::<axum::response::Html<std::string::String>, StatusCode>(Html(stammbaum.render()))
match headers.get("HX-Request") {
Some(_) => Ok::<axum::response::Html<std::string::String>, StatusCode>(Html(
stammbaum
.render()
.expect("should have rendered the template"),
)),
None => Ok::<axum::response::Html<std::string::String>, StatusCode>(Html(
StammbaumFull::from(stammbaum)
.render()
.expect("should have rendered the template"),
)),
}
}
pub fn run() -> Result<(), String> {
@ -50,7 +86,9 @@ pub fn run() -> Result<(), String> {
let app = Router::new()
.route("/", get(list_stammbaum))
.route("/{id}", get(get_person))
.layer(TraceLayer::new_for_http())
.with_state(stammbaum);
let addr = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.expect("faild to bind port");

View File

@ -1,5 +1,13 @@
use stammbaum::run;
use tracing_subscriber::EnvFilter;
#[allow(clippy::unwrap_used)]
fn main() {
#[allow(clippy::unwrap_used)]
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("stammbaum=debug"))
.unwrap(),
)
.init();
run().unwrap();
}

View File

@ -7,8 +7,30 @@ use uuid::Uuid;
pub type PersonID = Uuid;
#[derive(Template, Clone, Debug, Serialize, Deserialize)]
#[derive(Template)]
#[template(path = "person.html")]
pub struct PersonTemplate {
person: Person,
}
impl From<Person> for PersonTemplate {
fn from(value: Person) -> Self {
Self { person: value }
}
}
#[derive(Template)]
#[template(path = "person_full.html")]
pub struct PersonTemplateFull {
person: Person,
}
impl From<Person> for PersonTemplateFull {
fn from(value: Person) -> Self {
Self { person: value }
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Person {
id: PersonID,
first_name: Arc<str>,
@ -44,6 +66,29 @@ impl Person {
}
}
pub fn sex(&self) -> &Sex {
&self.sex
}
pub fn date_of_birth(&self) -> DateTime<Utc> {
self.date_of_birth
}
pub fn maiden_name(&self) -> Option<Arc<str>> {
self.maiden_name.clone()
}
pub fn last_name(&self) -> Arc<str> {
self.last_name.clone()
}
pub fn first_name(&self) -> Arc<str> {
self.first_name.clone()
}
pub fn parents(&self) -> &Vec<PersonID> {
&self.parents
}
pub fn add_parent(&mut self, parent: PersonID) {
self.parents.push(parent);
}

View File

@ -1,14 +1,29 @@
use serde::{Deserialize, Serialize};
use crate::person::{Person, PersonID};
use crate::person::{Person, PersonID, Sex};
use askama::Template;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Template)]
#[template(path = "stammbaum.html")]
pub struct Stammbaum {
members: Vec<Person>,
}
#[derive(Template)]
#[template(path = "stammbaum_full.html")]
pub struct StammbaumFull {
members: Vec<Person>,
}
impl From<Stammbaum> for StammbaumFull {
fn from(value: Stammbaum) -> Self {
Self {
members: value.members,
}
}
}
#[allow(dead_code)]
impl Stammbaum {
pub fn new(members: Vec<Person>) -> Self {
@ -18,13 +33,6 @@ impl Stammbaum {
pub fn get(&self, id: PersonID) -> Option<&Person> {
self.members.iter().find(|p| p.id() == id)
}
pub fn render(&self) -> String {
self.members
.iter()
.flat_map(|p: &Person| p.render())
.collect()
}
}
mod test {

View File

@ -1,31 +1,28 @@
{% extends "_layout.html" %}
{%- block content -%}
<div class="family-node">
<div class="name">
<span class="first-name">{{ first_name }}</span>
<span class="last-name">{{ last_name }}</span>
{% if maiden_name.is_some() %}
<span class="maiden-name">({{ maiden_name.as_ref().unwrap() }})</span>
<span class="first-name">{{ person.first_name }}</span>
<span class="last-name">{{ person.last_name }}</span>
{% if person.maiden_name.is_some() %}
<span class="maiden-name">({{ person.maiden_name.as_ref().unwrap() }})</span>
{% endif %}
</div>
<div class="details">
<div>Sex:
{% match sex %}
{% match person.sex %}
{% when Sex::Male %}Male
{% when Sex::Female %}Female
{% when Sex::Other(desc) %}{{ desc }}
{% endmatch %}
</div>
<div>Birthday: {{ date_of_birth.format("%Y-%m-%d") }}</div>
<a href="/{{ id }}">ID: {{ id }}</a>
<div>Birthday: {{ person.date_of_birth.format("%Y-%m-%d") }}</div>
<a href="/{{ person.id }}">ID: {{ person.id }}</a>
<div class="parents">
Parents:
{% if parents.is_empty() %}
{% if person.parents.is_empty() %}
<ul></ul>
{% else %}
<ul>
{% for parent_id in &parents %}
{% for parent_id in &person.parents %}
<li>
<a href="/{{ parent_id }}">{{ parent_id }}</a>
@ -35,4 +32,3 @@
{% endif %}
</div>
</div>
{%- endblock -%}

View File

@ -0,0 +1,38 @@
{% extends "_layout.html" %}
{%- block content -%}
<div class="family-node">
<div class="name">
<span class="first-name">{{ person.first_name }}</span>
<span class="last-name">{{ person.last_name }}</span>
{% if person.maiden_name.is_some() %}
<span class="maiden-name">({{ person.maiden_name.as_ref().unwrap() }})</span>
{% endif %}
</div>
<div class="details">
<div>Sex:
{% match person.sex %}
{% when Sex::Male %}Male
{% when Sex::Female %}Female
{% when Sex::Other(desc) %}{{ desc }}
{% endmatch %}
</div>
<div>Birthday: {{ person.date_of_birth.format("%Y-%m-%d") }}</div>
<a href="/{{ person.id }}">ID: {{ person.id }}</a>
<div class="parents">
Parents:
{% if person.parents.is_empty() %}
<ul></ul>
{% else %}
<ul>
{% for parent_id in &person.parents %}
<li>
<a href="/{{ parent_id }}">{{ parent_id }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{%- endblock -%}

36
templates/stammbaum.html Normal file
View File

@ -0,0 +1,36 @@
{% for person in members %}
<div class="family-node">
<div class="name">
<span class="first-name">{{ person.first_name() }}</span>
<span class="last-name">{{ person.last_name() }}</span>
{% if person.maiden_name().is_some() %}
<span class="maiden-name">({{ person.maiden_name().as_ref().unwrap() }})</span>
{% endif %}
</div>
<div class="details">
<div>Sex:
{% match person.sex() %}
{% when Sex::Male %}Male
{% when Sex::Female %}Female
{% when Sex::Other(desc) %}{{ desc }}
{% endmatch %}
</div>
<div>Birthday: {{ person.date_of_birth().format("%Y-%m-%d") }}</div>
<a href="/{{ person.id() }}">ID: {{ person.id() }}</a>
<div class="parents">
Parents:
{% if person.parents().is_empty() %}
<ul></ul>
{% else %}
<ul>
{% for parent_id in &person.parents() %}
<li>
<a href="/{{ parent_id }}">{{ parent_id }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}

View File

@ -0,0 +1,40 @@
{% extends "_layout.html" %}
{%- block content -%}
{% for person in members %}
<div class="family-node">
<div class="name">
<span class="first-name">{{ person.first_name() }}</span>
<span class="last-name">{{ person.last_name() }}</span>
{% if person.maiden_name().is_some() %}
<span class="maiden-name">({{ person.maiden_name().as_ref().unwrap() }})</span>
{% endif %}
</div>
<div class="details">
<div>Sex:
{% match person.sex() %}
{% when Sex::Male %}Male
{% when Sex::Female %}Female
{% when Sex::Other(desc) %}{{ desc }}
{% endmatch %}
</div>
<div>Birthday: {{ person.date_of_birth().format("%Y-%m-%d") }}</div>
<a href="/{{ person.id() }}">ID: {{ person.id() }}</a>
<div class="parents">
Parents:
{% if person.parents().is_empty() %}
<ul></ul>
{% else %}
<ul>
{% for parent_id in &person.parents() %}
<li>
<a href="/{{ parent_id }}">{{ parent_id }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% endfor %}
{%- endblock -%}