allow passing extra params to blog engine

This commit is contained in:
slonkazoid 2024-12-16 14:35:43 +03:00
parent e5cc685b0a
commit 9eddbdb881
Signed by: slonk
SSH key fingerprint: SHA256:tbZfJX4IOvZ0LGWOWu5Ijo8jfMPi78TU7x1VoEeCIjM
6 changed files with 82 additions and 18 deletions

20
Cargo.lock generated
View file

@ -318,6 +318,7 @@ dependencies = [
"rss", "rss",
"scc", "scc",
"serde", "serde",
"serde-value",
"serde_json", "serde_json",
"syntect", "syntect",
"thiserror", "thiserror",
@ -1541,6 +1542,15 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -1930,6 +1940,16 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.206" version = "1.0.206"

View file

@ -47,6 +47,7 @@ notify-debouncer-full = { version = "0.3.1", default-features = false }
rss = "2.0.7" rss = "2.0.7"
scc = { version = "2.1.0", features = ["serde"] } scc = { version = "2.1.0", features = ["serde"] }
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde-value = "0.7.0"
serde_json = { version = "1.0.124", features = ["preserve_order"] } serde_json = { version = "1.0.124", features = ["preserve_order"] }
syntect = "5.2.0" syntect = "5.2.0"
thiserror = "1.0.58" thiserror = "1.0.58"

View file

@ -13,6 +13,7 @@ use include_dir::{include_dir, Dir};
use rss::{Category, ChannelBuilder, ItemBuilder}; use rss::{Category, ChannelBuilder, ItemBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Map; use serde_json::Map;
use serde_value::Value;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tower::service_fn; use tower::service_fn;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -78,6 +79,8 @@ struct QueryParams {
tag: Option<String>, tag: Option<String>,
#[serde(rename = "n")] #[serde(rename = "n")]
num_posts: Option<usize>, num_posts: Option<usize>,
#[serde(flatten)]
other: HashMap<String, Value>,
} }
fn collect_tags(posts: &Vec<PostMetadata>) -> Map<String, serde_json::Value> { fn collect_tags(posts: &Vec<PostMetadata>) -> Map<String, serde_json::Value> {
@ -130,7 +133,11 @@ async fn index(
Query(query): Query<QueryParams>, Query(query): Query<QueryParams>,
) -> AppResult<impl IntoResponse> { ) -> AppResult<impl IntoResponse> {
let posts = posts let posts = posts
.get_max_n_post_metadata_with_optional_tag_sorted(query.num_posts, query.tag.as_deref()) .get_max_n_post_metadata_with_optional_tag_sorted(
query.num_posts,
query.tag.as_deref(),
&query.other,
)
.await?; .await?;
let tags = collect_tags(&posts); let tags = collect_tags(&posts);
@ -160,7 +167,11 @@ async fn all_posts(
Query(query): Query<QueryParams>, Query(query): Query<QueryParams>,
) -> AppResult<Json<Vec<PostMetadata>>> { ) -> AppResult<Json<Vec<PostMetadata>>> {
let posts = posts let posts = posts
.get_max_n_post_metadata_with_optional_tag_sorted(query.num_posts, query.tag.as_deref()) .get_max_n_post_metadata_with_optional_tag_sorted(
query.num_posts,
query.tag.as_deref(),
&query.other,
)
.await?; .await?;
Ok(Json(posts)) Ok(Json(posts))
@ -181,6 +192,7 @@ async fn rss(
.as_ref() .as_ref()
.and(Some(Filter::Tags(query.tag.as_deref().as_slice()))) .and(Some(Filter::Tags(query.tag.as_deref().as_slice())))
.as_slice(), .as_slice(),
&query.other,
) )
.await?; .await?;
@ -234,8 +246,9 @@ async fn post(
.. ..
}): State<AppState>, }): State<AppState>,
Path(name): Path<String>, Path(name): Path<String>,
Query(query): Query<QueryParams>,
) -> AppResult<impl IntoResponse> { ) -> AppResult<impl IntoResponse> {
match posts.get_post(&name).await? { match posts.get_post(&name, &query.other).await? {
ReturnedPost::Rendered(ref meta, rendered, rendered_in) => { ReturnedPost::Rendered(ref meta, rendered, rendered_in) => {
let joined_tags = meta.tags.join(", "); let joined_tags = meta.tags.join(", ");
@ -256,7 +269,7 @@ async fn post(
joined_tags, joined_tags,
style: &config.style, style: &config.style,
raw_name: if config.markdown_access { raw_name: if config.markdown_access {
raw_name = posts.get_raw(&meta.name).await?; raw_name = posts.as_raw(&meta.name).await?;
raw_name.as_deref() raw_name.as_deref()
} else { } else {
None None

View file

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::path::Path; use std::path::Path;
use std::process::Stdio; use std::process::Stdio;
use std::sync::Arc; use std::sync::Arc;
@ -6,6 +7,7 @@ use axum::async_trait;
use axum::http::HeaderValue; use axum::http::HeaderValue;
use futures::stream::FuturesUnordered; use futures::stream::FuturesUnordered;
use futures::StreamExt; use futures::StreamExt;
use serde_value::Value;
use tokio::fs::OpenOptions; use tokio::fs::OpenOptions;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
use tokio::time::Instant; use tokio::time::Instant;
@ -32,6 +34,7 @@ impl PostManager for Blag {
async fn get_all_posts( async fn get_all_posts(
&self, &self,
filters: &[Filter<'_>], filters: &[Filter<'_>],
query: &HashMap<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> { ) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> {
let mut set = FuturesUnordered::new(); let mut set = FuturesUnordered::new();
let mut meow = Vec::new(); let mut meow = Vec::new();
@ -57,7 +60,9 @@ impl PostManager for Blag {
}; };
if name.ends_with(".sh") { if name.ends_with(".sh") {
set.push(async move { self.get_post(name.trim_end_matches(".sh")).await }); set.push(
async move { self.get_post(name.trim_end_matches(".sh"), query).await },
);
} }
} }
} }
@ -84,7 +89,11 @@ impl PostManager for Blag {
Ok(meow) Ok(meow)
} }
async fn get_post(&self, name: &str) -> Result<ReturnedPost, PostError> { async fn get_post(
&self,
name: &str,
_query: &HashMap<String, Value>,
) -> Result<ReturnedPost, PostError> {
let mut path = self.root.join(name); let mut path = self.root.join(name);
if name.ends_with(".sh") { if name.ends_with(".sh") {
@ -157,7 +166,7 @@ impl PostManager for Blag {
)) ))
} }
async fn get_raw(&self, name: &str) -> Result<Option<String>, PostError> { async fn as_raw(&self, name: &str) -> Result<Option<String>, PostError> {
let mut buf = String::with_capacity(name.len() + 3); let mut buf = String::with_capacity(name.len() + 3);
buf += name; buf += name;
buf += ".sh"; buf += ".sh";

View file

@ -1,4 +1,4 @@
use std::collections::BTreeSet; use std::collections::{BTreeSet, HashMap};
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::io; use std::io;
use std::path::Path; use std::path::Path;
@ -14,6 +14,7 @@ use color_eyre::eyre::{self, Context};
use comrak::plugins::syntect::SyntectAdapter; use comrak::plugins::syntect::SyntectAdapter;
use fronma::parser::{parse, ParsedData}; use fronma::parser::{parse, ParsedData};
use serde::Deserialize; use serde::Deserialize;
use serde_value::Value;
use tokio::fs; use tokio::fs;
use tokio::io::AsyncReadExt; use tokio::io::AsyncReadExt;
use tracing::warn; use tracing::warn;
@ -140,6 +141,7 @@ impl PostManager for MarkdownPosts {
async fn get_all_posts( async fn get_all_posts(
&self, &self,
filters: &[Filter<'_>], filters: &[Filter<'_>],
query: &HashMap<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> { ) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError> {
let mut posts = Vec::new(); let mut posts = Vec::new();
@ -156,7 +158,7 @@ impl PostManager for MarkdownPosts {
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let post = self.get_post(&name).await?; let post = self.get_post(&name, query).await?;
if let ReturnedPost::Rendered(meta, content, stats) = post if let ReturnedPost::Rendered(meta, content, stats) = post
&& meta.apply_filters(filters) && meta.apply_filters(filters)
{ {
@ -171,6 +173,7 @@ impl PostManager for MarkdownPosts {
async fn get_all_post_metadata( async fn get_all_post_metadata(
&self, &self,
filters: &[Filter<'_>], filters: &[Filter<'_>],
_query: &HashMap<String, Value>,
) -> Result<Vec<PostMetadata>, PostError> { ) -> Result<Vec<PostMetadata>, PostError> {
let mut posts = Vec::new(); let mut posts = Vec::new();
@ -211,7 +214,11 @@ impl PostManager for MarkdownPosts {
Ok(posts) Ok(posts)
} }
async fn get_post(&self, name: &str) -> Result<ReturnedPost, PostError> { async fn get_post(
&self,
name: &str,
_query: &HashMap<String, Value>,
) -> Result<ReturnedPost, PostError> {
if self.config.markdown_access && name.ends_with(".md") { if self.config.markdown_access && name.ends_with(".md") {
let path = self.config.dirs.posts.join(name); let path = self.config.dirs.posts.join(name);
@ -287,7 +294,7 @@ impl PostManager for MarkdownPosts {
} }
} }
async fn get_raw(&self, name: &str) -> Result<Option<String>, PostError> { async fn as_raw(&self, name: &str) -> Result<Option<String>, PostError> {
let mut buf = String::with_capacity(name.len() + 3); let mut buf = String::with_capacity(name.len() + 3);
buf += name; buf += name;
buf += ".md"; buf += ".md";

View file

@ -2,11 +2,12 @@ pub mod blag;
pub mod cache; pub mod cache;
pub mod markdown_posts; pub mod markdown_posts;
use std::time::Duration; use std::{collections::HashMap, time::Duration};
use axum::{async_trait, http::HeaderValue}; use axum::{async_trait, http::HeaderValue};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_value::Value;
use crate::error::PostError; use crate::error::PostError;
pub use blag::Blag; pub use blag::Blag;
@ -74,8 +75,9 @@ pub trait PostManager {
async fn get_all_post_metadata( async fn get_all_post_metadata(
&self, &self,
filters: &[Filter<'_>], filters: &[Filter<'_>],
query: &HashMap<String, Value>,
) -> Result<Vec<PostMetadata>, PostError> { ) -> Result<Vec<PostMetadata>, PostError> {
self.get_all_posts(filters) self.get_all_posts(filters, query)
.await .await
.map(|vec| vec.into_iter().map(|(meta, ..)| meta).collect()) .map(|vec| vec.into_iter().map(|(meta, ..)| meta).collect())
} }
@ -83,15 +85,19 @@ pub trait PostManager {
async fn get_all_posts( async fn get_all_posts(
&self, &self,
filters: &[Filter<'_>], filters: &[Filter<'_>],
query: &HashMap<String, Value>,
) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError>; ) -> Result<Vec<(PostMetadata, String, RenderStats)>, PostError>;
async fn get_max_n_post_metadata_with_optional_tag_sorted( async fn get_max_n_post_metadata_with_optional_tag_sorted(
&self, &self,
n: Option<usize>, n: Option<usize>,
tag: Option<&str>, tag: Option<&str>,
query: &HashMap<String, Value>,
) -> Result<Vec<PostMetadata>, PostError> { ) -> Result<Vec<PostMetadata>, PostError> {
let filters = tag.and(Some(Filter::Tags(tag.as_slice()))); let filters = tag.and(Some(Filter::Tags(tag.as_slice())));
let mut posts = self.get_all_post_metadata(filters.as_slice()).await?; let mut posts = self
.get_all_post_metadata(filters.as_slice(), query)
.await?;
// we still want some semblance of order if created_at is None so sort by mtime as well // we still want some semblance of order if created_at is None so sort by mtime as well
posts.sort_unstable_by_key(|metadata| metadata.modified_at.unwrap_or_default()); posts.sort_unstable_by_key(|metadata| metadata.modified_at.unwrap_or_default());
posts.sort_by_key(|metadata| metadata.created_at.unwrap_or_default()); posts.sort_by_key(|metadata| metadata.created_at.unwrap_or_default());
@ -104,19 +110,27 @@ pub trait PostManager {
} }
#[allow(unused)] #[allow(unused)]
async fn get_post_metadata(&self, name: &str) -> Result<PostMetadata, PostError> { async fn get_post_metadata(
match self.get_post(name).await? { &self,
name: &str,
query: &HashMap<String, Value>,
) -> Result<PostMetadata, PostError> {
match self.get_post(name, query).await? {
ReturnedPost::Rendered(metadata, ..) => Ok(metadata), ReturnedPost::Rendered(metadata, ..) => Ok(metadata),
ReturnedPost::Raw(..) => Err(PostError::NotFound(name.to_string())), ReturnedPost::Raw(..) => Err(PostError::NotFound(name.to_string())),
} }
} }
async fn get_post(&self, name: &str) -> Result<ReturnedPost, PostError>; async fn get_post(
&self,
name: &str,
query: &HashMap<String, Value>,
) -> Result<ReturnedPost, PostError>;
async fn cleanup(&self) {} async fn cleanup(&self) {}
#[allow(unused)] #[allow(unused)]
async fn get_raw(&self, name: &str) -> Result<Option<String>, PostError> { async fn as_raw(&self, name: &str) -> Result<Option<String>, PostError> {
Ok(None) Ok(None)
} }
} }