convert relative to absolute links everywhere
This commit is contained in:
parent
7e2ebc4efb
commit
cbfc505649
17 changed files with 143 additions and 80 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2307,7 +2307,6 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"glob",
|
"glob",
|
||||||
"hyper",
|
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"prometheus",
|
"prometheus",
|
||||||
|
|
|
@ -6,12 +6,12 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.6.12", features = ["http2"] }
|
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"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
hyper = { version = "0.14.19", features = ["full"] }
|
# hyper = { version = "0.14.19", features = ["full"] }
|
||||||
lazy_static = "1.4.0"
|
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"] }
|
||||||
|
|
14
src/feed.rs
14
src/feed.rs
|
@ -24,8 +24,8 @@ pub fn render_atom_feed(state: &AppState) -> Result<String> {
|
||||||
|
|
||||||
let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
|
let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
|
||||||
let feed = FeedContext {
|
let feed = FeedContext {
|
||||||
feed_url: &format!("{}/atom.xml", state.base_url),
|
feed_url: &format!("{}atom.xml", state.base_url),
|
||||||
base_url: &state.base_url,
|
base_url: &state.base_url.to_string(),
|
||||||
last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
|
last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
|
||||||
tag: None,
|
tag: None,
|
||||||
posts: &posts,
|
posts: &posts,
|
||||||
|
@ -51,8 +51,8 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result<String> {
|
||||||
let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
|
let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
|
||||||
let slug = &tag.slug;
|
let slug = &tag.slug;
|
||||||
let feed = FeedContext {
|
let feed = FeedContext {
|
||||||
feed_url: &format!("{}/tags/{}/atom.xml", state.base_url, slug),
|
feed_url: &format!("{}tags/{}/atom.xml", state.base_url, slug),
|
||||||
base_url: &state.base_url,
|
base_url: &state.base_url.to_string(),
|
||||||
last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
|
last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
|
||||||
tag: Some(tag),
|
tag: Some(tag),
|
||||||
posts: &posts,
|
posts: &posts,
|
||||||
|
@ -63,14 +63,14 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result<String> {
|
||||||
Ok(state.tera.render("atom.xml", &ctx)?)
|
Ok(state.tera.render("atom.xml", &ctx)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JsonFeed<'a> {
|
struct _JsonFeed<'a> {
|
||||||
version: &'a str,
|
version: &'a str,
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
home_page_url: &'a str,
|
home_page_url: &'a str,
|
||||||
feed_url: &'a str,
|
feed_url: &'a str,
|
||||||
items: Vec<JsonFeedItem<'a>>,
|
items: Vec<_JsonFeedItem<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JsonFeedItem<'a> {
|
struct _JsonFeedItem<'a> {
|
||||||
id: &'a str,
|
id: &'a str,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
body,
|
body,
|
||||||
extract::State,
|
extract::State,
|
||||||
|
http::{header, HeaderMap, Request, StatusCode},
|
||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
use hyper::{
|
|
||||||
header::{self, CONTENT_TYPE},
|
|
||||||
HeaderMap, Request, StatusCode,
|
|
||||||
};
|
|
||||||
use lazy_static::lazy_static;
|
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;
|
||||||
|
@ -59,7 +56,11 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
|
headers: HeaderMap,
|
||||||
) -> std::result::Result<Response, WebsiteError> {
|
) -> std::result::Result<Response, WebsiteError> {
|
||||||
|
if should_return_304(&headers, Some(state.startup_time.into())) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
let ctx = tera::Context::new();
|
let ctx = tera::Context::new();
|
||||||
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);
|
||||||
|
@ -85,7 +86,7 @@ async fn metrics() -> impl IntoResponse {
|
||||||
|
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(200)
|
.status(200)
|
||||||
.header(CONTENT_TYPE, encoder.format_type())
|
.header(header::CONTENT_TYPE, encoder.format_type())
|
||||||
.body(body::boxed(body::Full::from(buffer)))
|
.body(body::boxed(body::Full::from(buffer)))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
@ -165,16 +166,18 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn setup_routes() {
|
async fn setup_routes() {
|
||||||
|
let mut state = AppState {
|
||||||
|
startup_time: chrono::offset::Utc::now(),
|
||||||
|
base_url: "http://localhost:8180".parse().unwrap(),
|
||||||
|
tera: tera::Tera::new("templates/**/*").unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
// Load the actual posts, just to make this test fail if
|
// Load the actual posts, just to make this test fail if
|
||||||
// aliases overlap with themselves or other routes
|
// aliases overlap with themselves or other routes
|
||||||
let posts = crate::post::load_all().await.unwrap();
|
let posts = crate::post::load_all(&state).await.unwrap();
|
||||||
let state = Arc::new(AppState {
|
state.tags = crate::tag::get_tags(posts.values());
|
||||||
startup_time: chrono::offset::Utc::now(),
|
state.posts = posts;
|
||||||
base_url: "http://localhost:8180".into(),
|
let state = Arc::new(state);
|
||||||
tera: tera::Tera::new("templates/**/*").unwrap(),
|
|
||||||
tags: crate::tag::get_tags(posts.values()),
|
|
||||||
posts,
|
|
||||||
});
|
|
||||||
|
|
||||||
super::routes(&state).with_state(state).into_make_service();
|
super::routes(&state).with_state(state).into_make_service();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,12 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
|
http::{header, HeaderMap, StatusCode},
|
||||||
response::{Html, IntoResponse, Redirect, Response},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use hyper::{
|
|
||||||
header::{self, CONTENT_TYPE},
|
|
||||||
HeaderMap, StatusCode,
|
|
||||||
};
|
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use tracing::{instrument, log::warn};
|
use tracing::{instrument, log::warn};
|
||||||
|
|
||||||
|
@ -74,7 +72,7 @@ pub async fn index(
|
||||||
let mut c = tera::Context::new();
|
let mut c = tera::Context::new();
|
||||||
c.insert("page", &ctx);
|
c.insert("page", &ctx);
|
||||||
c.insert("posts", &posts);
|
c.insert("posts", &posts);
|
||||||
c.insert("base_url", &state.base_url);
|
c.insert("base_url", &state.base_url.to_string());
|
||||||
|
|
||||||
let res = state.tera.render("posts_index.html", &c)?;
|
let res = state.tera.render("posts_index.html", &c)?;
|
||||||
|
|
||||||
|
@ -152,7 +150,7 @@ pub async fn feed(
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(CONTENT_TYPE, "application/atom+xml"),
|
(header::CONTENT_TYPE, "application/atom+xml"),
|
||||||
(
|
(
|
||||||
header::LAST_MODIFIED,
|
header::LAST_MODIFIED,
|
||||||
&last_changed.map_or_else(
|
&last_changed.map_or_else(
|
||||||
|
|
|
@ -2,14 +2,12 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
|
http::{header, HeaderMap, StatusCode},
|
||||||
response::{Html, IntoResponse, Redirect, Response},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use hyper::{
|
|
||||||
header::{self, CONTENT_TYPE},
|
|
||||||
HeaderMap, StatusCode,
|
|
||||||
};
|
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
@ -122,7 +120,7 @@ pub async fn feed(
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(CONTENT_TYPE, "application/atom+xml"),
|
(header::CONTENT_TYPE, "application/atom+xml"),
|
||||||
(
|
(
|
||||||
header::LAST_MODIFIED,
|
header::LAST_MODIFIED,
|
||||||
&last_changed.map_or_else(
|
&last_changed.map_or_else(
|
||||||
|
|
33
src/helpers.rs
Normal file
33
src/helpers.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use axum::http::{uri, Uri};
|
||||||
|
|
||||||
|
pub fn uri_with_path(uri: &Uri, path: &str) -> Uri {
|
||||||
|
if path.starts_with('/') {
|
||||||
|
// 'path' is an root path, so let's just override the uri's path
|
||||||
|
return uri::Builder::new()
|
||||||
|
.scheme(uri.scheme_str().unwrap())
|
||||||
|
.authority(uri.authority().unwrap().as_str())
|
||||||
|
.path_and_query(path)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'path' is a relative/local path, so let's combine it with the uri's path
|
||||||
|
let base_path = uri.path_and_query().map_or("/", |p| p.path());
|
||||||
|
|
||||||
|
if base_path.ends_with('/') {
|
||||||
|
return uri::Builder::new()
|
||||||
|
.scheme(uri.scheme_str().unwrap())
|
||||||
|
.authority(uri.authority().unwrap().as_str())
|
||||||
|
.path_and_query(format!("{base_path}{path}"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let (base, _) = base_path.rsplit_once('/').unwrap();
|
||||||
|
return uri::Builder::new()
|
||||||
|
.scheme(uri.scheme_str().unwrap())
|
||||||
|
.authority(uri.authority().unwrap().as_str())
|
||||||
|
.path_and_query(format!("{base}/{path}"))
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
}
|
45
src/main.rs
45
src/main.rs
|
@ -2,11 +2,15 @@
|
||||||
#![allow(clippy::unused_async)] // axum handlers needs async, even if no awaiting happens
|
#![allow(clippy::unused_async)] // axum handlers needs async, even if no awaiting happens
|
||||||
use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
|
use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use axum::extract::MatchedPath;
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
extract::{MatchedPath, OriginalUri},
|
||||||
|
http::{uri::PathAndQuery, Request, Uri},
|
||||||
|
response::Response,
|
||||||
|
};
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use color_eyre::eyre::{Error, Result};
|
use color_eyre::eyre::{Error, Result};
|
||||||
|
|
||||||
use hyper::{Body, Request, Response};
|
|
||||||
use post::Post;
|
use post::Post;
|
||||||
|
|
||||||
use tag::Tag;
|
use tag::Tag;
|
||||||
|
@ -18,6 +22,7 @@ use tracing_subscriber::{prelude::*, EnvFilter};
|
||||||
|
|
||||||
mod feed;
|
mod feed;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
mod helpers;
|
||||||
mod hilighting;
|
mod hilighting;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
mod post;
|
mod post;
|
||||||
|
@ -26,7 +31,7 @@ mod tag;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
startup_time: DateTime<chrono::offset::Utc>,
|
startup_time: DateTime<chrono::offset::Utc>,
|
||||||
base_url: String,
|
base_url: Uri,
|
||||||
posts: HashMap<String, Post>,
|
posts: HashMap<String, Post>,
|
||||||
tags: HashMap<String, Tag>,
|
tags: HashMap<String, Tag>,
|
||||||
tera: Tera,
|
tera: Tera,
|
||||||
|
@ -39,19 +44,22 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
|
|
||||||
let base_url = option_env!("SITE_BASE_URL")
|
let base_url: Uri = option_env!("SITE_BASE_URL")
|
||||||
.unwrap_or("http://localhost:8080")
|
.unwrap_or("http://localhost:8080")
|
||||||
.to_string();
|
.parse()
|
||||||
|
.unwrap();
|
||||||
let tera = Tera::new("templates/**/*")?;
|
let tera = Tera::new("templates/**/*")?;
|
||||||
let posts = post::load_all().await?;
|
let mut state = AppState {
|
||||||
let tags = tag::get_tags(posts.values());
|
|
||||||
let state = Arc::new(AppState {
|
|
||||||
startup_time: chrono::offset::Utc::now(),
|
startup_time: chrono::offset::Utc::now(),
|
||||||
base_url,
|
base_url,
|
||||||
tera,
|
tera,
|
||||||
posts,
|
..Default::default()
|
||||||
tags,
|
};
|
||||||
});
|
let posts = post::load_all(&state).await?;
|
||||||
|
let tags = tag::get_tags(posts.values());
|
||||||
|
state.posts = posts;
|
||||||
|
state.tags = tags;
|
||||||
|
let state = Arc::new(state);
|
||||||
|
|
||||||
let app = handlers::routes(&state)
|
let app = handlers::routes(&state)
|
||||||
.layer(CorsLayer::permissive())
|
.layer(CorsLayer::permissive())
|
||||||
|
@ -84,18 +92,19 @@ fn init_tracing() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_span(request: &Request<Body>) -> Span {
|
fn make_span(request: &Request<Body>) -> Span {
|
||||||
let uri = request.uri();
|
let uri = if let Some(OriginalUri(uri)) = request.extensions().get::<OriginalUri>() {
|
||||||
|
uri
|
||||||
|
} else {
|
||||||
|
request.uri()
|
||||||
|
};
|
||||||
let route = request
|
let route = request
|
||||||
.extensions()
|
.extensions()
|
||||||
.get::<MatchedPath>()
|
.get::<MatchedPath>()
|
||||||
.map(axum::extract::MatchedPath::as_str)
|
.map_or(uri.path(), axum::extract::MatchedPath::as_str);
|
||||||
.unwrap_or_default();
|
|
||||||
let method = request.method().as_str();
|
let method = request.method().as_str();
|
||||||
let target = uri
|
let target = uri
|
||||||
.path_and_query()
|
.path_and_query()
|
||||||
.map(axum::http::uri::PathAndQuery::as_str)
|
.map_or(uri.path(), PathAndQuery::as_str);
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let name = format!("{method} {route}");
|
let name = format!("{method} {route}");
|
||||||
|
|
||||||
info_span!(
|
info_span!(
|
||||||
|
@ -108,7 +117,7 @@ fn make_span(request: &Request<Body>) -> Span {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_response<B>(response: &Response<B>, _latency: Duration, span: &Span) {
|
fn on_response(response: &Response, _latency: Duration, span: &Span) {
|
||||||
span.record("http.status_code", response.status().as_str());
|
span.record("http.status_code", response.status().as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
use crate::helpers;
|
||||||
|
use crate::hilighting;
|
||||||
|
use axum::http::Uri;
|
||||||
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 crate::hilighting;
|
pub fn render_markdown_to_html(base_uri: Option<&Uri>, markdown: &str) -> String {
|
||||||
|
|
||||||
pub fn render_markdown_to_html(markdown: &str) -> String {
|
|
||||||
let options = Options::all();
|
let options = Options::all();
|
||||||
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, options);
|
||||||
|
@ -22,6 +23,24 @@ pub fn render_markdown_to_html(markdown: &str) -> String {
|
||||||
events.push(Event::Text(text));
|
events.push(Event::Text(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Start(Tag::Link(t, mut link, title)) => {
|
||||||
|
if let Some(uri) = base_uri {
|
||||||
|
if !link.contains("://") && !link.contains('@') {
|
||||||
|
// convert relative URIs to absolute URIs
|
||||||
|
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.push(Event::Start(Tag::Link(t, link, title)));
|
||||||
|
}
|
||||||
|
Event::Start(Tag::Image(t, mut link, title)) => {
|
||||||
|
if let Some(uri) = base_uri {
|
||||||
|
if !link.contains("://") && !link.contains('@') {
|
||||||
|
// convert relative URIs to absolute URIs
|
||||||
|
link = helpers::uri_with_path(uri, &link).to_string().into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events.push(Event::Start(Tag::Image(t, link, title)));
|
||||||
|
}
|
||||||
Event::Start(Tag::CodeBlock(kind)) => {
|
Event::Start(Tag::CodeBlock(kind)) => {
|
||||||
code_block = true;
|
code_block = true;
|
||||||
if let pulldown_cmark::CodeBlockKind::Fenced(lang) = kind {
|
if let pulldown_cmark::CodeBlockKind::Fenced(lang) = kind {
|
||||||
|
|
24
src/post.rs
24
src/post.rs
|
@ -14,7 +14,7 @@ use tracing::{
|
||||||
log::{debug, warn},
|
log::{debug, warn},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{markdown, AppState, WebsiteError};
|
use crate::{helpers, markdown, AppState, WebsiteError};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Default)]
|
#[derive(Deserialize, Debug, Default)]
|
||||||
pub struct TomlFrontMatter {
|
pub struct TomlFrontMatter {
|
||||||
|
@ -69,8 +69,8 @@ impl Post {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument(skip(state))]
|
||||||
pub async fn load_all() -> color_eyre::eyre::Result<HashMap<String, Post>> {
|
pub async fn load_all(state: &AppState) -> color_eyre::eyre::Result<HashMap<String, Post>> {
|
||||||
let mut res = HashMap::<String, Post>::new();
|
let mut res = HashMap::<String, Post>::new();
|
||||||
for path in glob("posts/**/*.md")? {
|
for path in glob("posts/**/*.md")? {
|
||||||
let path = path.unwrap();
|
let path = path.unwrap();
|
||||||
|
@ -87,15 +87,15 @@ pub async fn load_all() -> color_eyre::eyre::Result<HashMap<String, Post>> {
|
||||||
.trim_end_matches('\\')
|
.trim_end_matches('\\')
|
||||||
.trim_end_matches('/');
|
.trim_end_matches('/');
|
||||||
|
|
||||||
let post = load_post(slug).await?;
|
let post = load_post(state, slug).await?;
|
||||||
|
|
||||||
res.insert(slug.to_string(), post);
|
res.insert(slug.to_string(), post);
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument(skip(state))]
|
||||||
pub async fn load_post(slug: &str) -> color_eyre::eyre::Result<Post> {
|
pub async fn load_post(state: &AppState, slug: &str) -> color_eyre::eyre::Result<Post> {
|
||||||
debug!("loading post: {slug}");
|
debug!("loading post: {slug}");
|
||||||
|
|
||||||
let file_path = Path::new("posts").join(slug);
|
let file_path = Path::new("posts").join(slug);
|
||||||
|
@ -109,7 +109,9 @@ pub async fn load_post(slug: &str) -> color_eyre::eyre::Result<Post> {
|
||||||
let (tomlfm, content) = parse_frontmatter(content)?;
|
let (tomlfm, content) = parse_frontmatter(content)?;
|
||||||
let tomlfm = tomlfm.expect("Missing frontmatter");
|
let tomlfm = tomlfm.expect("Missing frontmatter");
|
||||||
|
|
||||||
let content = content.map(|c| markdown::render_markdown_to_html(&c));
|
let base_uri = helpers::uri_with_path(&state.base_url, &format!("/posts/{slug}/"));
|
||||||
|
|
||||||
|
let content = content.map(|c| markdown::render_markdown_to_html(Some(&base_uri), &c));
|
||||||
|
|
||||||
Ok(Post::new(
|
Ok(Post::new(
|
||||||
slug.to_string(),
|
slug.to_string(),
|
||||||
|
@ -143,7 +145,7 @@ fn parse_frontmatter(
|
||||||
pub async fn render_post(state: &AppState, post: &Post) -> Result<String, WebsiteError> {
|
pub async fn render_post(state: &AppState, post: &Post) -> Result<String, WebsiteError> {
|
||||||
let mut ctx = tera::Context::new();
|
let mut ctx = tera::Context::new();
|
||||||
ctx.insert("page", &post);
|
ctx.insert("page", &post);
|
||||||
ctx.insert("base_url", &state.base_url);
|
ctx.insert("base_url", &state.base_url.to_string());
|
||||||
|
|
||||||
state
|
state
|
||||||
.tera
|
.tera
|
||||||
|
@ -159,12 +161,12 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn render_all_posts() {
|
async fn render_all_posts() {
|
||||||
let state = AppState {
|
let mut state = AppState {
|
||||||
base_url: "localhost:8180".into(),
|
base_url: "localhost:8180".parse().unwrap(),
|
||||||
posts: super::load_all().await.unwrap(),
|
|
||||||
tera: Tera::new("templates/**/*").unwrap(),
|
tera: Tera::new("templates/**/*").unwrap(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<subtitle>tollyx's corner of the web</subtitle>
|
<subtitle>tollyx's corner of the web</subtitle>
|
||||||
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
|
<link href="{{ feed_url | safe }}" rel="self" type="application/atom+xml"/>
|
||||||
{% if tag -%}
|
{% if tag -%}
|
||||||
<link href="{{ base_url | safe }}/tags/{{ tag.slug }}/"/>
|
<link href="{{ base_url | safe }}tags/{{ tag.slug }}/"/>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<link href="{{ base_url | safe }}"/>
|
<link href="{{ base_url | safe }}"/>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
@ -21,9 +21,11 @@
|
||||||
<author>
|
<author>
|
||||||
<name>tollyx</name>
|
<name>tollyx</name>
|
||||||
</author>
|
</author>
|
||||||
<link rel="alternate" href="{{ base_url | safe }}{{ post.absolute_path | safe }}" type="text/html"/>
|
<link rel="alternate" href="{{ base_url | trim_end_matches(pat='/') | safe }}{{ post.absolute_path | safe }}" type="text/html"/>
|
||||||
<id>{{ post.slug | safe }}</id>
|
<id>{{ base_url | trim_end_matches(pat='/') | safe }}{{ post.absolute_path | safe }}</id>
|
||||||
<content type="html">{{ post.content }}</content>
|
<content type="html">
|
||||||
|
{{ post.content }}
|
||||||
|
</content>
|
||||||
</entry>
|
</entry>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</feed>
|
</feed>
|
|
@ -2,12 +2,12 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="alternate" type="application/rss+xml" href="/atom.xml" title="tollyx.net">
|
<link rel="alternate" type="application/rss+xml" href="{{base_url | safe}}atom.xml" title="tollyx.net">
|
||||||
{% if tag_slug -%}
|
{% if tag_slug -%}
|
||||||
<link rel="alternate" type="application/rss+xml" href="/tags/{{ tag_slug }}/atom.xml" title="tollyx.net: Posts tagged #{{ tag_slug }}">
|
<link rel="alternate" type="application/rss+xml" href="{{base_url | safe}}tags/{{ tag_slug }}/atom.xml" title="tollyx.net: Posts tagged #{{ tag_slug }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<link rel="stylesheet" href="/static/site.css">
|
<link rel="stylesheet" href="{{base_url | safe}}static/site.css">
|
||||||
<link rel="icon" type="image/png" href="/static/avatar.png" />
|
<link rel="icon" type="image/png" href="{{base_url | safe}}static/avatar.png" />
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:site_name" content="tollyx.net" />
|
<meta property="og:site_name" content="tollyx.net" />
|
||||||
{% if page -%}
|
{% if page -%}
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
<meta property="og:title" content="tollyx.net" />
|
<meta property="og:title" content="tollyx.net" />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<meta property="og:image" content="/avatar.png" />
|
<meta property="og:image" content="{{base_url | safe}}avatar.png" />
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@tollyx" />
|
<meta name="twitter:site" content="@tollyx" />
|
||||||
<meta name="twitter:creator" content="@tollyx" />
|
<meta name="twitter:creator" content="@tollyx" />
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<nav>
|
<nav>
|
||||||
<img src="/static/avatar.png" class="avatar"/> <a href="/">tollyx</a> - <a href="/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>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
{% if page.tags -%}
|
{% if page.tags -%}
|
||||||
<small>
|
<small>
|
||||||
<ul class="tags">
|
<ul class="tags">
|
||||||
{% for tag in page.tags %}<li><a href="/tags/{{tag}}/">#{{ tag }}</a></li>{% endfor %}
|
{% for tag in page.tags %}<li><a href="{{base_url | safe}}tags/{{tag}}/">#{{ tag }}</a></li>{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</small>
|
</small>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p>I occasionally write some stuff, it's quite rare but it does happen believe it or not.</p>
|
<p>I occasionally write some stuff, it's quite rare but it does happen believe it or not.</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts -%}
|
{% for post in posts -%}
|
||||||
<li><a href="{{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="%Y-%m-%d") }}</time> - {{ post.title -}}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
{{ post.title -}}
|
{{ post.title -}}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
<ul>
|
<ul>
|
||||||
{% for post in posts -%}
|
{% for post in posts -%}
|
||||||
<li><a href="{{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="%Y-%m-%d") }}</time> - {{ post.title -}}
|
||||||
{% else -%}
|
{% else -%}
|
||||||
{{ post.title -}}
|
{{ post.title -}}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<h1>Tags</h1>
|
<h1>Tags</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% for tag in tags -%}
|
{% for tag in tags -%}
|
||||||
<li><a href="{{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>
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
</ul>
|
</ul>
|
||||||
{% endblock main -%}
|
{% endblock main -%}
|
||||||
|
|
Loading…
Reference in a new issue