From 5f2a48905634e7874946425057dc904dcb6c7d16 Mon Sep 17 00:00:00 2001 From: cel 🌸 Date: Wed, 21 Jun 2023 23:46:20 +0100 Subject: implement blog --- src/posts/mod.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 12 deletions(-) (limited to 'src/posts') diff --git a/src/posts/mod.rs b/src/posts/mod.rs index b188ee0..be1e1a9 100644 --- a/src/posts/mod.rs +++ b/src/posts/mod.rs @@ -1,6 +1,8 @@ +use std::collections::HashSet; + use async_trait::async_trait; use chrono::{DateTime, Utc}; -use markdown::{mdast::Node, Constructs, ParseOptions}; +use markdown::{mdast::Node, Constructs, Options, ParseOptions}; use rocket::http::Status; use serde::{ser::SerializeStruct, Deserialize, Serialize}; use tokio::fs; @@ -28,15 +30,23 @@ pub struct Post { updated_at: Option>, tags: Vec, post_type: PostType, + render: Option, data: D, } -impl Serialize for Post { +impl Post { + pub async fn render(&mut self) -> Result<()> { + self.render = Some(self.data.render().await?); + Ok(()) + } +} + +impl Serialize for Post { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { - let mut state = serializer.serialize_struct("Post", 5)?; + let mut state = serializer.serialize_struct("Post", 7)?; state.serialize_field("subject", &self.subject)?; state.serialize_field("created_at", &self.created_at.to_string())?; state.serialize_field( @@ -45,6 +55,8 @@ impl Serialize for Post { )?; state.serialize_field("tags", &self.tags)?; state.serialize_field("post_type", &self.post_type)?; + state.serialize_field("render", &self.render)?; + state.serialize_field("data", &self.data)?; state.end() } } @@ -52,29 +64,55 @@ impl Serialize for Post { pub async fn get_blogposts() -> Result>> { let mut blogposts: Vec> = Vec::new(); let mut articles_dir = fs::read_dir("./articles").await?; - while let Some(dir) = articles_dir.next_entry().await? { - let mut file_path = dir.path(); - file_path.push(dir.file_name()); - let file_path = file_path.with_extension("md"); - println!("{:?}", file_path); + while let Some(file) = articles_dir.next_entry().await? { + let name = file.file_name(); + let name = name.to_str().unwrap_or_default()[..name.len() - 3].to_owned(); let blogpost: Article = Article { - path: file_path.to_str().unwrap_or_default().to_owned(), + path: file.path().to_str().unwrap_or_default().to_owned(), + name, }; let blogpost = Post::try_from(blogpost).await.unwrap(); - println!("{:?}", blogpost); blogposts.push(blogpost); } Ok(blogposts) } +pub async fn get_blogpost(post_name: &str) -> Result> { + let mut articles_dir = fs::read_dir("./articles").await?; + while let Some(file) = articles_dir.next_entry().await? { + let name = file.file_name(); + let name = &name.to_str().unwrap_or_default()[..name.len() - 3]; + if name == post_name { + let blogpost = Article { + path: file.path().to_str().unwrap_or_default().to_owned(), + name: name.to_owned(), + }; + let blogpost = Post::try_from(blogpost).await.unwrap(); + return Ok(blogpost); + } + } + Err(BlossomError::NotFound(Status::new(404))) +} + +pub fn get_tags(posts: &Vec>) -> HashSet { + posts.into_iter().fold(HashSet::new(), |mut acc, post| { + let tags = &post.tags; + for tag in tags { + acc.insert(tag.to_owned()); + } + acc + }) +} + #[async_trait] pub trait Content { async fn render(&self) -> Result; } -#[derive(Debug)] +#[derive(Serialize, Debug)] pub struct Article { path: String, + name: String, } impl Article { @@ -116,7 +154,17 @@ impl Content for Article { let mut file = fs::File::open(&self.path).await?; let mut buf = String::new(); file.read_to_string(&mut buf).await?; - Ok(markdown::to_html(&buf)) + let options = Options { + parse: ParseOptions { + constructs: Constructs { + frontmatter: true, + ..Constructs::default() + }, + ..ParseOptions::default() + }, + ..Options::default() + }; + Ok(markdown::to_html_with_options(&buf, &options).unwrap()) } } @@ -144,6 +192,7 @@ impl Post
{ updated_at, tags: metadata.tags, post_type: PostType::Article, + render: None, data: article, }) } -- cgit