site_footer and post listing order

This commit is contained in:
Michal Vanko 2024-01-18 22:33:36 +01:00
parent e0ad3f29ae
commit c9704a20f6
9 changed files with 108 additions and 38 deletions

View File

@ -0,0 +1 @@
pub mod site_footer;

View File

@ -0,0 +1,21 @@
use askama::Template;
use crate::{pages::post::PostMetadata, post_list::get_post_list, post_parser::ParseResult};
#[derive(Template)]
#[template(path = "site_footer.html")]
pub struct SiteFooter {
pub latest_posts: Vec<ParseResult<PostMetadata>>,
}
pub async fn render_site_footer() -> SiteFooter {
let mut post_list = get_post_list::<PostMetadata>().await.unwrap_or(vec![]);
post_list.sort_by_key(|post| post.metadata.date);
post_list.reverse();
let latest_posts = post_list
.into_iter()
.take(6)
.collect::<Vec<ParseResult<PostMetadata>>>();
SiteFooter { latest_posts }
}

View File

@ -1,8 +1,9 @@
use askama::filters::format;
use axum; use axum;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod components;
mod pages; mod pages;
mod post_list;
mod post_parser; mod post_parser;
mod router; mod router;
// mod template; // mod template;

View File

@ -1,9 +1,14 @@
use askama::Template; use askama::Template;
use crate::components::site_footer::{render_site_footer, SiteFooter};
#[derive(Template)] #[derive(Template)]
#[template(path = "index.html")] #[template(path = "index.html")]
pub struct IndexTemplate {} pub struct IndexTemplate {
site_footer: SiteFooter,
}
pub async fn render_index() -> IndexTemplate { pub async fn render_index() -> IndexTemplate {
IndexTemplate {} let site_footer = render_site_footer().await;
IndexTemplate { site_footer }
} }

View File

@ -3,7 +3,10 @@ use axum::{extract::Path, http::StatusCode};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::Deserialize; use serde::Deserialize;
use crate::post_parser::{deserialize_date, parse_post}; use crate::{
components::site_footer::{render_site_footer, SiteFooter},
post_parser::{deserialize_date, parse_post},
};
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct PostMetadata { pub struct PostMetadata {
@ -22,13 +25,20 @@ pub struct PostMetadata {
pub struct PostTemplate { pub struct PostTemplate {
pub title: String, pub title: String,
pub body: String, pub body: String,
pub site_footer: SiteFooter,
} }
pub async fn render_post(Path(post_id): Path<String>) -> Result<PostTemplate, StatusCode> { pub async fn render_post(Path(post_id): Path<String>) -> Result<PostTemplate, StatusCode> {
let site_footer = tokio::spawn(render_site_footer());
let path = format!("../_posts/blog/{}.md", post_id); let path = format!("../_posts/blog/{}.md", post_id);
let parsed = parse_post::<PostMetadata>(&path).await?; let parsed = parse_post::<PostMetadata>(&path).await?;
let site_footer = site_footer
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(PostTemplate { Ok(PostTemplate {
title: parsed.metadata.title, title: parsed.metadata.title,
body: parsed.body, body: parsed.body,
site_footer,
}) })
} }

View File

@ -1,9 +1,11 @@
use askama::Template; use askama::Template;
use axum::{extract::Path, http::StatusCode}; use axum::{extract::Path, http::StatusCode};
use tokio::fs::read_dir;
use tracing::info;
use crate::post_parser::{parse_post, ParseResult}; use crate::{
components::site_footer::{render_site_footer, SiteFooter},
post_list::get_post_list,
post_parser::ParseResult,
};
use super::post::PostMetadata; use super::post::PostMetadata;
@ -13,32 +15,20 @@ pub struct PostListTemplate {
pub title: String, pub title: String,
pub posts: Vec<ParseResult<PostMetadata>>, pub posts: Vec<ParseResult<PostMetadata>>,
pub tag: Option<String>, pub tag: Option<String>,
pub site_footer: SiteFooter,
} }
pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTemplate, StatusCode> { pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTemplate, StatusCode> {
// I will forget what happens here in a week. But essentially it's pattern matching and shadowing // I will forget what happens here in a week. But essentially it's pattern matching and shadowing
let tag = tag.map(|Path(tag)| tag); let tag = tag.map(|Path(tag)| tag);
let path = "../_posts/blog/"; let site_footer = tokio::spawn(render_site_footer());
let mut dir = read_dir(path) let mut post_list = get_post_list::<PostMetadata>().await?;
.await post_list.sort_by_key(|post| post.metadata.date);
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; post_list.reverse();
let mut posts: Vec<ParseResult<PostMetadata>> = Vec::new();
while let Some(file) = dir let posts = match &tag {
.next_entry() Some(tag) => post_list
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
{
let file_path = file.path();
let file_path_str = file_path.to_str().unwrap();
info!(":{}", file_path_str);
let post = parse_post::<PostMetadata>(file_path_str).await?;
posts.push(post);
}
let mut posts = match &tag {
Some(tag) => posts
.into_iter() .into_iter()
.filter(|post| { .filter(|post| {
post.metadata post.metadata
@ -49,23 +39,18 @@ pub async fn render_post_list(tag: Option<Path<String>>) -> Result<PostListTempl
.contains(&tag.to_lowercase()) .contains(&tag.to_lowercase())
}) })
.collect(), .collect(),
None => posts, None => post_list,
}; };
if std::env::var("TARGET") let site_footer = site_footer
.unwrap_or_else(|_| "DEV".to_owned()) .await
.eq("PROD") .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
{
posts = posts
.into_iter()
.filter(|post| !post.slug.starts_with("dev"))
.collect()
}
Ok(PostListTemplate { Ok(PostListTemplate {
title: "Posts".to_owned(), title: "Posts".to_owned(),
posts, posts,
tag, tag,
site_footer,
}) })
} }

View File

@ -0,0 +1,39 @@
use axum::http::StatusCode;
use serde::de::DeserializeOwned;
use tokio::fs::read_dir;
use tracing::info;
use crate::post_parser::{parse_post, ParseResult};
pub async fn get_post_list<'de, Metadata: DeserializeOwned>(
) -> Result<Vec<ParseResult<Metadata>>, StatusCode> {
let path = "../_posts/blog/";
let mut dir = read_dir(path)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let mut posts: Vec<ParseResult<Metadata>> = Vec::new();
while let Some(file) = dir
.next_entry()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
{
let file_path = file.path();
let file_path_str = file_path.to_str().unwrap();
info!(":{}", file_path_str);
let post = parse_post::<Metadata>(file_path_str).await?;
posts.push(post);
}
if std::env::var("TARGET")
.unwrap_or_else(|_| "DEV".to_owned())
.eq("PROD")
{
posts = posts
.into_iter()
.filter(|post| !post.slug.starts_with("dev"))
.collect()
}
return Ok(posts);
}

View File

@ -31,7 +31,8 @@
<link rel="icon" type="image/png" href="/m-logo-192.png" /> <link rel="icon" type="image/png" href="/m-logo-192.png" />
</head> </head>
<body> <body>
{% block content %} Placeholder {% endblock %} {% block content %} Placeholder {% endblock %} {# footer, should be not
{# footer, should be not dependant on the each individual handler but it should have it's own handler #} dependant on the each individual handler but it should have it's own handler
#} {{ site_footer|safe }}
</body> </body>
</html> </html>

View File

@ -0,0 +1,7 @@
<footer>
{% for post in latest_posts %}
<article>{{post.metadata.title}}</article>
{% endfor %}
</footer>