This commit is contained in:
@ -4,13 +4,19 @@ use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||
post_utils::post_listing::get_post_list,
|
||||
post_utils::{post_listing::get_post_list, post_parser::ParseResult},
|
||||
};
|
||||
|
||||
pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
|
||||
pub async fn get_popular_tags(segment: Option<String>) -> Result<Vec<String>, StatusCode> {
|
||||
const TAGS_LENGTH: usize = 7;
|
||||
|
||||
let post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
|
||||
let mut post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
|
||||
post_list.retain(|post| post.metadata.published);
|
||||
|
||||
if let Some(segment) = segment {
|
||||
post_list.retain(|post| post.metadata.segments.contains(&segment));
|
||||
}
|
||||
|
||||
let tags_sum = post_list
|
||||
.into_iter()
|
||||
.flat_map(|post| post.metadata.tags)
|
||||
@ -27,12 +33,32 @@ pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
|
||||
debug!("Tag: {}, Count: {}", tag, count);
|
||||
}
|
||||
|
||||
let popular_blog_tags = sorted_tags_by_count
|
||||
let popular_tags = sorted_tags_by_count
|
||||
.into_iter()
|
||||
.map(|tag_count| tag_count.0)
|
||||
.filter(|tag| tag != "dev")
|
||||
.take(TAGS_LENGTH)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
Ok(popular_blog_tags)
|
||||
Ok(popular_tags)
|
||||
}
|
||||
|
||||
pub fn get_posts_by_tag(
|
||||
post_list: Vec<ParseResult<BlogPostMetadata>>,
|
||||
tag: &Option<String>,
|
||||
) -> Vec<ParseResult<BlogPostMetadata>> {
|
||||
match tag {
|
||||
Some(tag) => post_list
|
||||
.into_iter()
|
||||
.filter(|post| {
|
||||
post.metadata
|
||||
.tags
|
||||
.iter()
|
||||
.map(|post_tag| post_tag.to_lowercase())
|
||||
.collect::<String>()
|
||||
.contains(&tag.to_lowercase())
|
||||
})
|
||||
.collect(),
|
||||
None => post_list,
|
||||
}
|
||||
}
|
||||
|
@ -49,5 +49,5 @@ pub async fn render_rss_feed() -> Result<impl IntoResponse, StatusCode> {
|
||||
.build();
|
||||
|
||||
let response = feed_builder.to_string();
|
||||
return Ok(([(header::CONTENT_TYPE, "application/xml")], response));
|
||||
Ok(([(header::CONTENT_TYPE, "application/xml")], response))
|
||||
}
|
||||
|
20
src/main.rs
20
src/main.rs
@ -1,4 +1,5 @@
|
||||
use axum::{self};
|
||||
use askama_axum::IntoResponse;
|
||||
use axum::{self, extract::OriginalUri, http::StatusCode};
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
use tracing::info;
|
||||
@ -33,9 +34,14 @@ async fn main() {
|
||||
.nest_service("/styles", ServeDir::new("styles"))
|
||||
.nest_service("/images", ServeDir::new("static/images"))
|
||||
.nest_service("/fonts", ServeDir::new("static/fonts"))
|
||||
.nest_service("/files", ServeDir::new("static/files"))
|
||||
.nest_service("/generated_images", ServeDir::new("generated_images"))
|
||||
.nest_service("/egg-fetcher", ServeDir::new("static/egg-fetcher"))
|
||||
.nest_service("/svg", ServeDir::new("static/svg"))
|
||||
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")); // Decap CMS config
|
||||
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")) // Decap CMS config
|
||||
.nest_service("/robots.txt", ServeDir::new("robots.txt"));
|
||||
|
||||
let app = app.fallback(handler_404);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let app = app.layer(LiveReloadLayer::new());
|
||||
@ -48,6 +54,11 @@ async fn main() {
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
async fn handler_404(OriginalUri(original_uri): OriginalUri) -> impl IntoResponse {
|
||||
info!("{original_uri} not found");
|
||||
(StatusCode::NOT_FOUND, "nothing to see here")
|
||||
}
|
||||
|
||||
// TODO Socials
|
||||
// - fotos
|
||||
// background gradient color
|
||||
@ -56,3 +67,8 @@ async fn main() {
|
||||
// TODO after release
|
||||
// OG tags
|
||||
// - projects page
|
||||
// TODO broken links
|
||||
// showcase/eggfetcher
|
||||
// broadcasts/
|
||||
// manifest.json
|
||||
//
|
||||
|
@ -1,6 +1,6 @@
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{OriginalUri, Path, RawQuery},
|
||||
extract::{OriginalUri, Path},
|
||||
http::StatusCode,
|
||||
};
|
||||
use tokio::try_join;
|
||||
@ -9,7 +9,7 @@ use tracing::debug;
|
||||
use crate::{
|
||||
blog_posts::{
|
||||
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||
tag_list::get_popular_blog_tags,
|
||||
tag_list::{get_popular_tags, get_posts_by_tag},
|
||||
},
|
||||
components::site_header::{HeaderProps, Link},
|
||||
filters,
|
||||
@ -21,10 +21,11 @@ use crate::{
|
||||
#[template(path = "blog_post_list.html")]
|
||||
pub struct PostListTemplate {
|
||||
pub title: String,
|
||||
pub og_title: String,
|
||||
pub segment: String,
|
||||
pub posts: Vec<ParseResult<BlogPostMetadata>>,
|
||||
pub tag: Option<String>,
|
||||
pub header_props: HeaderProps,
|
||||
pub blog_tags: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub featured_projects: Vec<ParseResult<ProjectMetadata>>,
|
||||
pub current_url: String,
|
||||
}
|
||||
@ -37,30 +38,17 @@ pub async fn render_blog_post_list(
|
||||
let tag = tag.map(|Path(tag)| tag);
|
||||
|
||||
let (blog_tags, featured_projects, mut post_list) = try_join!(
|
||||
get_popular_blog_tags(),
|
||||
get_popular_tags(Some("blog".to_string())),
|
||||
get_featured_projects(),
|
||||
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
|
||||
)?;
|
||||
|
||||
post_list.sort_by_key(|post| post.metadata.date);
|
||||
post_list.retain(|post| post.metadata.published);
|
||||
post_list.retain(|post| post.metadata.segments.contains(&"blog".to_string()));
|
||||
post_list.reverse();
|
||||
|
||||
let posts = match &tag {
|
||||
Some(tag) => post_list
|
||||
.into_iter()
|
||||
.filter(|post| {
|
||||
post.metadata
|
||||
.tags
|
||||
.iter()
|
||||
.map(|post_tag| post_tag.to_lowercase())
|
||||
.collect::<String>()
|
||||
.contains(&tag.to_lowercase())
|
||||
})
|
||||
.collect(),
|
||||
None => post_list,
|
||||
};
|
||||
|
||||
let posts = get_posts_by_tag(post_list, &tag);
|
||||
let header_props = match tag {
|
||||
Some(_) => HeaderProps::with_back_link(Link {
|
||||
href: "/blog".to_string(),
|
||||
@ -71,18 +59,19 @@ pub async fn render_blog_post_list(
|
||||
|
||||
debug!("uri:{:?}", original_uri);
|
||||
|
||||
let title = if let Some(tag) = &tag {
|
||||
format!("{tag} blog posts")
|
||||
let (title, og_title) = if let Some(tag) = &tag {
|
||||
(format!("#{tag}"), format!("{tag} blog posts"))
|
||||
} else {
|
||||
"Blog posts".to_string()
|
||||
("Blog posts".to_string(), "Blog posts".to_string())
|
||||
};
|
||||
|
||||
Ok(PostListTemplate {
|
||||
title,
|
||||
og_title,
|
||||
segment: "blog".to_string(),
|
||||
posts,
|
||||
tag,
|
||||
header_props,
|
||||
blog_tags,
|
||||
tags: blog_tags,
|
||||
featured_projects,
|
||||
current_url: original_uri.to_string(),
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
use askama::Template;
|
||||
use axum::extract::OriginalUri;
|
||||
use axum::{extract::Path, http::StatusCode};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
@ -17,15 +18,38 @@ pub struct BlogPostTemplate {
|
||||
pub body: String,
|
||||
pub date: DateTime<Utc>,
|
||||
pub tags: Vec<String>,
|
||||
pub segment: String,
|
||||
pub header_props: HeaderProps,
|
||||
pub slug: String,
|
||||
pub thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTemplate, StatusCode> {
|
||||
pub async fn render_blog_post(
|
||||
Path(post_id): Path<String>,
|
||||
OriginalUri(original_uri): OriginalUri,
|
||||
) -> Result<BlogPostTemplate, StatusCode> {
|
||||
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
|
||||
let parse_post = parse_post::<BlogPostMetadata>(&path, true);
|
||||
let parsed = parse_post.await?;
|
||||
let segment = if original_uri.to_string().starts_with("/blog") {
|
||||
"blog"
|
||||
} else if original_uri.to_string().starts_with("/broadcasts") {
|
||||
"broadcasts"
|
||||
} else {
|
||||
"blog"
|
||||
};
|
||||
|
||||
let header_props = match segment {
|
||||
"blog" => HeaderProps::with_back_link(Link {
|
||||
href: "/blog".to_string(),
|
||||
label: "All posts".to_string(),
|
||||
}),
|
||||
"broadcasts" => HeaderProps::with_back_link(Link {
|
||||
href: "/broadcasts".to_string(),
|
||||
label: "All broadcasts".to_string(),
|
||||
}),
|
||||
_ => HeaderProps::default(),
|
||||
};
|
||||
|
||||
Ok(BlogPostTemplate {
|
||||
title: parsed.metadata.title,
|
||||
@ -33,10 +57,8 @@ pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTem
|
||||
tags: parsed.metadata.tags,
|
||||
body: parsed.body,
|
||||
slug: parsed.slug,
|
||||
segment: segment.to_string(),
|
||||
thumbnail: parsed.metadata.thumbnail,
|
||||
header_props: HeaderProps::with_back_link(Link {
|
||||
href: "/blog".to_string(),
|
||||
label: "All posts".to_string(),
|
||||
}),
|
||||
header_props,
|
||||
})
|
||||
}
|
||||
|
66
src/pages/broadcast_list.rs
Normal file
66
src/pages/broadcast_list.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use axum::{
|
||||
extract::{OriginalUri, Path},
|
||||
http::StatusCode,
|
||||
};
|
||||
use tokio::try_join;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
blog_posts::{
|
||||
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||
tag_list::{get_popular_tags, get_posts_by_tag},
|
||||
},
|
||||
components::site_header::{HeaderProps, Link},
|
||||
post_utils::post_listing::get_post_list,
|
||||
projects::featured_projects::get_featured_projects,
|
||||
};
|
||||
|
||||
use super::blog_post_list::PostListTemplate;
|
||||
|
||||
pub async fn render_broadcast_post_list(
|
||||
tag: Option<Path<String>>,
|
||||
OriginalUri(original_uri): OriginalUri,
|
||||
) -> Result<PostListTemplate, StatusCode> {
|
||||
// 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 (popular_tags, featured_projects, mut post_list) = try_join!(
|
||||
get_popular_tags(Some("broadcasts".to_string())),
|
||||
get_featured_projects(),
|
||||
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
|
||||
)?;
|
||||
|
||||
post_list.sort_by_key(|post| post.metadata.date);
|
||||
post_list.retain(|post| post.metadata.published);
|
||||
post_list.retain(|post| post.metadata.segments.contains(&"broadcasts".to_string()));
|
||||
post_list.reverse();
|
||||
|
||||
let posts = get_posts_by_tag(post_list, &tag);
|
||||
|
||||
let header_props = match tag {
|
||||
Some(_) => HeaderProps::with_back_link(Link {
|
||||
href: "/broadcasts".to_string(),
|
||||
label: "All broadcasts".to_string(),
|
||||
}),
|
||||
None => HeaderProps::default(),
|
||||
};
|
||||
|
||||
debug!("uri:{:?}", original_uri);
|
||||
|
||||
let title = if let Some(tag) = &tag {
|
||||
format!("#{tag} broadcasts")
|
||||
} else {
|
||||
"Broadcasts".to_string()
|
||||
};
|
||||
|
||||
Ok(PostListTemplate {
|
||||
title: title.clone(),
|
||||
og_title: title,
|
||||
segment: "broadcasts".to_string(),
|
||||
posts,
|
||||
header_props,
|
||||
tags: popular_tags,
|
||||
featured_projects,
|
||||
current_url: original_uri.to_string(),
|
||||
})
|
||||
}
|
@ -5,7 +5,7 @@ use tokio::try_join;
|
||||
use crate::{
|
||||
blog_posts::{
|
||||
blog_post_model::BlogPostMetadata, featured_blog_posts::get_featured_blog_posts,
|
||||
tag_list::get_popular_blog_tags,
|
||||
tag_list::get_popular_tags,
|
||||
},
|
||||
components::site_header::HeaderProps,
|
||||
filters,
|
||||
@ -24,7 +24,7 @@ pub struct IndexTemplate {
|
||||
|
||||
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
||||
let (blog_tags, featured_blog_posts, featured_projects) = try_join!(
|
||||
get_popular_blog_tags(),
|
||||
get_popular_tags(Some("blog".to_string())),
|
||||
get_featured_blog_posts(),
|
||||
get_featured_projects()
|
||||
)?;
|
||||
|
@ -1,6 +1,8 @@
|
||||
pub mod admin;
|
||||
pub mod blog_post_list;
|
||||
pub mod blog_post_page;
|
||||
pub mod broadcast_list;
|
||||
pub mod contact;
|
||||
pub mod index;
|
||||
pub mod project_list;
|
||||
pub mod showcase;
|
||||
|
16
src/pages/showcase/egg_fetcher.rs
Normal file
16
src/pages/showcase/egg_fetcher.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use askama::Template;
|
||||
use axum::http::StatusCode;
|
||||
|
||||
use crate::components::site_header::HeaderProps;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "egg_fetcher_page.html")]
|
||||
pub struct EggFetcherShowcaseTemplate {
|
||||
header_props: HeaderProps,
|
||||
}
|
||||
|
||||
pub async fn render_egg_fetcher() -> Result<EggFetcherShowcaseTemplate, StatusCode> {
|
||||
Ok(EggFetcherShowcaseTemplate {
|
||||
header_props: HeaderProps::default(),
|
||||
})
|
||||
}
|
1
src/pages/showcase/mod.rs
Normal file
1
src/pages/showcase/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod egg_fetcher;
|
@ -2,8 +2,9 @@ use crate::{
|
||||
feed::render_rss_feed,
|
||||
pages::{
|
||||
admin::render_admin, blog_post_list::render_blog_post_list,
|
||||
blog_post_page::render_blog_post, contact::render_contact, index::render_index,
|
||||
project_list::render_projects_list,
|
||||
blog_post_page::render_blog_post, broadcast_list::render_broadcast_post_list,
|
||||
contact::render_contact, index::render_index, project_list::render_projects_list,
|
||||
showcase::egg_fetcher::render_egg_fetcher,
|
||||
},
|
||||
};
|
||||
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||
@ -16,8 +17,12 @@ pub fn get_router() -> Router {
|
||||
.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("/broadcasts", get(render_broadcast_post_list))
|
||||
.route("/broadcasts/tags/:tag", get(render_broadcast_post_list))
|
||||
.route("/broadcasts/:post_id", get(render_blog_post))
|
||||
.route("/contact", get(render_contact))
|
||||
.route("/showcase", get(render_projects_list))
|
||||
.route("/showcase/:project_slug", get(render_egg_fetcher))
|
||||
.route("/admin", get(render_admin))
|
||||
.route("/feed.xml", get(render_rss_feed))
|
||||
.layer(
|
||||
|
Reference in New Issue
Block a user