diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eb205fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.vscode +target diff --git a/.vscode/settings.json b/.vscode/settings.json index 718e72c..96e527c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,6 @@ "sv" ], "spellright.documentTypes": [ - "latex", - "plaintext" + "latex" ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index fa02fd6..2c9346b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ regex = "1.7.2" serde = "1.0.144" serde_derive = "1.0.144" serde_json = "1.0.85" -tera = "1.17.0" +tera = { version = "1.17.0", features = ["builtins"] } tokio = { version = "1.19.2", features = ["full"] } toml = "0.7.3" tower = { version = "0.4.12", features = ["full"] } diff --git a/Containerfile b/Containerfile deleted file mode 100644 index 085e7fd..0000000 --- a/Containerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM rust:slim as build-env -WORKDIR /app -COPY . /app -RUN cargo build --release - -FROM gcr.io/distroless/cc -COPY --from=build-env /app/target/release/website / -COPY --from=build-env /templates / -COPY --from=build-env /posts / -COPY --from=build-env /static / -CMD ["./website"] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..654e314 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +FROM rust:slim AS chef +RUN cargo install cargo-chef +WORKDIR app + +#################################################################################################### +## Planner +#################################################################################################### +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +#################################################################################################### +## Builder +#################################################################################################### +FROM chef AS builder + +RUN rustup target add x86_64-unknown-linux-musl +RUN apt update && apt install -y musl-tools musl-dev +RUN update-ca-certificates + +# Create appuser +ENV USER=website +ENV UID=10001 + +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + "${USER}" + +WORKDIR /app + +COPY --from=planner /app/recipe.json . +RUN cargo chef cook --release --recipe-path recipe.json + +COPY . . + +RUN cargo build --target x86_64-unknown-linux-musl --release + +#################################################################################################### +## Final image +#################################################################################################### +FROM scratch + +# Import from builder. +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /etc/group /etc/group + +WORKDIR /app + +# Copy our build +COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/website ./ +COPY --from=builder /app/posts ./posts +COPY --from=builder /app/static ./static +COPY --from=builder /app/templates ./templates + +EXPOSE 8180 + +# Use an unprivileged user. +USER website:website + +CMD ["./website"] \ No newline at end of file diff --git a/posts/dungeon/index.md b/posts/dungeon/index.md new file mode 100644 index 0000000..1454952 --- /dev/null +++ b/posts/dungeon/index.md @@ -0,0 +1,49 @@ ++++ +date = 2018-01-10T17:50:00+01:00 +draft = false +title = "Yet another (traditional) roguelike written in c++" +aliases = ["/blog/dungeon/", "/blog/dungeon.html"] + +[taxonomies] +tags = ["cpp", "roguelike"] ++++ + +![work-in-progress screenshot](screenshot.png) + +My current go-to pet project is [dungeon](https://github.com/tollyx/dungeon), a + roguelike which is currently pretty incomplete and basic, but I think I've + gotten far enough to start showing it off a bit more. + +It's a roguelike with your usual fantasy-setting (for now, anyway) + and mechanically it's going to be pretty typical as well. Eventually though, + I'm planning on implementing some kind of bodypart system where potions, food, + traps, enemy attacks and so on can transform your character, giving you both + positive and negative effects. + +Also, Lua scripting. I want as much as possible outside the main game engine be + scriptable in Lua. Which means items, status effects, skills, traps, actors and + whatever else that might be fun to have easily modifiable. I'm also using Lua + for basic data loading but that's just because I'm lazy and why the hell not + when Lua is already implemented. + +The main things that are currently missing are: + +- Menus +- In-game UI (including a log) +- Items (with lua-scripted effects) +- Loading enemies from data + +After those and a bit more content I'll be implementing whatever cool ideas that + I come up with. + +I'm mainly making it for learning purposes (and for fun), trying out new things + as I go. That lack of planning combined with not actually spending too much + time on it means that progress is slow. The fact that I find myself rewriting + things quite often due to the mentioned lack of planning doesn't help much, + either. + +I'll try to write blog posts about whatever in the game that might be + interesting for others, but it will probably take a while before they pop up as + the game is currently pretty basic and will stay so for a while longer. + +[You can find the source on github](https://github.com/tollyx/dungeon). diff --git a/posts/dungeon/screenshot.png b/posts/dungeon/screenshot.png new file mode 100644 index 0000000..1067b9a Binary files /dev/null and b/posts/dungeon/screenshot.png differ diff --git a/posts/foldertest/index.md b/posts/foldertest/index.md index 4409aa0..45721db 100644 --- a/posts/foldertest/index.md +++ b/posts/foldertest/index.md @@ -3,8 +3,6 @@ title="TOML metadata test" date=2023-03-25T14:50:25+01:00 +++ -# Testing post as index within folder - hope it works yay here have a squid miku to test relative paths: diff --git a/posts/foldertest/nestedpost.md b/posts/foldertest/nestedpost.md deleted file mode 100644 index 77a8a06..0000000 --- a/posts/foldertest/nestedpost.md +++ /dev/null @@ -1,9 +0,0 @@ -+++ -title='nested post test does work!' -date=2022-03-25T14:50:25+01:00 -+++ -# yet again a nested post test - -will it work this time, at least with the slug?? - -how about we go [even deeper!!](evendeeper/) diff --git a/posts/foldertest/nestedpost/evendeeper.md b/posts/foldertest/nestedpost/evendeeper.md deleted file mode 100644 index 3449497..0000000 --- a/posts/foldertest/nestedpost/evendeeper.md +++ /dev/null @@ -1,7 +0,0 @@ -+++ -title='how about even deeper??' -date=2024-03-25T14:50:25+01:00 -+++ -# WOWOAOWOFAODWAOWOAWAOWA - -SOOO DEEP BRO diff --git a/posts/hello-world.md b/posts/hello-world.md new file mode 100644 index 0000000..0c488bc --- /dev/null +++ b/posts/hello-world.md @@ -0,0 +1,11 @@ ++++ +date = 2017-06-25T22:40:00+01:00 +draft = false +title = "Hello world, again" +aliases = ["/blog/hello-world/", "/blog/hello-world.html"] +tags = ["hugo", "jekyll"] ++++ + +So I've yet again remade this website. This time I've moved from [jekyll](https://jekyllrb.com/) to [hugo](https://gohugo.io/), because I've had to reinstall ruby far too many times and there's always some issue with it preventing me from just getting it up and running so I can update this site. Hugo is just a binary, no need to install anything to get it to run. It's far less of a hassle. + +If you for whatever reason want to see the old posts (which were basically just dev logs required for some of my school courses), you can find them [here.](@/old/_index.md) diff --git a/posts/status-update-2020-05-16/index.md b/posts/status-update-2020-05-16/index.md new file mode 100644 index 0000000..ce61a06 --- /dev/null +++ b/posts/status-update-2020-05-16/index.md @@ -0,0 +1,24 @@ ++++ +date = 2020-05-16T13:43:00+02:00 +draft = false +title = 'Status update: 2020-05-16' +tags = ["hugo", "zola", "roguelike", "spork"] ++++ + +Oh boy, it's been a while since I wrote anything on this website, over two years ago! + +But I've been thinking of starting to write posts more often, try this proggramming-blogging thing out that seems to have had some sort of resurgance. I don't know if I have anything others will find interesting to read, but I've got a few drafts lying around that I'm gonna try finishing up. We'll see how it goes. Just don't expect me to post things too often - maybe once or twice a month, at most. + +And with that, I've moved the website to yet another static website generator, this time I'm using [zola]. Why? It's written in rust. That's pretty much all I got. It's actually very similar to [hugo], which I used previously, but I don't know - zola kinda feels nicer to work with when it comes to templates/themes? + +My latest free-time project I've been working on is yet another roguelike - this time, I'm following a tutorial so that I won't get stuck for too long figuring out architecture stuff. It's being written in rust and the tutorial I'm following is [this one][roguelike-tutorial]. It's pretty great - it's using [specs] which I've tried out multiple times but I never really got a good hang of figuring out how to do stuff with an ECS, so learning more about that has been nice. But I did the stupid mistake of not writing down some small errors in the tutorial, so I'll probably go back and try to find them again and open a few PR's for them. I'll try to write a post or two about the roguelike here as well. But for now, [you can find the source code for it over here][roguelike-src]. + +In other news, I bought myself an apartment. I'm moving at the start of June, so that's pretty exciting. + +Also, in case someone actually got my rss feed in their reader - sorry, it broke again when I switched to zola, and from what I've read it'll break again in version 0.11 due to a change I agree with. You'll have to fix the feed link, twice. Again, sorry about that. + +[zola]: https://getzola.org +[hugo]: https://gohugo.io +[roguelike-tutorial]: https://bfnightly.bracketproductions.com/rustbook/chapter_0.html +[specs]: https://crates.io/crates/specs +[roguelike-src]: https://gitlab.com/tollyx/roguelike-tutorial-rs diff --git a/posts/test.md b/posts/test.md deleted file mode 100644 index 1fbf5a9..0000000 --- a/posts/test.md +++ /dev/null @@ -1,7 +0,0 @@ -+++ -title="test page please ignore" -date=2023-03-25T15:16:10+01:00 -+++ -# Test page please ignore - -Hello world! diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 17cbcd8..095dd29 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -7,43 +7,26 @@ use axum::{ Extension, }; -use crate::State; - -#[derive(Debug)] -pub enum Error { - NotFound, - InternalError(anyhow::Error), -} - -impl From for Error -where - E: Into, -{ - fn from(value: E) -> Self { - Error::InternalError(value.into()) - } -} - -pub type Result>> = std::result::Result; +use crate::{State, WebsiteError}; #[instrument(skip(state))] -pub async fn index(Extension(state): Extension>) -> Result { +pub async fn index(Extension(state): Extension>) -> std::result::Result>, WebsiteError> { let ctx = tera::Context::new(); let res = state.tera.render("index.html", &ctx).map_err(|e| { error!("Failed rendering index: {}", e); - Error::NotFound + WebsiteError::NotFound })?; Ok(Html(res.into())) } -impl IntoResponse for Error { +impl IntoResponse for WebsiteError { fn into_response(self) -> Response { match self { - Error::NotFound => { + WebsiteError::NotFound => { info!("not found"); (StatusCode::NOT_FOUND, ()).into_response() } - Error::InternalError(e) => { + WebsiteError::InternalError(e) => { error!("internal error: {e}"); (StatusCode::INTERNAL_SERVER_ERROR, ()).into_response() } diff --git a/src/main.rs b/src/main.rs index d6e4a9f..426ef26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,8 +37,6 @@ pub async fn init_app() -> Result { let tera = Tera::new("templates/**/*")?; let posts = post::load_all().await?; - let posts_router = post::build_router(posts.values()); - let state = Arc::new(State { tera, posts }); let middleware = tower::ServiceBuilder::new() @@ -50,10 +48,30 @@ pub async fn init_app() -> Result { .route("/", get(handlers::index)) .nest( "/posts", - posts_router.fallback_service(tower_http::services::ServeDir::new("./posts")), + post::router(), ) .nest_service("/static", tower_http::services::ServeDir::new("./static")) + .route("/.healthcheck", get(healthcheck)) .layer(middleware); Ok(app) } + +async fn healthcheck() -> &'static str { + "OK" +} + +#[derive(Debug)] +pub enum WebsiteError { + NotFound, + InternalError(anyhow::Error), +} + +impl From for WebsiteError +where + E: Into, +{ + fn from(value: E) -> Self { + WebsiteError::InternalError(value.into()) + } +} diff --git a/src/post.rs b/src/post.rs index 1222015..7b9dbf4 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, path::Path, sync::Arc}; -use axum::{response::Html, routing::get, Extension, Router}; +use axum::{response::Html, routing::get, Extension, Router, extract}; use chrono::{DateTime, FixedOffset}; use glob::glob; -use hyper::Uri; use lazy_static::lazy_static; use pulldown_cmark::{html, Options, Parser}; use regex::Regex; @@ -13,26 +12,40 @@ use tokio::fs; use tracing::{instrument, log::*}; -use crate::{handlers, State}; +use crate::{State, WebsiteError}; -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct FrontMatter { - pub title: String, - pub date: DateTime, -} - -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, Default)] pub struct TomlFrontMatter { pub title: String, - pub date: toml::value::Datetime, + pub date: Option, + pub draft: Option, + pub aliases: Option>, + pub tags: Option>, } #[derive(Serialize, Clone, Debug)] pub struct Post { + pub title: String, + pub date: Option>, + pub aliases: Vec, + pub tags: Vec, pub content: String, pub slug: String, pub absolute_path: String, - pub frontmatter: FrontMatter, +} + +impl Post { + pub fn new(slug: String, content: String, fm: TomlFrontMatter) -> Post { + Post { + absolute_path: format!("/posts/{}/", slug), + slug, + content, + title: fm.title, + date: fm.date.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(), + } + } } #[instrument] @@ -42,26 +55,26 @@ pub async fn load_all() -> color_eyre::eyre::Result> { let path = path.unwrap(); debug!("found page: {}", path.display()); - let post = load_post(&path.to_string_lossy()).await?; + let path = path.to_string_lossy().replace('\\', "/"); + let slug = path + .trim_start_matches("posts") + .trim_start_matches('/') + .trim_start_matches('\\') + .trim_end_matches(".html") + .trim_end_matches(".md") + .trim_end_matches("index") + .trim_end_matches('\\') + .trim_end_matches('/'); - res.insert(post.slug.clone(), post); + let post = load_post(slug).await?; + + res.insert(slug.to_string(), post); } Ok(res) } #[instrument] -pub async fn load_post(path: &str) -> color_eyre::eyre::Result { - let path = path.replace('\\', "/"); - let slug = path - .trim_start_matches("posts") - .trim_start_matches('/') - .trim_start_matches('\\') - .trim_end_matches(".html") - .trim_end_matches(".md") - .trim_end_matches("index") - .trim_end_matches('\\') - .trim_end_matches('/'); - +pub async fn load_post(slug: &str) -> color_eyre::eyre::Result { debug!("loading post: {slug}"); let file_path = Path::new("posts").join(slug); @@ -83,25 +96,7 @@ pub async fn load_post(path: &str) -> color_eyre::eyre::Result { content_html }); - let date = toml_date_to_chrono(tomlfm.date)?; - - let frontmatter = FrontMatter { - title: tomlfm.title, - date, - }; - - Ok(Post { - absolute_path: format!("/posts/{}/", slug), - slug: slug.to_string(), - content: content.unwrap_or_default(), - frontmatter, - }) -} - -fn toml_date_to_chrono( - toml: toml::value::Datetime, -) -> color_eyre::eyre::Result> { - Ok(DateTime::parse_from_rfc3339(&toml.to_string())?) + Ok(Post::new(slug.to_string(), content.unwrap_or_default(), tomlfm)) } #[instrument] @@ -125,44 +120,34 @@ fn parse_frontmatter( }) } -#[instrument(skip(state, post))] -async fn render_post(state: &State, post: &Post) -> Result { +#[instrument(skip(tera, post))] +async fn render_post(tera: &tera::Tera, post: &Post) -> Result { let mut ctx = tera::Context::new(); - ctx.insert("title", &post.frontmatter.title); - ctx.insert("content", &post.content); + ctx.insert("page", &post); - state.tera.render("post.html", &ctx).map_err(|e| e.into()) + tera.render("post.html", &ctx).map_err(|e| e.into()) } -#[instrument(skip(posts))] -pub fn build_router<'a, I>(posts: I) -> Router -where - I: Iterator, +pub fn router() -> Router { - let mut router = Router::new().route("/", get(index)); - - for post in posts { - let slug = &post.slug; - let path = format!("/{slug}/"); - info!("adding post route: {path}"); - router = router.route(&path, get(view)); - } - - router + Router::new() + .route("/", get(index)) + .route("/:slug/", get(view)) + .fallback_service(tower_http::services::ServeDir::new("./posts")) } #[instrument(skip(state))] pub async fn view( - uri: Uri, + extract::Path(slug): extract::Path, Extension(state): Extension>, -) -> Result, handlers::Error> { - debug!("viewing post: {uri}"); +) -> Result, WebsiteError> { + debug!("viewing post: {slug}"); let post = state .posts - .get(uri.path().trim_matches('/')) - .ok_or(handlers::Error::NotFound)?; + .get(&slug) + .ok_or(WebsiteError::NotFound)?; - let res = render_post(&state, post).await?; + let res = render_post(&state.tera, post).await?; Ok(Html(res)) } @@ -170,17 +155,39 @@ pub async fn view( #[instrument(skip(state))] pub async fn index( Extension(state): Extension>, -) -> Result, handlers::Error> { +) -> Result, WebsiteError> { let mut ctx = tera::Context::new(); let mut posts = state.posts.values().collect::>(); - posts.sort_by_key(|p| &p.frontmatter.date); + posts.sort_by_key(|p| &p.date); + posts.reverse(); - ctx.insert("title", "Posts"); + ctx.insert("page.title", "Posts"); ctx.insert("posts", &posts); - let res = state.tera.render("posts_index.html", &ctx)?; + let res = match state.tera.render("posts_index.html", &ctx) { + Ok(r) => r, + Err(e) => { + error!("failed to render posts index: {}", e); + return Err(e.into()); + } + }; Ok(Html(res)) } + +#[cfg(test)] +mod tests { + use tera::Tera; + + #[tokio::test] + async fn render_all() { + let tera = Tera::new("templates/**/*").unwrap(); + let posts = super::load_all().await.unwrap(); + + for (_slug, post) in posts { + super::render_post(&tera, &post).await.unwrap(); + } + } +} diff --git a/templates/base.html b/templates/base.html index 0c1c572..fd00488 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,17 +1,17 @@ -{% include "partials/head.html" %} +{% include "partials/head.html" -%}
- {% include "partials/header.html" %} + {% include "partials/header.html" -%}

- {% block main %}{% endblock main %} + {% block main %}{% endblock main -%}

- {% include "partials/footer.html" %} + {% include "partials/footer.html" -%}
\ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 0a68a36..f63c2b8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,9 +7,9 @@
  • static content ✅
  • sass compilation
  • -
  • post metadata (frontmatter)
  • +
  • post metadata (frontmatter) ✅
  • rss/atom/jsonfeed
  • -
  • proper error handling
  • +
  • proper error handling ✅ (i guess??)
  • other pages???
{% endblock main %} \ No newline at end of file diff --git a/templates/partials/head.html b/templates/partials/head.html index a6df569..e81ee41 100644 --- a/templates/partials/head.html +++ b/templates/partials/head.html @@ -4,8 +4,8 @@ - {% if title -%} - {{ title }} | tollyx.net + {% if page.title -%} + {{ page.title }} | tollyx.net {% else -%} tollyx.net {% endif -%} diff --git a/templates/post.html b/templates/post.html index 2574d4e..80c3a41 100644 --- a/templates/post.html +++ b/templates/post.html @@ -1,5 +1,13 @@ {% extends "base.html" %} -{% block main %} - {{ content | safe }} -{% endblock main %} \ No newline at end of file +{% block main -%} +
+ {% if page.title -%} +

{{ page.title }}

+ {% endif -%} + {% if page.date -%} + Posted on + {% endif -%} + {{ page.content | safe -}} +
+{% endblock main -%} \ No newline at end of file diff --git a/templates/posts_index.html b/templates/posts_index.html index 6f099f0..110793d 100644 --- a/templates/posts_index.html +++ b/templates/posts_index.html @@ -1,15 +1,15 @@ {% extends "base.html" %} {% block main -%} -

posts

-

i occasionally write some stuff i guess

+

Posts

+

I occasionally write some stuff, it's quite rare but it does happen believe it or not.

{% endblock main -%} \ No newline at end of file