diff options
Diffstat (limited to 'src/posts')
| -rw-r--r-- | src/posts/article.rs | 0 | ||||
| -rw-r--r-- | src/posts/mod.rs | 228 | ||||
| -rw-r--r-- | src/posts/syndication.rs | 96 | 
3 files changed, 0 insertions, 324 deletions
diff --git a/src/posts/article.rs b/src/posts/article.rs deleted file mode 100644 index e69de29..0000000 --- a/src/posts/article.rs +++ /dev/null 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, -        }) -    } -} diff --git a/src/posts/syndication.rs b/src/posts/syndication.rs deleted file mode 100644 index f6f0b17..0000000 --- a/src/posts/syndication.rs +++ /dev/null @@ -1,96 +0,0 @@ -use atom_syndication::{Category, Content, Entry, Feed, Generator, Link, Person, Text, TextType}; - -use super::{Article, Post}; - -pub async fn atom(posts: Vec<Post<Article>>) -> Feed { -    let me = Person { -        name: "cel".into(), -        email: Some("cel@blos.sm".into()), -        uri: Some("https://blos.sm".into()), -    }; -    let mut authors = Vec::new(); -    authors.push(me); -    let link = Link { -        href: "https://blos.sm/feed".into(), -        rel: "self".into(), -        hreflang: Some("en".into()), -        mime_type: Some("application/atom+xml".into()), -        title: Some("atom feed".into()), -        length: None, -    }; -    let mut links = Vec::new(); -    links.push(link); -    let mut feed = Feed { -        title: Text { -            value: "cel's site".into(), -            base: None, -            lang: Some("en".into()), -            r#type: TextType::Text, -        }, -        id: "https://blos.sm".into(), -        updated: posts[0].created_at.into(), -        authors: authors.clone(), -        categories: Vec::new(), -        contributors: authors.clone(), -        generator: Some(Generator { -            value: "blos.sm".into(), -            uri: Some("https://bunny.garden/cel/blos.sm".into()), -            version: None, -        }), -        icon: Some("/icon.png".into()), -        links: links.clone(), -        logo: Some("/logo.png".into()), -        rights: None, -        subtitle: None, -        entries: Vec::new(), -        base: Some("https://blos.sm".into()), -        lang: Some("en".into()), -        ..Default::default() -    }; -    for mut post in posts { -        post.render().await.unwrap_or_default(); -        let mut id = String::from("https://blos.sm/blog/"); -        id.push_str(&post.data.name); -        let categories = post -            .tags -            .into_iter() -            .map(|tag| Category { -                term: tag.clone(), -                scheme: None, -                label: Some(tag.clone()), -            }) -            .collect(); -        let entry = Entry { -            title: Text { -                value: post.subject.unwrap_or_default(), -                base: None, -                lang: Some("en".into()), -                r#type: TextType::Text, -            }, -            id: id.clone(), -            updated: if let Some(updated_at) = post.updated_at { -                updated_at.into() -            } else { -                post.created_at.into() -            }, -            authors: authors.clone(), -            categories, -            contributors: authors.clone(), -            links: links.clone(), -            published: Some(post.created_at.into()), -            rights: None, -            source: None, -            summary: None, -            content: Some(Content { -                base: None, -                lang: Some("en".into()), -                value: post.render, -                src: Some(id), -                content_type: Some("html".to_string()), -            }), -            ..Default::default() -        }; -        feed.entries.push(entry); -    } -    feed -}  | 
