use std::path::Path; use markdown::{mdast::Node, CompileOptions, Constructs, Options, ParseOptions}; use serde::Deserialize; use tokio::{fs, io::AsyncReadExt}; use chrono::{DateTime, Utc}; use crate::{ article::Article, error::BlossomError, posts::{Post, PostType}, Result, }; static DIRECTORY: &str = "./blog"; #[derive(Clone)] pub struct Blogpost { file_name: String, title: String, published_at: DateTime, updated_at: Option>, tags: Vec, content: String, } #[derive(Deserialize)] pub struct BlogpostMetadata { title: String, published_at: String, updated_at: Option, tags: Vec, } impl Article for Blogpost { type Metadata = BlogpostMetadata; type Article = Blogpost; fn directory() -> &'static str { DIRECTORY } fn new(file_name: String, metadata: Self::Metadata, content: String) -> Result { let updated_at = if let Some(updated_at) = metadata.updated_at { Some(updated_at.parse::>()?) } else { None }; Ok(Blogpost { file_name, title: metadata.title, published_at: metadata.published_at.parse::>()?, updated_at, tags: metadata.tags, content, }) } } impl Post for Blogpost { fn id(&self) -> &str { &self.file_name } fn subject(&self) -> Option<&str> { Some(&self.title) } fn published_at(&self) -> &DateTime { &self.published_at } fn updated_at(&self) -> Option<&DateTime> { self.updated_at.as_ref() } fn tags(&self) -> Vec<&str> { self.tags.iter().map(|s| &**s).collect() } fn lang(&self) -> &str { "en" } fn post_type(&self) -> PostType { PostType::Article } fn content(&self) -> &str { &self.content } } impl Blogpost { pub fn file_name(&self) -> &str { &self.file_name } }