1
0
Fork 0

So much stuff

This commit is contained in:
Adrian Hedqvist 2023-03-25 21:38:16 +01:00
parent 73f88fd78c
commit 3189b57a46
21 changed files with 289 additions and 158 deletions

3
.dockerignore Normal file
View file

@ -0,0 +1,3 @@
.git
.vscode
target

View file

@ -3,7 +3,6 @@
"sv" "sv"
], ],
"spellright.documentTypes": [ "spellright.documentTypes": [
"latex", "latex"
"plaintext"
] ]
} }

View file

@ -20,7 +20,7 @@ regex = "1.7.2"
serde = "1.0.144" serde = "1.0.144"
serde_derive = "1.0.144" serde_derive = "1.0.144"
serde_json = "1.0.85" serde_json = "1.0.85"
tera = "1.17.0" tera = { version = "1.17.0", features = ["builtins"] }
tokio = { version = "1.19.2", features = ["full"] } tokio = { version = "1.19.2", features = ["full"] }
toml = "0.7.3" toml = "0.7.3"
tower = { version = "0.4.12", features = ["full"] } tower = { version = "0.4.12", features = ["full"] }

View file

@ -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"]

65
Dockerfile Normal file
View file

@ -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"]

49
posts/dungeon/index.md Normal file
View file

@ -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).

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -3,8 +3,6 @@ title="TOML metadata test"
date=2023-03-25T14:50:25+01:00 date=2023-03-25T14:50:25+01:00
+++ +++
# Testing post as index within folder
hope it works yay hope it works yay
here have a squid miku to test relative paths: here have a squid miku to test relative paths:

View file

@ -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/)

View file

@ -1,7 +0,0 @@
+++
title='how about even deeper??'
date=2024-03-25T14:50:25+01:00
+++
# WOWOAOWOFAODWAOWOAWAOWA
SOOO DEEP BRO

11
posts/hello-world.md Normal file
View file

@ -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)

View file

@ -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

View file

@ -1,7 +0,0 @@
+++
title="test page please ignore"
date=2023-03-25T15:16:10+01:00
+++
# Test page please ignore
Hello world!

View file

@ -7,43 +7,26 @@ use axum::{
Extension, Extension,
}; };
use crate::State; use crate::{State, WebsiteError};
#[derive(Debug)]
pub enum Error {
NotFound,
InternalError(anyhow::Error),
}
impl<E> From<E> for Error
where
E: Into<anyhow::Error>,
{
fn from(value: E) -> Self {
Error::InternalError(value.into())
}
}
pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
#[instrument(skip(state))] #[instrument(skip(state))]
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result { pub async fn index(Extension(state): Extension<Arc<State>>) -> std::result::Result<Html<Vec<u8>>, WebsiteError> {
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);
Error::NotFound WebsiteError::NotFound
})?; })?;
Ok(Html(res.into())) Ok(Html(res.into()))
} }
impl IntoResponse for Error { impl IntoResponse for WebsiteError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
match self { match self {
Error::NotFound => { WebsiteError::NotFound => {
info!("not found"); info!("not found");
(StatusCode::NOT_FOUND, ()).into_response() (StatusCode::NOT_FOUND, ()).into_response()
} }
Error::InternalError(e) => { WebsiteError::InternalError(e) => {
error!("internal error: {e}"); error!("internal error: {e}");
(StatusCode::INTERNAL_SERVER_ERROR, ()).into_response() (StatusCode::INTERNAL_SERVER_ERROR, ()).into_response()
} }

View file

@ -37,8 +37,6 @@ pub async fn init_app() -> Result<Router> {
let tera = Tera::new("templates/**/*")?; let tera = Tera::new("templates/**/*")?;
let posts = post::load_all().await?; let posts = post::load_all().await?;
let posts_router = post::build_router(posts.values());
let state = Arc::new(State { tera, posts }); let state = Arc::new(State { tera, posts });
let middleware = tower::ServiceBuilder::new() let middleware = tower::ServiceBuilder::new()
@ -50,10 +48,30 @@ pub async fn init_app() -> Result<Router> {
.route("/", get(handlers::index)) .route("/", get(handlers::index))
.nest( .nest(
"/posts", "/posts",
posts_router.fallback_service(tower_http::services::ServeDir::new("./posts")), post::router(),
) )
.nest_service("/static", tower_http::services::ServeDir::new("./static")) .nest_service("/static", tower_http::services::ServeDir::new("./static"))
.route("/.healthcheck", get(healthcheck))
.layer(middleware); .layer(middleware);
Ok(app) Ok(app)
} }
async fn healthcheck() -> &'static str {
"OK"
}
#[derive(Debug)]
pub enum WebsiteError {
NotFound,
InternalError(anyhow::Error),
}
impl<E> From<E> for WebsiteError
where
E: Into<anyhow::Error>,
{
fn from(value: E) -> Self {
WebsiteError::InternalError(value.into())
}
}

View file

@ -1,10 +1,9 @@
use std::{collections::HashMap, path::Path, sync::Arc}; 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 chrono::{DateTime, FixedOffset};
use glob::glob; use glob::glob;
use hyper::Uri;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use pulldown_cmark::{html, Options, Parser}; use pulldown_cmark::{html, Options, Parser};
use regex::Regex; use regex::Regex;
@ -13,26 +12,40 @@ use tokio::fs;
use tracing::{instrument, log::*}; use tracing::{instrument, log::*};
use crate::{handlers, State}; use crate::{State, WebsiteError};
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Deserialize, Debug, Default)]
pub struct FrontMatter {
pub title: String,
pub date: DateTime<FixedOffset>,
}
#[derive(Deserialize, Debug)]
pub struct TomlFrontMatter { pub struct TomlFrontMatter {
pub title: String, pub title: String,
pub date: toml::value::Datetime, pub date: Option<toml::value::Datetime>,
pub draft: Option<bool>,
pub aliases: Option<Vec<String>>,
pub tags: Option<Vec<String>>,
} }
#[derive(Serialize, Clone, Debug)] #[derive(Serialize, Clone, Debug)]
pub struct Post { pub struct Post {
pub title: String,
pub date: Option<DateTime<FixedOffset>>,
pub aliases: Vec<String>,
pub tags: Vec<String>,
pub content: String, pub content: String,
pub slug: String, pub slug: String,
pub absolute_path: 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] #[instrument]
@ -42,16 +55,7 @@ pub async fn load_all() -> color_eyre::eyre::Result<HashMap<String, Post>> {
let path = path.unwrap(); let path = path.unwrap();
debug!("found page: {}", path.display()); debug!("found page: {}", path.display());
let post = load_post(&path.to_string_lossy()).await?; let path = path.to_string_lossy().replace('\\', "/");
res.insert(post.slug.clone(), post);
}
Ok(res)
}
#[instrument]
pub async fn load_post(path: &str) -> color_eyre::eyre::Result<Post> {
let path = path.replace('\\', "/");
let slug = path let slug = path
.trim_start_matches("posts") .trim_start_matches("posts")
.trim_start_matches('/') .trim_start_matches('/')
@ -62,6 +66,15 @@ pub async fn load_post(path: &str) -> color_eyre::eyre::Result<Post> {
.trim_end_matches('\\') .trim_end_matches('\\')
.trim_end_matches('/'); .trim_end_matches('/');
let post = load_post(slug).await?;
res.insert(slug.to_string(), post);
}
Ok(res)
}
#[instrument]
pub async fn load_post(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);
@ -83,25 +96,7 @@ pub async fn load_post(path: &str) -> color_eyre::eyre::Result<Post> {
content_html content_html
}); });
let date = toml_date_to_chrono(tomlfm.date)?; Ok(Post::new(slug.to_string(), content.unwrap_or_default(), tomlfm))
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<DateTime<FixedOffset>> {
Ok(DateTime::parse_from_rfc3339(&toml.to_string())?)
} }
#[instrument] #[instrument]
@ -125,44 +120,34 @@ fn parse_frontmatter(
}) })
} }
#[instrument(skip(state, post))] #[instrument(skip(tera, post))]
async fn render_post(state: &State, post: &Post) -> Result<String, handlers::Error> { async fn render_post(tera: &tera::Tera, post: &Post) -> Result<String, WebsiteError> {
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.insert("title", &post.frontmatter.title); ctx.insert("page", &post);
ctx.insert("content", &post.content);
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 router() -> Router
pub fn build_router<'a, I>(posts: I) -> Router
where
I: Iterator<Item = &'a Post>,
{ {
let mut router = Router::new().route("/", get(index)); Router::new()
.route("/", get(index))
for post in posts { .route("/:slug/", get(view))
let slug = &post.slug; .fallback_service(tower_http::services::ServeDir::new("./posts"))
let path = format!("/{slug}/");
info!("adding post route: {path}");
router = router.route(&path, get(view));
}
router
} }
#[instrument(skip(state))] #[instrument(skip(state))]
pub async fn view( pub async fn view(
uri: Uri, extract::Path(slug): extract::Path<String>,
Extension(state): Extension<Arc<State>>, Extension(state): Extension<Arc<State>>,
) -> Result<Html<String>, handlers::Error> { ) -> Result<Html<String>, WebsiteError> {
debug!("viewing post: {uri}"); debug!("viewing post: {slug}");
let post = state let post = state
.posts .posts
.get(uri.path().trim_matches('/')) .get(&slug)
.ok_or(handlers::Error::NotFound)?; .ok_or(WebsiteError::NotFound)?;
let res = render_post(&state, post).await?; let res = render_post(&state.tera, post).await?;
Ok(Html(res)) Ok(Html(res))
} }
@ -170,17 +155,39 @@ pub async fn view(
#[instrument(skip(state))] #[instrument(skip(state))]
pub async fn index( pub async fn index(
Extension(state): Extension<Arc<State>>, Extension(state): Extension<Arc<State>>,
) -> Result<Html<String>, handlers::Error> { ) -> Result<Html<String>, WebsiteError> {
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
let mut posts = state.posts.values().collect::<Vec<&Post>>(); let mut posts = state.posts.values().collect::<Vec<&Post>>();
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); 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)) 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();
}
}
}

View file

@ -1,17 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
{% include "partials/head.html" %} {% include "partials/head.html" -%}
<body> <body>
<header> <header>
{% include "partials/header.html" %} {% include "partials/header.html" -%}
</header> </header>
<hr> <hr>
<main> <main>
{% block main %}{% endblock main %} {% block main %}{% endblock main -%}
</main> </main>
<hr> <hr>
<footer> <footer>
{% include "partials/footer.html" %} {% include "partials/footer.html" -%}
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -7,9 +7,9 @@
<ul> <ul>
<li>static content ✅</li> <li>static content ✅</li>
<li>sass compilation</li> <li>sass compilation</li>
<li>post metadata (frontmatter)</li> <li>post metadata (frontmatter)</li>
<li>rss/atom/jsonfeed</li> <li>rss/atom/jsonfeed</li>
<li>proper error handling</li> <li>proper error handling ✅ (i guess??)</li>
<li>other pages???</li> <li>other pages???</li>
</ul> </ul>
{% endblock main %} {% endblock main %}

View file

@ -4,8 +4,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/site.css"> <link rel="stylesheet" href="/static/site.css">
<link rel="icon" href="/static/avatar.png" /> <link rel="icon" href="/static/avatar.png" />
{% if title -%} {% if page.title -%}
<title>{{ title }} | tollyx.net</title> <title>{{ page.title }} | tollyx.net</title>
{% else -%} {% else -%}
<title>tollyx.net</title> <title>tollyx.net</title>
{% endif -%} {% endif -%}

View file

@ -1,5 +1,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main %} {% block main -%}
{{ content | safe }} <article>
{% endblock main %} {% if page.title -%}
<h1>{{ page.title }}</h1>
{% endif -%}
{% if page.date -%}
<small>Posted on <time datetime="{{ page.date }}">{{ page.date | date(format="%Y-%m-%d %H:%M") }}</time></small>
{% endif -%}
{{ page.content | safe -}}
</article>
{% endblock main -%}

View file

@ -1,13 +1,13 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main -%} {% block main -%}
<h1>posts</h1> <h1>Posts</h1>
<p>i occasionally write some stuff i guess</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.frontmatter -%} <li><a href="{{post.absolute_path | safe}}">{% if post.date -%}
{{post.frontmatter.title -}} <time datetime="{{ post.date }}">{{ post.date | date(format="%Y-%m-%d") }}</time> - {{ post.title -}}
{% else -%} {% else -%}
{{post.slug -}} {{ post.title -}}
{% endif -%} {% endif -%}
</a></li> </a></li>
{% endfor -%} {% endfor -%}