Restructure project
parent
0c158652c9
commit
14726ca7d4
@ -1,6 +1,35 @@
|
|||||||
[workspace]
|
[package]
|
||||||
|
name = "blog"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
members = [
|
[lib]
|
||||||
"blog_server",
|
name = "blog"
|
||||||
"utils/css_gen"
|
path = "src/lib/lib.rs"
|
||||||
]
|
|
||||||
|
[[bin]]
|
||||||
|
name = "blog_server"
|
||||||
|
path = "src/bin/blog_server/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "css_gen"
|
||||||
|
path = "src/bin/css_gen/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libshire = { git = "https://github.com/pantonshire/libshire" }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
axum = "0.5"
|
||||||
|
tower = { version = "0.4", features = ["limit"] }
|
||||||
|
tower-http = { version = "0.3", features = ["fs", "trace"] }
|
||||||
|
mime = "0.3"
|
||||||
|
maud = "0.23"
|
||||||
|
atom_syndication = "0.11"
|
||||||
|
rss = "2"
|
||||||
|
knuffel = "2"
|
||||||
|
pulldown-cmark = "0.9"
|
||||||
|
syntect = "4"
|
||||||
|
notify = "4"
|
||||||
|
chrono = "0.4"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
miette = { version = "4", features = ["fancy"] }
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "blog_server"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# My own utilities library
|
|
||||||
libshire = { git = "https://github.com/pantonshire/libshire" }
|
|
||||||
# Async runtime for Axum
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
# Web server framework
|
|
||||||
axum = "0.5"
|
|
||||||
# Middleware for the web server
|
|
||||||
tower = { version = "0.4", features = ["limit"] }
|
|
||||||
tower-http = { version = "0.3", features = ["fs", "trace"] }
|
|
||||||
# MIME type implementation
|
|
||||||
mime = "0.3"
|
|
||||||
# Compile-time HTTP templating
|
|
||||||
maud = "0.23"
|
|
||||||
# Serialisation for RSS and Atom
|
|
||||||
atom_syndication = "0.11"
|
|
||||||
rss = "2"
|
|
||||||
# KDL parsing
|
|
||||||
knuffel = "2"
|
|
||||||
# CommonMark parsing
|
|
||||||
pulldown-cmark = "0.9"
|
|
||||||
# Syntax highlighting
|
|
||||||
syntect = "4"
|
|
||||||
# Filesystem event watcher
|
|
||||||
notify = "4"
|
|
||||||
# Time library
|
|
||||||
chrono = "0.4"
|
|
||||||
# Logging for observability
|
|
||||||
tracing = "0.1"
|
|
||||||
tracing-subscriber = "0.3"
|
|
||||||
# Pretty errors
|
|
||||||
miette = { version = "4", features = ["fancy"] }
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use atom_syndication as atom;
|
|
||||||
use axum::{
|
|
||||||
body::Bytes,
|
|
||||||
extract::Extension,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::response::Atom;
|
|
||||||
use crate::{
|
|
||||||
Config,
|
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
time::unix_epoch,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn handle(
|
|
||||||
Extension(config): Extension<Arc<Config>>,
|
|
||||||
Extension(posts): Extension<ConcurrentPostsStore>,
|
|
||||||
) -> Atom<Bytes> {
|
|
||||||
let (atom_entries, updated) = {
|
|
||||||
let guard = posts.read().await;
|
|
||||||
|
|
||||||
let atom_entries = guard.iter_by_created()
|
|
||||||
.take(config.atom.num_posts)
|
|
||||||
.map(|post| {
|
|
||||||
atom::EntryBuilder::default()
|
|
||||||
.id(format!("urn:uuid:{}", post.uuid()))
|
|
||||||
.title(post.title().to_owned())
|
|
||||||
.updated(post.updated())
|
|
||||||
.links(vec![
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/articles/{}",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain,
|
|
||||||
post.id()
|
|
||||||
))
|
|
||||||
.rel("alternate".to_owned())
|
|
||||||
.mime_type(Some("text/html".to_owned()))
|
|
||||||
.build()
|
|
||||||
])
|
|
||||||
.author(atom::PersonBuilder::default()
|
|
||||||
.name(post.author().to_owned())
|
|
||||||
.build())
|
|
||||||
.build()
|
|
||||||
})
|
|
||||||
.collect::<Vec<atom::Entry>>();
|
|
||||||
|
|
||||||
let updated = guard.last_updated()
|
|
||||||
.unwrap_or_else(unix_epoch);
|
|
||||||
|
|
||||||
(atom_entries, updated)
|
|
||||||
};
|
|
||||||
|
|
||||||
Atom(atom::FeedBuilder::default()
|
|
||||||
.id(format!("urn:uuid:{}", *config.namespace_uuid))
|
|
||||||
.title(config.atom.title.clone())
|
|
||||||
.updated(updated)
|
|
||||||
.links(vec![
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/atom.xml",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain
|
|
||||||
))
|
|
||||||
.rel("self".to_owned())
|
|
||||||
.build(),
|
|
||||||
atom::LinkBuilder::default()
|
|
||||||
.href(format!(
|
|
||||||
"{}://{}/articles/",
|
|
||||||
config.self_ref.protocol,
|
|
||||||
config.self_ref.domain
|
|
||||||
))
|
|
||||||
.rel("alternate".to_owned())
|
|
||||||
.mime_type(Some("text/html".to_owned()))
|
|
||||||
.build()
|
|
||||||
])
|
|
||||||
.entries(atom_entries)
|
|
||||||
.build()
|
|
||||||
.to_string()
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use atom_syndication as atom;
|
||||||
|
use axum::{body::Bytes, extract::Extension};
|
||||||
|
|
||||||
|
use blog::{db::ConcurrentPostsStore, time::unix_epoch};
|
||||||
|
|
||||||
|
use crate::Config;
|
||||||
|
|
||||||
|
use super::response::Atom;
|
||||||
|
|
||||||
|
pub async fn handle(
|
||||||
|
Extension(config): Extension<Arc<Config>>,
|
||||||
|
Extension(posts): Extension<ConcurrentPostsStore>,
|
||||||
|
) -> Atom<Bytes> {
|
||||||
|
let (atom_entries, updated) = {
|
||||||
|
let guard = posts.read().await;
|
||||||
|
|
||||||
|
let atom_entries = guard
|
||||||
|
.iter_by_created()
|
||||||
|
.take(config.atom.num_posts)
|
||||||
|
.map(|post| {
|
||||||
|
atom::EntryBuilder::default()
|
||||||
|
.id(format!("urn:uuid:{}", post.uuid()))
|
||||||
|
.title(post.title().to_owned())
|
||||||
|
.updated(post.updated())
|
||||||
|
.links(vec![atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/articles/{}",
|
||||||
|
config.self_ref.protocol,
|
||||||
|
config.self_ref.domain,
|
||||||
|
post.id()
|
||||||
|
))
|
||||||
|
.rel("alternate".to_owned())
|
||||||
|
.mime_type(Some("text/html".to_owned()))
|
||||||
|
.build()])
|
||||||
|
.author(
|
||||||
|
atom::PersonBuilder::default()
|
||||||
|
.name(post.author().to_owned())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
})
|
||||||
|
.collect::<Vec<atom::Entry>>();
|
||||||
|
|
||||||
|
let updated = guard.last_updated().unwrap_or_else(unix_epoch);
|
||||||
|
|
||||||
|
(atom_entries, updated)
|
||||||
|
};
|
||||||
|
|
||||||
|
Atom(
|
||||||
|
atom::FeedBuilder::default()
|
||||||
|
.id(format!("urn:uuid:{}", *config.namespace_uuid))
|
||||||
|
.title(config.atom.title.clone())
|
||||||
|
.updated(updated)
|
||||||
|
.links(vec![
|
||||||
|
atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/atom.xml",
|
||||||
|
config.self_ref.protocol, config.self_ref.domain
|
||||||
|
))
|
||||||
|
.rel("self".to_owned())
|
||||||
|
.build(),
|
||||||
|
atom::LinkBuilder::default()
|
||||||
|
.href(format!(
|
||||||
|
"{}://{}/articles/",
|
||||||
|
config.self_ref.protocol, config.self_ref.domain
|
||||||
|
))
|
||||||
|
.rel("alternate".to_owned())
|
||||||
|
.mime_type(Some("text/html".to_owned()))
|
||||||
|
.build(),
|
||||||
|
])
|
||||||
|
.entries(atom_entries)
|
||||||
|
.build()
|
||||||
|
.to_string()
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::template;
|
use crate::template;
|
||||||
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle() -> Html {
|
pub async fn handle() -> Html {
|
||||||
@ -1,10 +1,10 @@
|
|||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
||||||
@ -1,10 +1,10 @@
|
|||||||
use axum::extract::{Extension, Path};
|
use axum::extract::{Extension, Path};
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::{Error, Html};
|
use super::response::{Error, Html};
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
@ -1,10 +1,10 @@
|
|||||||
use axum::extract::Extension;
|
use axum::extract::Extension;
|
||||||
use maud::html;
|
use maud::html;
|
||||||
|
|
||||||
use crate::{
|
use blog::db::ConcurrentPostsStore;
|
||||||
posts_store::ConcurrentPostsStore,
|
|
||||||
template,
|
use crate::template;
|
||||||
};
|
|
||||||
use super::response::Html;
|
use super::response::Html;
|
||||||
|
|
||||||
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
pub async fn handle(Extension(posts): Extension<ConcurrentPostsStore>) -> Html {
|
||||||
@ -1,10 +1,11 @@
|
|||||||
use std::env;
|
use std::{env, process};
|
||||||
use std::process;
|
|
||||||
|
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::{
|
||||||
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
|
highlighting::ThemeSet,
|
||||||
|
html::css_for_theme_with_class_style,
|
||||||
|
};
|
||||||
|
|
||||||
const CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "cb_" };
|
use blog::codeblock::CLASS_STYLE;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let theme_set = ThemeSet::load_defaults();
|
let theme_set = ThemeSet::load_defaults();
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
pub mod codeblock;
|
||||||
|
pub mod post;
|
||||||
|
pub mod db;
|
||||||
|
pub mod time;
|
||||||
|
pub mod uuid;
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
use std::{borrow, fmt, ops};
|
||||||
|
|
||||||
|
use libshire::strings::ShString22;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||||
|
pub struct PostId(ShString22);
|
||||||
|
|
||||||
|
impl PostId {
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_file_name(file_name: &str) -> Option<Self> {
|
||||||
|
fn is_invalid_char(c: char) -> bool {
|
||||||
|
c == '/' || c == '\\' || c == '.'
|
||||||
|
}
|
||||||
|
|
||||||
|
let prefix = file_name
|
||||||
|
.strip_suffix(super::POST_FILE_EXTENSION)?;
|
||||||
|
|
||||||
|
if prefix.contains(is_invalid_char) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self(ShString22::new_from_str(prefix)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_inner(self) -> ShString22 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_inner(&self) -> &ShString22 {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for PostId {
|
||||||
|
type Target = str;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::DerefMut for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsMut<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn as_mut(&mut self) -> &mut str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::Borrow<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn borrow(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl borrow::BorrowMut<str> for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn borrow_mut(&mut self) -> &mut str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PostId {
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&**self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
mod id;
|
||||||
|
mod parse;
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use libshire::{strings::ShString22, uuid::Uuid};
|
||||||
|
use maud::{Markup, PreEscaped};
|
||||||
|
|
||||||
|
pub use id::PostId;
|
||||||
|
pub use parse::{parse, ParseError};
|
||||||
|
|
||||||
|
const POST_FILE_EXTENSION: &str = ".kdl.md";
|
||||||
|
|
||||||
|
pub struct Post {
|
||||||
|
uuid: Uuid,
|
||||||
|
id: PostId,
|
||||||
|
title: String,
|
||||||
|
subtitle: Option<String>,
|
||||||
|
author: ShString22,
|
||||||
|
html: Markup,
|
||||||
|
tags: Vec<ShString22>,
|
||||||
|
created: DateTime<Utc>,
|
||||||
|
updated: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
pub fn uuid(&self) -> Uuid {
|
||||||
|
self.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &PostId {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self) -> &str {
|
||||||
|
&self.title
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subtitle(&self) -> Option<&str> {
|
||||||
|
self.subtitle.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn author(&self) -> &str {
|
||||||
|
&self.author
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html(&self) -> PreEscaped<&str> {
|
||||||
|
PreEscaped(&self.html.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tags(&self) -> &[ShString22] {
|
||||||
|
&self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn created(&self) -> DateTime<Utc> {
|
||||||
|
self.created
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updated(&self) -> DateTime<Utc> {
|
||||||
|
self.updated
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "css_gen"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
syntect = "4"
|
|
||||||
Loading…
Reference in New Issue