diff options
| author | 2023-06-21 23:46:20 +0100 | |
|---|---|---|
| committer | 2023-06-21 23:46:20 +0100 | |
| commit | 5f2a48905634e7874946425057dc904dcb6c7d16 (patch) | |
| tree | 89335193f5303c2d236440d6d88c3a1f00c0c4c9 /src | |
| parent | a508c3a7b49373d89a0522784a2a7e96b8d873e0 (diff) | |
| download | blossom-5f2a48905634e7874946425057dc904dcb6c7d16.tar.gz blossom-5f2a48905634e7874946425057dc904dcb6c7d16.tar.bz2 blossom-5f2a48905634e7874946425057dc904dcb6c7d16.zip | |
implement blog
Diffstat (limited to '')
| -rw-r--r-- | src/error.rs | 1 | ||||
| -rw-r--r-- | src/main.rs | 32 | ||||
| -rw-r--r-- | src/posts/mod.rs | 73 | 
3 files changed, 93 insertions, 13 deletions
| diff --git a/src/error.rs b/src/error.rs index 81cadd1..eb6f6e6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ pub enum BlossomError {      Chrono(Status, #[response(ignore)] chrono::ParseError),      Io(Status, #[response(ignore)] std::io::Error),      Deserialization(Status, #[response(ignore)] toml::de::Error), +    NotFound(Status),      NoMetadata(Status),      Unimplemented(Status),  } diff --git a/src/main.rs b/src/main.rs index dd2bd34..c08c2e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,36 @@ async fn home(clients: &State<Clients>) -> Template {      )  } +#[get("/blog")] +async fn blog() -> Template { +    let mut blogposts = posts::get_blogposts().await.unwrap_or_default(); +    let tags = posts::get_tags(&blogposts); +    for blogpost in &mut blogposts { +        blogpost.render().await; +    } +    let reverse = "reverse".to_owned(); +    Template::render( +        "blog", +        context! { +            reverse, +            blogposts, +            tags, +        }, +    ) +} + +#[get("/blog/<blogpost>")] +async fn blogpost(blogpost: &str) -> Result<Template> { +    let mut blogpost = posts::get_blogpost(blogpost).await?; +    blogpost.render().await?; +    Ok(Template::render( +        "blogpost", +        context! { +            blogpost, +        }, +    )) +} +  #[get("/contact")]  async fn contact() -> Template {      Template::render("contact", context! {}) @@ -90,7 +120,7 @@ async fn main() -> std::result::Result<(), rocket::Error> {          .attach(Template::custom(|engines| {              engines.tera.autoescape_on(vec![]);          })) -        .mount("/", routes![home, contact, plants]) +        .mount("/", routes![home, contact, blog, blogpost, plants])          .register("/", catchers![catcher])          .mount("/", FileServer::from(relative!("static")))          .launch() 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<D: Content> {      updated_at: Option<DateTime<Utc>>,      tags: Vec<String>,      post_type: PostType, +    render: Option<String>,      data: D,  } -impl<D: Content> Serialize for Post<D> { +impl<D: Content + Serialize> 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", 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<D: Content> Serialize for Post<D> {          )?;          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<D: Content> Serialize for Post<D> {  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(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<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.unwrap(); +            return Ok(blogpost); +        } +    } +    Err(BlossomError::NotFound(Status::new(404))) +} + +pub fn get_tags<D: Content>(posts: &Vec<Post<D>>) -> HashSet<String> { +    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<String>;  } -#[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<Article> {              updated_at,              tags: metadata.tags,              post_type: PostType::Article, +            render: None,              data: article,          })      } | 
