diff --git a/axum_server/justfile b/axum_server/justfile index 124f5cf..281234c 100644 --- a/axum_server/justfile +++ b/axum_server/justfile @@ -20,7 +20,10 @@ decap_server: # Run dev server in watch mode dev: - (just server_dev; just tailwind) | parallel + #!/usr/bin/env -S parallel --shebang --ungroup --jobs {{ num_cpus() }} + just server_dev + just tailwind + just decap_server # Run dev server in watch mode test: diff --git a/axum_server/src/blog_posts/blog_post_model.rs b/axum_server/src/blog_posts/blog_post_model.rs new file mode 100644 index 0000000..a428e34 --- /dev/null +++ b/axum_server/src/blog_posts/blog_post_model.rs @@ -0,0 +1,18 @@ +use chrono::{DateTime, Utc}; +use serde::Deserialize; + +use crate::post_utils::post_parser::deserialize_date; + +pub const BLOG_POST_PATH: &str = "../_posts/blog"; + +#[derive(Deserialize, Debug)] +pub struct BlogPostMetadata { + pub layout: String, + pub title: String, + pub segments: Vec, + pub published: bool, + #[serde(deserialize_with = "deserialize_date")] + pub date: DateTime, + pub thumbnail: Option, + pub tags: Vec, +} diff --git a/axum_server/src/blog_posts/featured_blog_posts.rs b/axum_server/src/blog_posts/featured_blog_posts.rs new file mode 100644 index 0000000..e812924 --- /dev/null +++ b/axum_server/src/blog_posts/featured_blog_posts.rs @@ -0,0 +1,16 @@ +use axum::http::StatusCode; + +use crate::post_utils::{post_listing::get_post_list, post_parser::ParseResult}; + +use super::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}; + +pub async fn get_featured_blog_posts() -> Result>, StatusCode> { + let post_list = get_post_list::(BLOG_POST_PATH).await?; + + let featured_posts = post_list + .into_iter() + .filter(|post| post.metadata.segments.contains(&"featured".to_string())) + .collect(); + + Ok(featured_posts) +} diff --git a/axum_server/src/blog_posts/mod.rs b/axum_server/src/blog_posts/mod.rs new file mode 100644 index 0000000..bcaca0f --- /dev/null +++ b/axum_server/src/blog_posts/mod.rs @@ -0,0 +1,3 @@ +pub mod blog_post_model; +pub mod featured_blog_posts; +pub mod tag_list; diff --git a/axum_server/src/tag_list.rs b/axum_server/src/blog_posts/tag_list.rs similarity index 82% rename from axum_server/src/tag_list.rs rename to axum_server/src/blog_posts/tag_list.rs index 3cbbfca..aeef179 100644 --- a/axum_server/src/tag_list.rs +++ b/axum_server/src/blog_posts/tag_list.rs @@ -1,15 +1,16 @@ -use crate::{ - pages::post::{PostMetadata, BLOG_POST_PATH}, - post_list::get_post_list, -}; use axum::http::StatusCode; use std::collections::HashMap; use tracing::debug; +use crate::{ + blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}, + post_utils::post_listing::get_post_list, +}; + pub async fn get_popular_blog_tags() -> Result, StatusCode> { const TAGS_LENGTH: usize = 7; - let post_list = get_post_list::(BLOG_POST_PATH).await?; + let post_list = get_post_list::(BLOG_POST_PATH).await?; let tags_sum = post_list .into_iter() .flat_map(|post| post.metadata.tags) diff --git a/axum_server/src/components/site_footer.rs b/axum_server/src/components/site_footer.rs index 80a61d4..d4dcb92 100644 --- a/axum_server/src/components/site_footer.rs +++ b/axum_server/src/components/site_footer.rs @@ -2,26 +2,25 @@ use askama::Template; use axum::http::StatusCode; use crate::{ - pages::post::{PostMetadata, BLOG_POST_PATH}, - post_list::get_post_list, - post_parser::ParseResult, + blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}, + post_utils::{post_listing::get_post_list, post_parser::ParseResult}, }; #[derive(Template)] #[template(path = "site_footer.html")] pub struct SiteFooter { - pub latest_posts: Vec>, + pub latest_posts: Vec>, } // TODO remove whole site footer anyway pub async fn render_site_footer() -> Result { - let mut post_list = get_post_list::(BLOG_POST_PATH).await?; + let mut post_list = get_post_list::(BLOG_POST_PATH).await?; post_list.sort_by_key(|post| post.metadata.date); post_list.reverse(); let latest_posts = post_list .into_iter() .take(6) - .collect::>>(); + .collect::>>(); Ok(SiteFooter { latest_posts }) } diff --git a/axum_server/src/featured_posts.rs b/axum_server/src/featured_posts.rs deleted file mode 100644 index 79067b0..0000000 --- a/axum_server/src/featured_posts.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::{ - pages::post::{PostMetadata, BLOG_POST_PATH}, - post_list::get_post_list, - post_parser::ParseResult, -}; -use axum::http::StatusCode; - -pub async fn get_featured_posts() -> Result>, StatusCode> { - let post_list = get_post_list::(BLOG_POST_PATH).await?; - - let featured_posts = post_list - .into_iter() - .filter(|post| post.metadata.segments.contains(&"featured".to_string())) - .collect(); - - Ok(featured_posts) -} diff --git a/axum_server/src/feed.rs b/axum_server/src/feed.rs index 7d67498..d2142a9 100644 --- a/axum_server/src/feed.rs +++ b/axum_server/src/feed.rs @@ -3,11 +3,11 @@ use axum::response::IntoResponse; use chrono::Utc; use rss::{ChannelBuilder, GuidBuilder, Item, ItemBuilder}; -use crate::pages::post::BLOG_POST_PATH; -use crate::{pages::post::PostMetadata, post_list::get_post_list}; +use crate::blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}; +use crate::post_utils::post_listing::get_post_list; pub async fn render_rss_feed() -> Result { - let mut post_list = get_post_list::(BLOG_POST_PATH) + let mut post_list = get_post_list::(BLOG_POST_PATH) .await .unwrap_or(vec![]); post_list.sort_by_key(|post| post.metadata.date); diff --git a/axum_server/src/main.rs b/axum_server/src/main.rs index debc465..69b9577 100644 --- a/axum_server/src/main.rs +++ b/axum_server/src/main.rs @@ -3,18 +3,14 @@ use tower_http::services::ServeDir; use tower_livereload::LiveReloadLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +mod blog_posts; mod components; -mod featured_posts; -mod featured_projects; mod feed; mod filters; mod pages; -mod post_list; -// mod project_list; -// TODO make post and project modules -mod post_parser; +mod post_utils; +mod projects; mod router; -mod tag_list; // mod template; #[tokio::main] diff --git a/axum_server/src/pages/post_list.rs b/axum_server/src/pages/blog_post_list.rs similarity index 79% rename from axum_server/src/pages/post_list.rs rename to axum_server/src/pages/blog_post_list.rs index 57a100b..c59c4e4 100644 --- a/axum_server/src/pages/post_list.rs +++ b/axum_server/src/pages/blog_post_list.rs @@ -2,33 +2,33 @@ use askama::Template; use axum::{extract::Path, http::StatusCode}; use crate::{ + blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH}, components::{ site_footer::{render_site_footer, SiteFooter}, site_header::{HeaderProps, Link}, }, filters, - post_list::get_post_list, - post_parser::ParseResult, + post_utils::{post_listing::get_post_list, post_parser::ParseResult}, }; -use super::post::{PostMetadata, BLOG_POST_PATH}; - #[derive(Template)] #[template(path = "post_list.html")] pub struct PostListTemplate { pub title: String, - pub posts: Vec>, + pub posts: Vec>, pub tag: Option, pub site_footer: SiteFooter, pub header_props: HeaderProps, } -pub async fn render_post_list(tag: Option>) -> Result { +pub async fn render_blog_post_list( + tag: Option>, +) -> Result { // 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 site_footer = render_site_footer().await?; - let mut post_list = get_post_list::(BLOG_POST_PATH).await?; + let mut post_list = get_post_list::(BLOG_POST_PATH).await?; post_list.sort_by_key(|post| post.metadata.date); post_list.reverse(); diff --git a/axum_server/src/pages/blog_post_page.rs b/axum_server/src/pages/blog_post_page.rs new file mode 100644 index 0000000..e0537ca --- /dev/null +++ b/axum_server/src/pages/blog_post_page.rs @@ -0,0 +1,43 @@ +use askama::Template; +use axum::{extract::Path, http::StatusCode}; +use chrono::{DateTime, Utc}; +use tokio::try_join; + +use crate::{ + blog_posts::blog_post_model::BlogPostMetadata, components::site_header::Link, filters, + post_utils::post_parser::parse_post, +}; + +use crate::components::{ + site_footer::{render_site_footer, SiteFooter}, + site_header::HeaderProps, +}; + +#[derive(Template)] +#[template(path = "blog_post.html")] +pub struct BlogPostTemplate { + pub title: String, + pub body: String, + pub date: DateTime, + pub tags: Vec, + pub site_footer: SiteFooter, + pub header_props: HeaderProps, +} + +pub async fn render_blog_post(Path(post_id): Path) -> Result { + let path = format!("../_posts/blog/{}.md", post_id); + let parse_post = parse_post::(&path); + let (site_footer, parsed) = try_join!(render_site_footer(), parse_post)?; + + Ok(BlogPostTemplate { + title: parsed.metadata.title, + date: parsed.metadata.date, + tags: parsed.metadata.tags, + body: parsed.body, + site_footer, + header_props: HeaderProps::with_back_link(Link { + href: "/blog".to_string(), + label: "All posts".to_string(), + }), + }) +} diff --git a/axum_server/src/pages/index.rs b/axum_server/src/pages/index.rs index cc7ec97..826d71e 100644 --- a/axum_server/src/pages/index.rs +++ b/axum_server/src/pages/index.rs @@ -2,36 +2,35 @@ use askama::Template; use axum::http::StatusCode; use tokio::try_join; -use crate::filters; use crate::{ + blog_posts::{ + blog_post_model::BlogPostMetadata, featured_blog_posts::get_featured_blog_posts, + tag_list::get_popular_blog_tags, + }, components::{ site_footer::{render_site_footer, SiteFooter}, site_header::HeaderProps, }, - featured_posts::get_featured_posts, - featured_projects::get_featured_projects, - post_parser::ParseResult, - tag_list::get_popular_blog_tags, + filters, + post_utils::post_parser::ParseResult, + projects::{featured_projects::get_featured_projects, project_model::ProjectMetadata}, }; -use super::post::PostMetadata; -use super::project::ProjectMetadata; - #[derive(Template)] #[template(path = "index.html")] pub struct IndexTemplate { site_footer: SiteFooter, header_props: HeaderProps, blog_tags: Vec, - featured_posts: Vec>, + featured_blog_posts: Vec>, featured_projects: Vec>, } pub async fn render_index() -> Result { - let (site_footer, blog_tags, featured_posts, featured_projects) = try_join!( + let (site_footer, blog_tags, featured_blog_posts, featured_projects) = try_join!( render_site_footer(), get_popular_blog_tags(), - get_featured_posts(), + get_featured_blog_posts(), get_featured_projects() )?; @@ -39,7 +38,7 @@ pub async fn render_index() -> Result { site_footer, header_props: HeaderProps::default(), blog_tags, - featured_posts, + featured_blog_posts, featured_projects, }) } diff --git a/axum_server/src/pages/mod.rs b/axum_server/src/pages/mod.rs index 7d686e4..eca9c52 100644 --- a/axum_server/src/pages/mod.rs +++ b/axum_server/src/pages/mod.rs @@ -1,6 +1,5 @@ pub mod admin; +pub mod blog_post_list; +pub mod blog_post_page; pub mod contact; pub mod index; -pub mod post; -pub mod post_list; -pub mod project; diff --git a/axum_server/src/pages/post.rs b/axum_server/src/pages/post.rs deleted file mode 100644 index f7d54e5..0000000 --- a/axum_server/src/pages/post.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::{components::site_header::Link, filters}; -use askama::Template; -use axum::{extract::Path, http::StatusCode}; -use chrono::{DateTime, Utc}; -use serde::Deserialize; -use tokio::try_join; - -use crate::{ - components::{ - site_footer::{render_site_footer, SiteFooter}, - site_header::HeaderProps, - }, - post_parser::{deserialize_date, parse_post}, -}; - -pub const BLOG_POST_PATH: &str = "../_posts/blog"; - -#[derive(Deserialize, Debug)] -pub struct PostMetadata { - pub layout: String, - pub title: String, - pub segments: Vec, - pub published: bool, - #[serde(deserialize_with = "deserialize_date")] - pub date: DateTime, - pub thumbnail: Option, - pub tags: Vec, -} - -#[derive(Template)] -#[template(path = "post.html")] -pub struct PostTemplate { - pub title: String, - pub body: String, - pub date: DateTime, - pub tags: Vec, - pub site_footer: SiteFooter, - pub header_props: HeaderProps, -} - -pub async fn render_post(Path(post_id): Path) -> Result { - let path = format!("../_posts/blog/{}.md", post_id); - let parse_post = parse_post::(&path); - let (site_footer, parsed) = try_join!(render_site_footer(), parse_post)?; - - Ok(PostTemplate { - title: parsed.metadata.title, - date: parsed.metadata.date, - tags: parsed.metadata.tags, - body: parsed.body, - site_footer, - header_props: HeaderProps::with_back_link(Link { - href: "/blog".to_string(), - label: "All posts".to_string(), - }), - }) -} diff --git a/axum_server/src/post_utils/mod.rs b/axum_server/src/post_utils/mod.rs new file mode 100644 index 0000000..8c2382e --- /dev/null +++ b/axum_server/src/post_utils/mod.rs @@ -0,0 +1,2 @@ +pub mod post_listing; +pub mod post_parser; diff --git a/axum_server/src/post_list.rs b/axum_server/src/post_utils/post_listing.rs similarity index 95% rename from axum_server/src/post_list.rs rename to axum_server/src/post_utils/post_listing.rs index 6588c75..f760ea2 100644 --- a/axum_server/src/post_list.rs +++ b/axum_server/src/post_utils/post_listing.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use tokio::fs::read_dir; use tracing::info; -use crate::post_parser::{parse_post, ParseResult}; +use super::post_parser::{parse_post, ParseResult}; pub async fn get_post_list<'de, Metadata: DeserializeOwned>( path: &str, diff --git a/axum_server/src/post_parser.rs b/axum_server/src/post_utils/post_parser.rs similarity index 100% rename from axum_server/src/post_parser.rs rename to axum_server/src/post_utils/post_parser.rs diff --git a/axum_server/src/featured_projects.rs b/axum_server/src/projects/featured_projects.rs similarity index 84% rename from axum_server/src/featured_projects.rs rename to axum_server/src/projects/featured_projects.rs index f881690..6bc9837 100644 --- a/axum_server/src/featured_projects.rs +++ b/axum_server/src/projects/featured_projects.rs @@ -1,9 +1,11 @@ -use crate::{ - pages::project::ProjectMetadata, - post_list::get_post_list, +use axum::http::StatusCode; + +use crate::post_utils::{ + post_listing::get_post_list, post_parser::{parse_html, ParseResult}, }; -use axum::http::StatusCode; + +use super::project_model::ProjectMetadata; pub async fn get_featured_projects() -> Result>, StatusCode> { let project_list = get_post_list::("../_projects").await?; diff --git a/axum_server/src/projects/mod.rs b/axum_server/src/projects/mod.rs new file mode 100644 index 0000000..c135f9e --- /dev/null +++ b/axum_server/src/projects/mod.rs @@ -0,0 +1,2 @@ +pub mod featured_projects; +pub mod project_model; diff --git a/axum_server/src/pages/project.rs b/axum_server/src/projects/project_model.rs similarity index 100% rename from axum_server/src/pages/project.rs rename to axum_server/src/projects/project_model.rs diff --git a/axum_server/src/router.rs b/axum_server/src/router.rs index 1f87198..b812f6f 100644 --- a/axum_server/src/router.rs +++ b/axum_server/src/router.rs @@ -1,8 +1,8 @@ use crate::{ feed::render_rss_feed, pages::{ - admin::render_admin, contact::render_contact, index::render_index, post::render_post, - post_list::render_post_list, + admin::render_admin, blog_post_list::render_blog_post_list, + blog_post_page::render_blog_post, contact::render_contact, index::render_index, }, }; use axum::{extract::MatchedPath, http::Request, routing::get, Router}; @@ -12,9 +12,9 @@ use tracing::info_span; pub fn get_router() -> Router { Router::new() .route("/", get(render_index)) - .route("/blog", get(render_post_list)) - .route("/blog/tags/:tag", get(render_post_list)) - .route("/blog/:post_id", get(render_post)) + .route("/blog", get(render_blog_post_list)) + .route("/blog/tags/:tag", get(render_blog_post_list)) + .route("/blog/:post_id", get(render_blog_post)) .route("/contact", get(render_contact)) .route("/admin", get(render_admin)) .route("/feed.xml", get(render_rss_feed)) diff --git a/axum_server/templates/post.html b/axum_server/templates/blog_post.html similarity index 100% rename from axum_server/templates/post.html rename to axum_server/templates/blog_post.html diff --git a/axum_server/templates/components/project_preview_card.html b/axum_server/templates/components/project_preview_card.html index 3edb8fc..40d7f86 100644 --- a/axum_server/templates/components/project_preview_card.html +++ b/axum_server/templates/components/project_preview_card.html @@ -35,7 +35,7 @@