allow passing extra params to blog engine
This commit is contained in:
parent
e5cc685b0a
commit
9eddbdb881
6 changed files with 82 additions and 18 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
21
src/app.rs
21
src/app.rs
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue