diff options
| author | 2023-06-21 19:32:30 +0100 | |
|---|---|---|
| committer | 2023-06-21 19:32:30 +0100 | |
| commit | 14db008af73d25cc92ea6c0b1f3b1e0ef67920d1 (patch) | |
| tree | be9eecf46dd7dc01686e26b88e27f98d8e9c7fdd | |
| parent | e0cd7838c350cb9814d8285cad04648f32b3db59 (diff) | |
| download | blossom-14db008af73d25cc92ea6c0b1f3b1e0ef67920d1.tar.gz blossom-14db008af73d25cc92ea6c0b1f3b1e0ef67920d1.tar.bz2 blossom-14db008af73d25cc92ea6c0b1f3b1e0ef67920d1.zip | |
implement foundation for posts
Diffstat (limited to '')
| -rw-r--r-- | Cargo.lock | 140 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | articles/rot/rot.md | 6 | ||||
| -rw-r--r-- | src/error.rs | 22 | ||||
| -rw-r--r-- | src/main.rs | 13 | ||||
| -rw-r--r-- | src/posts/mod.rs | 141 | 
6 files changed, 279 insertions, 47 deletions
| @@ -80,18 +80,18 @@ checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]]  name = "async-trait" -version = "0.1.64" +version = "0.1.68"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 2.0.18",  ]  [[package]] @@ -381,7 +381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"  dependencies = [   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -417,7 +417,7 @@ dependencies = [   "proc-macro2",   "quote",   "scratch", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -434,7 +434,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -445,7 +445,7 @@ checksum = "dcdbcee2d9941369faba772587a565f4f534e42cb8d17e5295871de730163b2b"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -484,7 +484,7 @@ dependencies = [   "proc-macro2",   "proc-macro2-diagnostics",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -555,7 +555,7 @@ dependencies = [   "atomic",   "pear",   "serde", - "toml", + "toml 0.5.11",   "uncased",   "version_check",  ] @@ -703,7 +703,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -1180,6 +1180,15 @@ dependencies = [  ]  [[package]] +name = "markdown" +version = "1.0.0-alpha.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bd98c3b68451b0390a289c58c856adb4e2b50cc40507ce2a105d5b00eafc80" +dependencies = [ + "unicode-id", +] + +[[package]]  name = "mastodon-async"  version = "1.1.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1469,7 +1478,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -1561,7 +1570,7 @@ dependencies = [   "proc-macro2",   "proc-macro2-diagnostics",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -1600,7 +1609,7 @@ dependencies = [   "pest_meta",   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -1709,9 +1718,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"  [[package]]  name = "proc-macro2" -version = "1.0.51" +version = "1.0.60"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"  dependencies = [   "unicode-ident",  ] @@ -1724,7 +1733,7 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",   "version_check",   "yansi",  ] @@ -1742,9 +1751,9 @@ dependencies = [  [[package]]  name = "quote" -version = "1.0.23" +version = "1.0.28"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"  dependencies = [   "proc-macro2",  ] @@ -1805,7 +1814,7 @@ checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -1934,7 +1943,7 @@ dependencies = [   "proc-macro2",   "quote",   "rocket_http", - "syn", + "syn 1.0.107",   "unicode-xid",  ] @@ -2075,7 +2084,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -2110,6 +2119,15 @@ dependencies = [  ]  [[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + +[[package]]  name = "serde_urlencoded"  version = "0.7.1"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2160,9 +2178,11 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"  name = "site"  version = "0.1.0"  dependencies = [ + "async-trait",   "chrono",   "chrono-humanize",   "listenbrainz", + "markdown",   "mastodon-async",   "reqwest",   "rocket", @@ -2171,6 +2191,8 @@ dependencies = [   "serde_json",   "time 0.3.22",   "tokio", + "tokio-stream", + "toml 0.7.4",  ]  [[package]] @@ -2279,6 +2301,17 @@ dependencies = [  ]  [[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]]  name = "tap-reader"  version = "1.0.1"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2346,7 +2379,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -2440,7 +2473,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -2455,9 +2488,9 @@ dependencies = [  [[package]]  name = "tokio-stream" -version = "0.1.11" +version = "0.1.14"  source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"  dependencies = [   "futures-core",   "pin-project-lite", @@ -2488,6 +2521,40 @@ dependencies = [  ]  [[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]]  name = "tower-service"  version = "0.3.2"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2513,7 +2580,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",  ]  [[package]] @@ -2658,6 +2725,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"  [[package]] +name = "unicode-id" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" + +[[package]]  name = "unicode-ident"  version = "1.0.6"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2800,7 +2873,7 @@ dependencies = [   "once_cell",   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",   "wasm-bindgen-shared",  ] @@ -2834,7 +2907,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"  dependencies = [   "proc-macro2",   "quote", - "syn", + "syn 1.0.107",   "wasm-bindgen-backend",   "wasm-bindgen-shared",  ] @@ -3036,6 +3109,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"  [[package]] +name = "winnow" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" +dependencies = [ + "memchr", +] + +[[package]]  name = "winreg"  version = "0.10.1"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -19,3 +19,7 @@ chrono = "0.4.23"  serde_json = "1.0.97"  time = "0.3.22"  chrono-humanize = "0.2.2" +markdown = "1.0.0-alpha.10" +async-trait = "0.1.68" +toml = "0.7.4" +tokio-stream = { version = "0.1.14", features = ["fs"] } diff --git a/articles/rot/rot.md b/articles/rot/rot.md index 38a04c2..5af2a8d 100644 --- a/articles/rot/rot.md +++ b/articles/rot/rot.md @@ -1,4 +1,8 @@ -# rot - 2022-04-27  ++++ +title = "rot" +created_at = "2022-04-27T09:48:30+0100" +tags = ["thoughts"] ++++  i want to write something about rot. why not? i think it's become my favourite word over the last few months. it's so versatile. diff --git a/src/error.rs b/src/error.rs index 890a148..81cadd1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,10 @@ pub enum BlossomError {      Reqwest(Status, #[response(ignore)] reqwest::Error),      ListenBrainz(Status, #[response(ignore)] listenbrainz::Error),      Skinnyverse(Status, #[response(ignore)] mastodon_async::Error), +    Chrono(Status, #[response(ignore)] chrono::ParseError), +    Io(Status, #[response(ignore)] std::io::Error), +    Deserialization(Status, #[response(ignore)] toml::de::Error), +    NoMetadata(Status),      Unimplemented(Status),  } @@ -25,3 +29,21 @@ impl From<mastodon_async::Error> for BlossomError {          BlossomError::Skinnyverse(Status::new(500), e)      }  } + +impl From<chrono::ParseError> for BlossomError { +    fn from(e: chrono::ParseError) -> Self { +        BlossomError::Chrono(Status::new(500), e) +    } +} + +impl From<std::io::Error> for BlossomError { +    fn from(e: std::io::Error) -> Self { +        BlossomError::Io(Status::new(500), e) +    } +} + +impl From<toml::de::Error> for BlossomError { +    fn from(e: toml::de::Error) -> Self { +        BlossomError::Deserialization(Status::new(500), e) +    } +} diff --git a/src/main.rs b/src/main.rs index 57d4973..dd2bd34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,20 +26,23 @@ extern crate rocket;  #[get("/")]  async fn home(clients: &State<Clients>) -> Template { -    let (live, listenbrainz, skweets) = tokio::join!( +    let (live, listenbrainz, skweets, blogposts) = tokio::join!(          live::get_live_status(&clients.reqwest),          scrobbles::get_now_playing(&clients.listenbrainz), -        skweets::get_recents(&clients.skinnyverse) +        skweets::get_recents(&clients.skinnyverse), +        posts::get_blogposts()      );      let is_live = live.unwrap_or_default().online;      let listenbrainz = listenbrainz.unwrap_or_default();      let skweets = skweets.unwrap_or_default(); +    let blogposts = blogposts.unwrap_or_default();      Template::render(          "home",          context! { -        is_live, -        listenbrainz, -        skweets, +            is_live, +            listenbrainz, +            skweets, +            blogposts,          },      )  } diff --git a/src/posts/mod.rs b/src/posts/mod.rs index b0c3749..b188ee0 100644 --- a/src/posts/mod.rs +++ b/src/posts/mod.rs @@ -1,6 +1,14 @@ +use async_trait::async_trait;  use chrono::{DateTime, Utc}; -use std::path::Path; +use markdown::{mdast::Node, Constructs, 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, @@ -12,22 +20,131 @@ enum TextFormat {      Html,  } -pub struct Post<T: Content> { -    id: i64, +#[derive(Debug)] +pub struct Post<D: Content> { +    // id: i64, +    subject: Option<String>,      created_at: DateTime<Utc>,      updated_at: Option<DateTime<Utc>>, -    post_type: PostType, -    media: Option<Vec<Box<Path>>>, -    content: Option<T>,      tags: Vec<String>, +    post_type: PostType, +    data: D, +} + +impl<D: Content> 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)?; +        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.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(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); +        let blogpost: Article = Article { +            path: file_path.to_str().unwrap_or_default().to_owned(), +        }; +        let blogpost = Post::try_from(blogpost).await.unwrap(); +        println!("{:?}", blogpost); +        blogposts.push(blogpost); +    } +    Ok(blogposts) +} + +#[async_trait]  pub trait Content { -    fn render(&self) -> String; +    async fn render(&self) -> Result<String>;  } -// impl<T> Post<T> { -//     // renders as internal html (must sanitize) -//     fn new(type: PostType, ) -> Post<T> { -//     } -// } +#[derive(Debug)] +pub struct Article { +    path: 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?; +        Ok(markdown::to_html(&buf)) +    } +} + +#[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 { +            subject: Some(metadata.title), +            created_at: metadata.created_at.parse::<DateTime<Utc>>()?, +            updated_at, +            tags: metadata.tags, +            post_type: PostType::Article, +            data: article, +        }) +    } +} | 
