too much stuff
This commit is contained in:
parent
cbfc505649
commit
e0a3f35caf
21 changed files with 262 additions and 86 deletions
12
404.html
12
404.html
|
@ -1,12 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>404 Not Found</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>404 Not Found</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
126
Cargo.lock
generated
126
Cargo.lock
generated
|
@ -17,6 +17,17 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -153,6 +164,12 @@ dependencies = [
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.2"
|
version = "0.21.2"
|
||||||
|
@ -354,6 +371,25 @@ dependencies = [
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "config"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"json5",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"pathdiff",
|
||||||
|
"ron",
|
||||||
|
"rust-ini",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"toml 0.5.11",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -471,6 +507,12 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dlv-list"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -708,6 +750,9 @@ name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
@ -942,6 +987,17 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "json5"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1034,6 +1090,12 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1054,6 +1116,16 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
@ -1166,6 +1238,16 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-multimap"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||||
|
dependencies = [
|
||||||
|
"dlv-list",
|
||||||
|
"hashbrown 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -1210,6 +1292,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
@ -1343,7 +1431,7 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06"
|
checksum = "bdc0001cfea3db57a2e24bc0d818e9e20e554b5f97fabb9bc231dc240269ae06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.2",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"line-wrap",
|
"line-wrap",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
|
@ -1515,6 +1603,27 @@ version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ron"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-ini"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"ordered-multimap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
|
@ -1891,6 +2000,15 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
|
@ -1953,7 +2071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
|
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"base64",
|
"base64 0.21.2",
|
||||||
"bitflags 2.3.3",
|
"bitflags 2.3.3",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -2306,8 +2424,8 @@ dependencies = [
|
||||||
"cached",
|
"cached",
|
||||||
"chrono",
|
"chrono",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
|
"config",
|
||||||
"glob",
|
"glob",
|
||||||
"lazy_static",
|
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
|
@ -2318,7 +2436,7 @@ dependencies = [
|
||||||
"syntect",
|
"syntect",
|
||||||
"tera",
|
"tera",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml 0.7.6",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
@ -10,9 +10,8 @@ axum = { version = "0.6.12", features = ["http2", "original-uri"] }
|
||||||
cached = "0.44.0"
|
cached = "0.44.0"
|
||||||
chrono = { version = "0.4.24", features = ["serde"] }
|
chrono = { version = "0.4.24", features = ["serde"] }
|
||||||
color-eyre = "0.6.1"
|
color-eyre = "0.6.1"
|
||||||
|
config = "0.13.3"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
# hyper = { version = "0.14.19", features = ["full"] }
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
opentelemetry = { version = "0.19.0", features = ["metrics"] }
|
opentelemetry = { version = "0.19.0", features = ["metrics"] }
|
||||||
prometheus = { version = "0.13.3", features = ["process"] }
|
prometheus = { version = "0.13.3", features = ["process"] }
|
||||||
pulldown-cmark = "0.9.2"
|
pulldown-cmark = "0.9.2"
|
||||||
|
|
3
config.toml
Normal file
3
config.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
base_url = "http://localhost:8080/"
|
||||||
|
bind_address = "0.0.0.0:8080"
|
||||||
|
logging = "info"
|
7
posts/draft-test.md
Normal file
7
posts/draft-test.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
+++
|
||||||
|
title="draft test"
|
||||||
|
draft=true
|
||||||
|
date=2023-07-29T17:25:20+02:00
|
||||||
|
+++
|
||||||
|
|
||||||
|
wow look it's a hidden post because it's marked as a draft
|
|
@ -13,6 +13,8 @@ here have a squid kid miku to test relative paths:
|
||||||
modified post test, see if docker skips build using
|
modified post test, see if docker skips build using
|
||||||
its cache if only a post has changed.
|
its cache if only a post has changed.
|
||||||
|
|
||||||
|
testing "smart" punctuation --- I don't know if I want it. 'it should' do some fancy stuff.
|
||||||
|
|
||||||
code hilighting test:
|
code hilighting test:
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
|
@ -20,3 +22,11 @@ fn main() {
|
||||||
println!("Hello world!")
|
println!("Hello world!")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
uh oh, here comes a screenshot from a different post!
|
||||||
|
|
||||||
|
![dungeon screenshot](../dungeon/screenshot.png)
|
||||||
|
|
||||||
|
and here it is again, except it should 404!
|
||||||
|
|
||||||
|
![missing dungeon screenshot](../dungeon/screenshot.jpeg)
|
||||||
|
|
|
@ -16,7 +16,7 @@ struct FeedContext<'a> {
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub fn render_atom_feed(state: &AppState) -> Result<String> {
|
pub fn render_atom_feed(state: &AppState) -> Result<String> {
|
||||||
let mut posts: Vec<_> = state.posts.values().filter(|p| p.is_published()).collect();
|
let mut posts: Vec<_> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
posts.reverse();
|
posts.reverse();
|
||||||
|
@ -41,7 +41,7 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result<String> {
|
||||||
let mut posts: Vec<_> = state
|
let mut posts: Vec<_> = state
|
||||||
.posts
|
.posts
|
||||||
.values()
|
.values()
|
||||||
.filter(|p| p.is_published() && p.tags.contains(&tag.slug))
|
.filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag.slug))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
|
|
|
@ -7,14 +7,14 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use cached::once_cell::sync::Lazy;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::services::ServeFile;
|
use tower_http::services::ServeDir;
|
||||||
use tracing::{
|
use tracing::{
|
||||||
instrument,
|
instrument,
|
||||||
log::{error, info},
|
log::{error, info, debug},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{AppState, WebsiteError};
|
use crate::{AppState, WebsiteError};
|
||||||
|
@ -22,16 +22,14 @@ use crate::{AppState, WebsiteError};
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
|
|
||||||
lazy_static! {
|
pub static HIT_COUNTER: Lazy<IntCounterVec> = Lazy::new(|| prometheus::register_int_counter_vec!(
|
||||||
pub static ref HIT_COUNTER: IntCounterVec = prometheus::register_int_counter_vec!(
|
|
||||||
opts!(
|
opts!(
|
||||||
"http_requests_total",
|
"http_requests_total",
|
||||||
"Total amount of http requests received"
|
"Total amount of http requests received"
|
||||||
),
|
),
|
||||||
&["route", "method", "status"]
|
&["route", "method", "status"]
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap());
|
||||||
}
|
|
||||||
|
|
||||||
pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
@ -44,13 +42,12 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
.route("/metrics", get(metrics))
|
.route("/metrics", get(metrics))
|
||||||
.route_service(
|
.route_service(
|
||||||
"/posts/:slug/*path",
|
"/posts/:slug/*path",
|
||||||
tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")),
|
ServeDir::new("./"),
|
||||||
)
|
)
|
||||||
.route_service(
|
.route_service(
|
||||||
"/static/*path",
|
"/static/*path",
|
||||||
tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")),
|
ServeDir::new("./"),
|
||||||
)
|
)
|
||||||
.fallback_service(ServeFile::new("./404.html"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
@ -61,10 +58,11 @@ pub async fn index(
|
||||||
if should_return_304(&headers, Some(state.startup_time.into())) {
|
if should_return_304(&headers, Some(state.startup_time.into())) {
|
||||||
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
}
|
}
|
||||||
let ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
|
ctx.insert("base_url", &state.base_url.to_string());
|
||||||
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
let res = state.tera.render("index.html", &ctx).map_err(|e| {
|
||||||
error!("Failed rendering index: {}", e);
|
error!("Failed rendering index: {}", e);
|
||||||
WebsiteError::NotFound
|
WebsiteError::InternalError(e.into())
|
||||||
})?;
|
})?;
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
|
@ -97,17 +95,17 @@ pub async fn not_found() -> impl IntoResponse {
|
||||||
|
|
||||||
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Response {
|
||||||
let path = request.uri().path().to_string();
|
let path = request.uri().path().to_string();
|
||||||
let method = request.method().to_string();
|
let method = request.method().clone();
|
||||||
|
|
||||||
let response = next.run(request).await;
|
let response = next.run(request).await;
|
||||||
|
|
||||||
if !response.status().is_client_error() {
|
if !response.status().is_client_error() {
|
||||||
HIT_COUNTER
|
HIT_COUNTER
|
||||||
.with_label_values(&[&path, &method, response.status().as_str()])
|
.with_label_values(&[&path, method.as_str(), response.status().as_str()])
|
||||||
.inc();
|
.inc();
|
||||||
} else if response.status() == StatusCode::NOT_FOUND {
|
} else if response.status() == StatusCode::NOT_FOUND {
|
||||||
HIT_COUNTER
|
HIT_COUNTER
|
||||||
.with_label_values(&["not found", &method, response.status().as_str()])
|
.with_label_values(&["not found", method.as_str(), response.status().as_str()])
|
||||||
.inc();
|
.inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,16 +114,16 @@ pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Respon
|
||||||
|
|
||||||
fn should_return_304(headers: &HeaderMap, last_changed: Option<DateTime<FixedOffset>>) -> bool {
|
fn should_return_304(headers: &HeaderMap, last_changed: Option<DateTime<FixedOffset>>) -> bool {
|
||||||
let Some(date) = last_changed else {
|
let Some(date) = last_changed else {
|
||||||
info!("no last modified date");
|
debug!("no last modified date");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let Some(since) = headers.get(header::IF_MODIFIED_SINCE) else {
|
let Some(since) = headers.get(header::IF_MODIFIED_SINCE) else {
|
||||||
info!("no IF_MODIFIED_SINCE header");
|
debug!("no IF_MODIFIED_SINCE header");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(parsed) = DateTime::<FixedOffset>::parse_from_rfc2822(since.to_str().unwrap()) else {
|
let Ok(parsed) = DateTime::<FixedOffset>::parse_from_rfc2822(since.to_str().unwrap()) else {
|
||||||
info!("failed to parse IF_MODIFIED_SINCE header");
|
debug!("failed to parse IF_MODIFIED_SINCE header");
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -160,7 +158,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn render_index() {
|
fn render_index() {
|
||||||
let tera = tera::Tera::new("templates/**/*").unwrap();
|
let tera = tera::Tera::new("templates/**/*").unwrap();
|
||||||
let ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
|
ctx.insert("base_url", "http://localhost/");
|
||||||
let _res = tera.render("index.html", &ctx).unwrap();
|
let _res = tera.render("index.html", &ctx).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub async fn index(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> Result<Response, WebsiteError> {
|
) -> Result<Response, WebsiteError> {
|
||||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||||
|
|
||||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ pub async fn feed(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> Result<Response, WebsiteError> {
|
) -> Result<Response, WebsiteError> {
|
||||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
let mut posts: Vec<&Post> = state.posts.values().filter(|p| !p.draft && p.is_published()).collect();
|
||||||
|
|
||||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
@ -203,6 +203,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
let page = PageContext { title: "Posts" };
|
let page = PageContext { title: "Posts" };
|
||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
|
ctx.insert("base_url", "http://localhost/");
|
||||||
ctx.insert("page", &page);
|
ctx.insert("page", &page);
|
||||||
ctx.insert("posts", &posts);
|
ctx.insert("posts", &posts);
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub async fn view(
|
||||||
let mut posts: Vec<&Post> = state
|
let mut posts: Vec<&Post> = state
|
||||||
.posts
|
.posts
|
||||||
.values()
|
.values()
|
||||||
.filter(|p| p.is_published() && p.tags.contains(&tag))
|
.filter(|p| !p.draft && p.is_published() && p.tags.contains(&tag))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
let last_changed = posts.iter().filter_map(|p| p.last_modified()).max();
|
||||||
|
|
|
@ -31,3 +31,28 @@ pub fn uri_with_path(uri: &Uri, path: &str) -> Uri {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use axum::http::Uri;
|
||||||
|
|
||||||
|
use crate::helpers::uri_with_path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uri_with_relative_path() {
|
||||||
|
let uri: Uri = "http://localhost/baba/".parse().unwrap();
|
||||||
|
assert_eq!(uri_with_path(&uri, "is/you").to_string(), "http://localhost/baba/is/you");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uri_with_absolute_path() {
|
||||||
|
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||||
|
assert_eq!(uri_with_path(&uri, "/keke/is/move").to_string(), "http://localhost/keke/is/move");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn uri_with_index_relative_path() {
|
||||||
|
let uri: Uri = "http://localhost/baba/is/you".parse().unwrap();
|
||||||
|
assert_eq!(uri_with_path(&uri, "happy").to_string(), "http://localhost/baba/is/happy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -11,6 +11,7 @@ use axum::{
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use color_eyre::eyre::{Error, Result};
|
use color_eyre::eyre::{Error, Result};
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
use post::Post;
|
use post::Post;
|
||||||
|
|
||||||
use tag::Tag;
|
use tag::Tag;
|
||||||
|
@ -39,15 +40,20 @@ pub struct AppState {
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
let cfg = Config::builder()
|
||||||
|
.add_source(config::File::with_name("config.toml"))
|
||||||
|
.add_source(config::Environment::with_prefix("APP"))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
init_tracing();
|
init_tracing(&cfg);
|
||||||
|
|
||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
|
|
||||||
let base_url: Uri = option_env!("SITE_BASE_URL")
|
let base_url: Uri = cfg.get_string("base_url")?
|
||||||
.unwrap_or("http://localhost:8080")
|
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let tera = Tera::new("templates/**/*")?;
|
let tera = Tera::new("templates/**/*")?;
|
||||||
let mut state = AppState {
|
let mut state = AppState {
|
||||||
startup_time: chrono::offset::Utc::now(),
|
startup_time: chrono::offset::Utc::now(),
|
||||||
|
@ -55,6 +61,7 @@ async fn main() -> Result<()> {
|
||||||
tera,
|
tera,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let posts = post::load_all(&state).await?;
|
let posts = post::load_all(&state).await?;
|
||||||
let tags = tag::get_tags(posts.values());
|
let tags = tag::get_tags(posts.values());
|
||||||
state.posts = posts;
|
state.posts = posts;
|
||||||
|
@ -73,17 +80,23 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
info!("Now listening at {}", state.base_url);
|
info!("Now listening at {}", state.base_url);
|
||||||
|
|
||||||
axum::Server::bind(&"0.0.0.0:8080".parse().unwrap())
|
axum::Server::bind(&cfg.get_string("bind_address")?.parse().unwrap())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_tracing() {
|
fn init_tracing(cfg: &Config) {
|
||||||
let filter = EnvFilter::builder()
|
let filter = if let Ok(filter) = cfg.get_string("logging") {
|
||||||
.with_default_directive("into".parse().unwrap())
|
EnvFilter::builder()
|
||||||
.from_env_lossy();
|
.with_default_directive("info".parse().unwrap())
|
||||||
|
.parse_lossy(filter)
|
||||||
|
} else {
|
||||||
|
EnvFilter::builder()
|
||||||
|
.with_default_directive("info".parse().unwrap())
|
||||||
|
.from_env_lossy()
|
||||||
|
};
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(filter)
|
.with(filter)
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
use crate::hilighting;
|
use crate::hilighting;
|
||||||
use axum::http::Uri;
|
use axum::http::Uri;
|
||||||
|
use cached::once_cell::sync::Lazy;
|
||||||
use pulldown_cmark::Event;
|
use pulldown_cmark::Event;
|
||||||
use pulldown_cmark::Tag;
|
use pulldown_cmark::Tag;
|
||||||
use pulldown_cmark::{Options, Parser};
|
use pulldown_cmark::{Options, Parser};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
static STARTS_WITH_SCHEMA_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+:").unwrap());
|
||||||
|
static EMAIL_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^.+?@\w+(\.\w+)*$").unwrap());
|
||||||
|
|
||||||
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
|
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
|
||||||
let options = Options::all();
|
let mut opt = Options::empty();
|
||||||
|
opt.insert(Options::ENABLE_FOOTNOTES);
|
||||||
|
opt.insert(Options::ENABLE_HEADING_ATTRIBUTES);
|
||||||
|
opt.insert(Options::ENABLE_STRIKETHROUGH);
|
||||||
|
opt.insert(Options::ENABLE_TABLES);
|
||||||
|
opt.insert(Options::ENABLE_TASKLISTS);
|
||||||
|
opt.insert(Options::ENABLE_SMART_PUNCTUATION);
|
||||||
|
|
||||||
let mut content_html = String::new();
|
let mut content_html = String::new();
|
||||||
let parser = Parser::new_ext(markdown, options);
|
let parser = Parser::new_ext(markdown, opt);
|
||||||
|
|
||||||
let mut code_block = false;
|
let mut code_block = false;
|
||||||
let mut code_lang = None;
|
let mut code_lang = None;
|
||||||
|
@ -25,7 +37,7 @@ pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String
|
||||||
}
|
}
|
||||||
Event::Start(Tag::Link(t, mut link, title)) => {
|
Event::Start(Tag::Link(t, mut link, title)) => {
|
||||||
if let Some(uri) = base_uri {
|
if let Some(uri) = base_uri {
|
||||||
if !link.contains("://") && !link.contains('@') {
|
if !link.starts_with('#') && !STARTS_WITH_SCHEMA_RE.is_match(&link) && !EMAIL_RE.is_match(&link) {
|
||||||
// convert relative URIs to absolute URIs
|
// convert relative URIs to absolute URIs
|
||||||
link = helpers::uri_with_path(uri, &link).to_string().into();
|
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||||
}
|
}
|
||||||
|
|
20
src/post.rs
20
src/post.rs
|
@ -1,10 +1,9 @@
|
||||||
use std::{collections::HashMap, path::Path};
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
|
use cached::once_cell::sync::Lazy;
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
@ -16,6 +15,11 @@ use tracing::{
|
||||||
|
|
||||||
use crate::{helpers, markdown, AppState, WebsiteError};
|
use crate::{helpers, markdown, AppState, WebsiteError};
|
||||||
|
|
||||||
|
static FRONTMATTER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(
|
||||||
|
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
pub struct TomlFrontMatter {
|
pub struct TomlFrontMatter {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
@ -29,6 +33,7 @@ pub struct TomlFrontMatter {
|
||||||
#[derive(Serialize, Clone, Debug, Default)]
|
#[derive(Serialize, Clone, Debug, Default)]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
pub draft: bool,
|
||||||
pub date: Option<DateTime<FixedOffset>>,
|
pub date: Option<DateTime<FixedOffset>>,
|
||||||
pub updated: Option<DateTime<FixedOffset>>,
|
pub updated: Option<DateTime<FixedOffset>>,
|
||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
|
@ -45,6 +50,7 @@ impl Post {
|
||||||
slug,
|
slug,
|
||||||
content,
|
content,
|
||||||
title: fm.title,
|
title: fm.title,
|
||||||
|
draft: fm.draft.unwrap_or(false),
|
||||||
date: fm
|
date: fm
|
||||||
.date
|
.date
|
||||||
.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
|
.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
|
||||||
|
@ -124,13 +130,6 @@ pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result
|
||||||
fn parse_frontmatter(
|
fn parse_frontmatter(
|
||||||
src: String,
|
src: String,
|
||||||
) -> color_eyre::eyre::Result<(Option<TomlFrontMatter>, Option<String>)> {
|
) -> color_eyre::eyre::Result<(Option<TomlFrontMatter>, Option<String>)> {
|
||||||
lazy_static! {
|
|
||||||
static ref FRONTMATTER_REGEX: Regex = regex::Regex::new(
|
|
||||||
r"^[\s]*\+{3}(\r?\n(?s).*?(?-s))\+{3}[\s]*(?:$|(?:\r?\n((?s).*(?-s))$))"
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) {
|
Ok(if let Some(captures) = FRONTMATTER_REGEX.captures(&src) {
|
||||||
(
|
(
|
||||||
Some(toml::from_str(captures.get(1).unwrap().as_str())?),
|
Some(toml::from_str(captures.get(1).unwrap().as_str())?),
|
||||||
|
@ -162,10 +161,11 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn render_all_posts() {
|
async fn render_all_posts() {
|
||||||
let mut state = AppState {
|
let mut state = AppState {
|
||||||
base_url: "localhost:8180".parse().unwrap(),
|
base_url: "http://localhost:8180".parse().unwrap(),
|
||||||
tera: Tera::new("templates/**/*").unwrap(),
|
tera: Tera::new("templates/**/*").unwrap(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
state.posts = super::load_all(&state).await.unwrap();
|
state.posts = super::load_all(&state).await.unwrap();
|
||||||
for post in state.posts.values() {
|
for post in state.posts.values() {
|
||||||
super::render_post(&state, post).await.unwrap();
|
super::render_post(&state, post).await.unwrap();
|
||||||
|
|
|
@ -2,16 +2,10 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
{% include "partials/head.html" %}
|
{% include "partials/head.html" %}
|
||||||
<body>
|
<body>
|
||||||
<header>
|
|
||||||
{% include "partials/header.html" -%}
|
{% include "partials/header.html" -%}
|
||||||
</header>
|
|
||||||
<hr>
|
|
||||||
<main>
|
<main>
|
||||||
{% block main %}{% endblock main -%}
|
{% block main %}{% endblock main -%}
|
||||||
</main>
|
</main>
|
||||||
<hr>
|
|
||||||
<footer>
|
|
||||||
{% include "partials/footer.html" -%}
|
{% include "partials/footer.html" -%}
|
||||||
</footer>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
|
<footer>
|
||||||
|
<hr style="border-style: dashed;">
|
||||||
<p>footer stuff goes here</p>
|
<p>footer stuff goes here</p>
|
||||||
|
</footer>
|
|
@ -1,3 +1,5 @@
|
||||||
|
<header>
|
||||||
<nav>
|
<nav>
|
||||||
<img src="{{base_url | safe}}static/avatar.png" class="avatar"/> <a href="{{base_url | safe}}">tollyx</a> - <a href="{{base_url | safe}}posts/">posts</a>
|
<img src="{{base_url | safe}}static/avatar.png" class="avatar"/> <a href="{{base_url | safe}}">tollyx</a> - <a href="{{base_url | safe}}posts/">posts</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
<h1>{{ page.title }}</h1>
|
<h1>{{ page.title }}</h1>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% if page.date -%}
|
{% if page.date -%}
|
||||||
<small>Posted on <time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d %H:%M") }}</time>
|
<small>
|
||||||
|
{%- if page.draft %}Draft {% endif -%}
|
||||||
|
Published <time datetime="{{ page.date }}">{{ page.date | date(format="%F %R%:::z") }}</time>
|
||||||
{%- if page.updated -%}
|
{%- if page.updated -%}
|
||||||
, Updated <time datetime="{{ page.updated }}">{{ page.updated | date(format="%Y-%m-%d %H:%M") }}</time>
|
, Updated <time datetime="{{ page.updated }}">{{ page.updated | date(format="%F %R%:::z") }}</time>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</small>
|
</small>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts -%}
|
{% for post in posts -%}
|
||||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
||||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
|
<time datetime="{{ post.date }}">{{ post.date | date(format="%F") }}</time> – {{ post.title -}}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
{{ post.title -}}
|
{{ post.title -}}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts -%}
|
{% for post in posts -%}
|
||||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{post.absolute_path | safe}}">{% if post.date -%}
|
||||||
<time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
|
<time datetime="{{ post.date }}">{{ post.date | date(format="%F") }}</time> - {{ post.title -}}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
{{ post.title -}}
|
{{ post.title -}}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block main -%}
|
{% block main -%}
|
||||||
<h1>Tags</h1>
|
<h2>Tags</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for tag in tags -%}
|
{% for tag in tags -%}
|
||||||
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{tag.absolute_path | safe}}">#{{ tag.slug }}</a></li>
|
<li><a href="{{base_url | trim_end_matches(pat='/') | safe}}{{tag.absolute_path | safe}}">#{{ tag.slug }}</a></li>
|
||||||
|
|
Loading…
Reference in a new issue