diff --git a/404.html b/404.html
new file mode 100644
index 0000000..08d5bf3
--- /dev/null
+++ b/404.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ 404 Not Found
+
+
+ 404 Not Found
+
+
\ No newline at end of file
diff --git a/posts/foldertest/index.md b/posts/foldertest/index.md
index d50de83..7c3a5b0 100644
--- a/posts/foldertest/index.md
+++ b/posts/foldertest/index.md
@@ -1,6 +1,7 @@
+++
title="TOML metadata test"
date=2023-03-26T11:57:00+02:00
+updated=2023-04-03T22:07:57+02:00
+++
hope it works yay
diff --git a/src/feed.rs b/src/feed.rs
new file mode 100644
index 0000000..1f1a138
--- /dev/null
+++ b/src/feed.rs
@@ -0,0 +1,47 @@
+use serde::Serialize;
+use tera::Tera;
+use tracing::instrument;
+use serde_derive::Serialize;
+use color_eyre::Result;
+
+use crate::{post::Post, tag::Tag};
+
+
+#[derive(Serialize)]
+struct FeedContext<'a> {
+ feed_url: &'a str,
+ last_updated: &'a str,
+ tag: Option<&'a Tag>,
+ posts: &'a [&'a Post],
+}
+
+#[instrument(skip(posts, tera))]
+pub fn render_atom_feed(posts: &[&Post], tera: &Tera) -> Result {
+ let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
+ let feed = FeedContext {
+ feed_url: "https://tollyx.net/atom.xml",
+ last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
+ tag: None,
+ posts,
+ };
+
+ let ctx = tera::Context::from_serialize(&feed)?;
+
+ Ok(tera.render("atom.xml", &ctx)?)
+}
+
+#[instrument(skip(tag, posts, tera))]
+pub fn render_atom_tag_feed(tag: &Tag, posts: &[&Post], tera: &Tera) -> Result {
+ let updated = posts.iter().map(|p| p.updated.or(p.date)).max().flatten();
+ let slug = &tag.slug;
+ let feed = FeedContext {
+ feed_url: &format!("https://tollyx.net/tags/{slug}/atom.xml"),
+ last_updated: &updated.map_or_else(String::default, |d| d.to_rfc3339()),
+ tag: Some(tag),
+ posts
+ };
+
+ let ctx = tera::Context::from_serialize(&feed)?;
+
+ Ok(tera.render("atom.xml", &ctx)?)
+}
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 4af9194..184a254 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -9,6 +9,7 @@ use axum::{
use hyper::{header::CONTENT_TYPE, Request, StatusCode};
use lazy_static::lazy_static;
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
+use tower_http::services::ServeFile;
use std::sync::Arc;
use tracing::{instrument, log::*};
@@ -39,9 +40,10 @@ pub fn routes(state: &Arc) -> Router> {
.route("/metrics", get(metrics))
.route_service(
"/posts/:slug/*path",
- tower_http::services::ServeDir::new("./"),
+ tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")),
)
- .route_service("/static/*path", tower_http::services::ServeDir::new("./"))
+ .route_service("/static/*path", tower_http::services::ServeDir::new("./").fallback(ServeFile::new("./404.html")))
+ .fallback_service(ServeFile::new("./404.html"))
}
#[instrument(skip(state))]
@@ -126,6 +128,7 @@ mod tests {
// aliases overlap with themselves or other routes
let posts = crate::post::load_all().await.unwrap();
let state = Arc::new(AppState {
+ base_url: "http://localhost:8180".into(),
tera: tera::Tera::new("templates/**/*").unwrap(),
tags: crate::tag::get_tags(posts.values()),
posts,
diff --git a/src/handlers/posts.rs b/src/handlers/posts.rs
index da6252f..7b88333 100644
--- a/src/handlers/posts.rs
+++ b/src/handlers/posts.rs
@@ -2,10 +2,11 @@ use std::sync::Arc;
use axum::{
extract::{Path, State},
- response::{Html, Redirect},
+ response::{Html, Redirect, IntoResponse},
routing::get,
Router,
};
+use hyper::{header::CONTENT_TYPE, StatusCode};
use serde_derive::Serialize;
use tracing::{instrument, log::*};
@@ -17,6 +18,7 @@ use crate::{
pub fn router() -> Router> {
Router::new()
.route("/posts", get(|| async { Redirect::permanent("/") }))
+ .route("/atom.xml", get(feed))
.route("/posts/", get(index))
.route("/posts/:slug", get(redirect))
.route("/posts/:slug/", get(view))
@@ -80,6 +82,23 @@ pub async fn view(
Ok(Html(res))
}
+pub async fn feed(
+ State(state): State>,
+) -> Result {
+
+ let mut posts: Vec<&Post> = state
+ .posts
+ .values()
+ .filter(|p| p.is_published())
+ .collect();
+
+ posts.sort_by_key(|p| &p.date);
+ posts.reverse();
+ posts.truncate(10);
+
+ Ok((StatusCode::OK, [(CONTENT_TYPE, "application/atom+xml")], crate::feed::render_atom_feed(&posts, &state.tera)?))
+}
+
#[instrument(skip(state))]
pub async fn redirect(
Path(slug): Path,
diff --git a/src/handlers/tags.rs b/src/handlers/tags.rs
index 04c6df8..49cceec 100644
--- a/src/handlers/tags.rs
+++ b/src/handlers/tags.rs
@@ -2,10 +2,11 @@ use std::sync::Arc;
use axum::{
extract::{Path, State},
- response::{Html, Redirect},
+ response::{Html, Redirect, IntoResponse},
routing::get,
Router,
};
+use hyper::{header::CONTENT_TYPE, StatusCode};
use serde_derive::Serialize;
use tracing::instrument;
@@ -17,6 +18,7 @@ pub fn router() -> Router> {
.route("/tags/", get(index))
.route("/tags/:tag", get(redirect))
.route("/tags/:tag/", get(view))
+ .route("/tags/:tag/atom.xml", get(feed))
}
#[derive(Serialize, Debug)]
@@ -65,6 +67,25 @@ pub async fn view(
Ok(Html(res))
}
+pub async fn feed(
+ Path(slug): Path,
+ State(state): State>,
+) -> Result {
+ let tag = state.tags.get(&slug).ok_or(WebsiteError::NotFound)?;
+
+ let mut posts: Vec<&Post> = state
+ .posts
+ .values()
+ .filter(|p| p.is_published() && p.tags.contains(&slug))
+ .collect();
+
+ posts.sort_by_key(|p| &p.date);
+ posts.reverse();
+ posts.truncate(10);
+
+ Ok((StatusCode::OK, [(CONTENT_TYPE, "application/atom+xml")], crate::feed::render_atom_tag_feed(tag, &posts, &state.tera)?))
+}
+
#[instrument(skip(state))]
pub async fn redirect(
Path(slug): Path,
diff --git a/src/main.rs b/src/main.rs
index f5ef2ac..d41a4b4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,8 +15,10 @@ use tracing_subscriber::{prelude::*, EnvFilter};
mod handlers;
mod post;
mod tag;
+mod feed;
pub struct AppState {
+ base_url: String,
posts: HashMap,
tags: HashMap,
tera: Tera,
@@ -29,10 +31,11 @@ async fn main() -> Result<()> {
info!("Starting server...");
+ let base_url = option_env!("SITE_BASE_URL").unwrap_or("http://localhost:8180").to_string();
let tera = Tera::new("templates/**/*")?;
let posts = post::load_all().await?;
let tags = tag::get_tags(posts.values());
- let state = Arc::new(AppState { tera, posts, tags });
+ let state = Arc::new(AppState { base_url, tera, posts, tags });
let app = handlers::routes(&state)
.layer(CorsLayer::permissive())
@@ -56,8 +59,7 @@ async fn main() -> Result<()> {
fn init_tracing() {
let filter = EnvFilter::builder()
.with_default_directive("into".parse().unwrap())
- .from_env_lossy()
- .add_directive("otel=debug".parse().unwrap());
+ .from_env_lossy();
tracing_subscriber::registry()
.with(filter)
diff --git a/src/post.rs b/src/post.rs
index 8ff15ff..ce6d4d6 100644
--- a/src/post.rs
+++ b/src/post.rs
@@ -17,6 +17,7 @@ use crate::WebsiteError;
pub struct TomlFrontMatter {
pub title: String,
pub date: Option,
+ pub updated: Option,
pub draft: Option,
pub aliases: Option>,
pub tags: Option>,
@@ -26,6 +27,7 @@ pub struct TomlFrontMatter {
pub struct Post {
pub title: String,
pub date: Option>,
+ pub updated: Option>,
pub aliases: Vec,
pub tags: Vec,
pub content: String,
@@ -43,6 +45,9 @@ impl Post {
date: fm
.date
.map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
+ updated: fm
+ .updated
+ .map(|d| DateTime::parse_from_rfc3339(&d.to_string()).expect("bad toml datetime")),
aliases: fm.aliases.unwrap_or_default(),
tags: fm.tags.unwrap_or_default(),
}
diff --git a/templates/atom.xml b/templates/atom.xml
new file mode 100644
index 0000000..bec96eb
--- /dev/null
+++ b/templates/atom.xml
@@ -0,0 +1,29 @@
+
+
+ tollyx.net
+ {%- if tag %} - #{{ tag.slug }}{% endif -%}
+
+ tollyx's corner of the web
+
+ {% if tag -%}
+
+ {%- else -%}
+
+ {%- endif %}
+ tollyx-website
+ {{ last_updated | date(format="%+") }}
+ {{ feed_url | safe }}
+ {%- for post in posts %}
+
+ {{ post.title }}
+ {{ post.date | date(format="%+") }}
+ {{ post.updated | default(value=post.date) | date(format="%+") }}
+
+ tollyx
+
+
+ {{ post.slug | safe }}
+ {{ post.content }}
+
+ {%- endfor %}
+
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index a7b6766..f0becdd 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -1,9 +1,9 @@
-{% include "partials/head.html" -%}
+{% include "partials/head.html" %}
- {% include "partials/header.html" -%}
+{% include "partials/header.html" -%}
diff --git a/templates/index.html b/templates/index.html
index d461637..b26941c 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -13,7 +13,7 @@
✅ tests
✅ page aliases (redirects, for back-compat with old routes)
⬜ sass compilation (using rsass? grass?)
- ⬜ rss/atom/jsonfeed
+ ✅ rss/atom/jsonfeed (atom is good enough for now)
✅ proper error handling (i guess??)
⬜ fancy styling
⬜ other pages???
diff --git a/templates/partials/head.html b/templates/partials/head.html
index e81ee41..8891201 100644
--- a/templates/partials/head.html
+++ b/templates/partials/head.html
@@ -2,11 +2,29 @@
+
+ {% if tag -%}
+
+ {%- endif %}
-
+
+
+
+ {% if page -%}
+
+ {%- elif tag -%}
+
+ {%- else -%}
+
+ {%- endif %}
+
+
+
+
+
{% if page.title -%}
{{ page.title }} | tollyx.net
- {% else -%}
+ {%- else -%}
tollyx.net
- {% endif -%}
+ {%- endif %}
\ No newline at end of file
diff --git a/templates/post.html b/templates/post.html
index eaa5430..9c2a23e 100644
--- a/templates/post.html
+++ b/templates/post.html
@@ -6,15 +6,19 @@
{{ page.title }}
{% endif -%}
{% if page.date -%}
- Posted on
- {% endif -%}
+ Posted on
+ {%- if page.updated -%}
+ , Updated
+ {%- endif -%}
+
+ {%- endif %}
{{ page.content | safe -}}
{% if page.tags -%}
-
-
-
- {% endif -%}
+
+
+
+ {%- endif %}
{% endblock main -%}