Logic for recommended articles

This commit is contained in:
Michal Vanko 2024-11-24 14:56:54 +01:00
parent fec60900f5
commit f7eb6cc95d
2 changed files with 65 additions and 17 deletions

View File

@ -3,7 +3,9 @@ use axum::extract::OriginalUri;
use axum::{extract::Path, http::StatusCode}; use axum::{extract::Path, http::StatusCode};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::blog_posts::blog_post_model::BLOG_POST_PATH; use crate::blog_posts::blog_post_model::{Segment, BLOG_POST_PATH};
use crate::post_utils::post_listing::get_post_list;
use crate::post_utils::post_parser::ParseResult;
use crate::{ use crate::{
blog_posts::blog_post_model::BlogPostMetadata, components::site_header::Link, filters, blog_posts::blog_post_model::BlogPostMetadata, components::site_header::Link, filters,
post_utils::post_parser::parse_post, post_utils::post_parser::parse_post,
@ -18,10 +20,11 @@ pub struct BlogPostTemplate {
pub body: String, pub body: String,
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
pub tags: Vec<String>, pub tags: Vec<String>,
pub segment: String, pub segment: Segment,
pub header_props: HeaderProps, pub header_props: HeaderProps,
pub slug: String, pub slug: String,
pub thumbnail: Option<String>, pub thumbnail: Option<String>,
pub recommended_posts: Vec<ParseResult<BlogPostMetadata>>,
} }
pub async fn render_blog_post( pub async fn render_blog_post(
@ -29,22 +32,27 @@ pub async fn render_blog_post(
OriginalUri(original_uri): OriginalUri, OriginalUri(original_uri): OriginalUri,
) -> Result<BlogPostTemplate, StatusCode> { ) -> Result<BlogPostTemplate, StatusCode> {
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id); let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
let parse_post = parse_post::<BlogPostMetadata>(&path); let post = parse_post::<BlogPostMetadata>(&path).await?;
let parsed = parse_post.await?;
let segment = if original_uri.to_string().starts_with("/blog") { let segment = if original_uri.to_string().starts_with("/blog") {
"blog" Segment::Blog
} else if original_uri.to_string().starts_with("/broadcasts") { } else if original_uri.to_string().starts_with("/broadcasts") {
"broadcasts" Segment::Broadcasts
} else { } else {
"blog" Segment::Blog
}; };
let mut recommended_posts = get_recommended_posts(&segment, &post.metadata.tags).await?;
recommended_posts.retain(|post| post.slug != post_id);
recommended_posts.sort_by_key(|post| post.slug.to_string());
recommended_posts.reverse();
let recommended_posts = recommended_posts.into_iter().take(2).collect::<Vec<_>>();
let header_props = match segment { let header_props = match segment {
"blog" => HeaderProps::with_back_link(Link { Segment::Blog => HeaderProps::with_back_link(Link {
href: "/blog".to_string(), href: "/blog".to_string(),
label: "All posts".to_string(), label: "All posts".to_string(),
}), }),
"broadcasts" => HeaderProps::with_back_link(Link { Segment::Broadcasts => HeaderProps::with_back_link(Link {
href: "/broadcasts".to_string(), href: "/broadcasts".to_string(),
label: "All broadcasts".to_string(), label: "All broadcasts".to_string(),
}), }),
@ -52,13 +60,40 @@ pub async fn render_blog_post(
}; };
Ok(BlogPostTemplate { Ok(BlogPostTemplate {
title: parsed.metadata.title, title: post.metadata.title,
date: parsed.metadata.date, date: post.metadata.date,
tags: parsed.metadata.tags, tags: post.metadata.tags,
body: parsed.body, body: post.body,
slug: parsed.slug, slug: post.slug,
segment: segment.to_string(), segment,
thumbnail: parsed.metadata.thumbnail, thumbnail: post.metadata.thumbnail,
header_props, header_props,
recommended_posts,
}) })
} }
async fn get_recommended_posts(
segment: &Segment,
tags: &[String],
) -> Result<Vec<ParseResult<BlogPostMetadata>>, StatusCode> {
let posts = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
let recommended_posts = posts
.into_iter()
.filter(|post| {
let is_same_segment = post
.metadata
.segments
.iter()
.any(|post_segment| post_segment == segment);
let has_same_tags = post
.metadata
.tags
.iter()
.any(|post_tag| tags.contains(post_tag));
is_same_segment && has_same_tags
})
.collect::<Vec<_>>();
Ok(recommended_posts)
}

View File

@ -33,7 +33,20 @@
</article> </article>
<!-- TODO: Next recommendations for reading --> <!-- TODO: Next recommendations for reading -->
<!-- TODO: Bact to all posts --> <!-- TODO: Back to all posts -->
{# footer #} {# footer #}
<footer>
<hr class="border-slate-300 m-5 md:my-8">
<ul class="mx-5">
{% for post in recommended_posts %}
<li>
{% include "components/blog_post_preview.html" %}
<!-- TODO hidden block media -->
<hr class="border-slate-300 my-5 md:my-8 hidden lg:block">
</li>
{% endfor %}
</ul>
</footer>
{% endblock %} {% endblock %}