diff options
Diffstat (limited to '')
-rw-r--r-- | src/posts/mod.rs | 228 |
1 files changed, 0 insertions, 228 deletions
diff --git a/src/posts/mod.rs b/src/posts/mod.rs deleted file mode 100644 index c75dd53..0000000 --- a/src/posts/mod.rs +++ /dev/null @@ -1,228 +0,0 @@ -mod article; -mod note; -pub mod syndication; - -use std::collections::HashSet; - -use async_trait::async_trait; -use chrono::{DateTime, Utc}; -use markdown::{mdast::Node, CompileOptions, Constructs, Options, ParseOptions}; -use rocket::http::Status; -use serde::{ser::SerializeStruct, Deserialize, Serialize}; -use tokio::fs; -use tokio::io::AsyncReadExt; - -use crate::{error::BlossomError, Result}; - -#[derive(Serialize, Debug)] -enum PostType { - Article, - Note, -} - -#[derive(Debug)] -pub struct Post<D: Content> { - id: String, - subject: Option<String>, - created_at: DateTime<Utc>, - updated_at: Option<DateTime<Utc>>, - tags: Vec<String>, - post_type: PostType, - render: Option<String>, - data: D, -} - -impl<D: Content> Post<D> { - pub async fn render(&mut self) -> Result<()> { - self.render = Some(self.data.render().await?); - Ok(()) - } -} - -impl<D: Content + Serialize> Serialize for Post<D> { - fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - 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( - "updated_at", - &self.updated_at.and_then(|time| Some(time.to_string())), - )?; - 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() - } -} - -pub async fn get_blogposts() -> Result<Vec<Post<Article>>> { - let mut blogposts: Vec<Post<Article>> = Vec::new(); - 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].to_owned(); - let blogpost: Article = Article { - path: file.path().to_str().unwrap_or_default().to_owned(), - name, - }; - let blogpost = Post::try_from(blogpost).await.unwrap(); - blogposts.push(blogpost); - } - blogposts.sort_by_key(|post| post.created_at); - blogposts.reverse(); - Ok(blogposts) -} - -pub async fn get_blogpost(post_name: &str) -> Result<Post<Article>> { - 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?; - return Ok(blogpost); - } - } - Err(BlossomError::NotFound(Status::new(404))) -} - -pub fn get_tags<D: Content>(posts: &Vec<Post<D>>) -> Vec<&String> { - let mut tags = posts - .into_iter() - .fold(HashSet::new(), |mut acc, post| { - let tags = &post.tags; - for tag in tags { - acc.insert(tag); - } - acc - }) - .into_iter() - .collect::<Vec<_>>(); - tags.sort(); - tags -} - -pub fn filter_by_tags<'p, D: Content>( - posts: Vec<Post<D>>, - filter_tags: &HashSet<String>, -) -> Vec<Post<D>> { - posts - .into_iter() - .filter(|post| { - for tag in &post.tags { - match filter_tags.contains(tag) { - true => return true, - false => continue, - } - } - false - }) - .collect() -} - -#[async_trait] -pub trait Content { - async fn render(&self) -> Result<String>; -} - -#[derive(Serialize, Debug)] -pub struct Article { - path: String, - name: String, -} - -impl Article { - async fn tree(&self) -> Result<Node> { - let mut file = fs::File::open(&self.path).await?; - let mut buf = String::new(); - file.read_to_string(&mut buf).await?; - Ok(markdown::to_mdast( - &buf, - &ParseOptions { - constructs: Constructs { - frontmatter: true, - ..Constructs::default() - }, - ..ParseOptions::default() - }, - ) - .unwrap()) - } - - async fn frontmatter(&self) -> Result<String> { - let tree = self.tree().await?; - let children = tree.children(); - if let Some(children) = children { - if let Some(toml) = children.into_iter().find_map(|el| match el { - Node::Toml(toml) => Some(toml.value.to_owned()), - _ => None, - }) { - return Ok(toml); - }; - } - Err(BlossomError::NoMetadata(Status::new(500))) - } -} - -#[async_trait] -impl Content for Article { - async fn render(&self) -> Result<String> { - let mut file = fs::File::open(&self.path).await?; - let mut buf = String::new(); - file.read_to_string(&mut buf).await?; - let options = Options { - parse: ParseOptions { - constructs: Constructs { - frontmatter: true, - ..Constructs::gfm() - }, - ..ParseOptions::default() - }, - compile: CompileOptions { - gfm_task_list_item_checkable: true, - allow_dangerous_html: true, - ..CompileOptions::default() - }, - }; - Ok(markdown::to_html_with_options(&buf, &options).unwrap()) - } -} - -#[derive(Deserialize)] -struct ArticleMetadata { - title: String, - created_at: String, - updated_at: Option<String>, - tags: Vec<String>, -} - -impl Post<Article> { - async fn try_from(article: Article) -> Result<Post<Article>> { - let metadata = article.frontmatter().await?; - let metadata: ArticleMetadata = toml::from_str(&metadata)?; - let updated_at = if let Some(updated_at) = metadata.updated_at { - Some(updated_at.parse::<DateTime<Utc>>()?) - } else { - None - }; - - Ok(Post { - id: article.name.to_owned(), - subject: Some(metadata.title), - created_at: metadata.created_at.parse::<DateTime<Utc>>()?, - updated_at, - tags: metadata.tags, - post_type: PostType::Article, - render: None, - data: article, - }) - } -} |