Rewrite the site routing with Axum (#441)
* broken state Signed-off-by: Xe Iaso <me@christine.website> * fix??? Signed-off-by: Xe Iaso <me@christine.website> * Port everything else to axum Signed-off-by: Xe <me@christine.website> * headers Signed-off-by: Xe Iaso <me@christine.website> * site update post Signed-off-by: Christine Dodrill <me@christine.website> * fix headers Signed-off-by: Xe Iaso <me@christine.website> * remove warp example Signed-off-by: Xe Iaso <me@christine.website> * 80c wrap Signed-off-by: Xe Iaso <me@christine.website> * bump version Signed-off-by: Xe Iaso <me@christine.website>
This commit is contained in:
parent
f45ca40ae1
commit
8b747c1c40
File diff suppressed because it is too large
Load Diff
20
Cargo.toml
20
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "xesite"
|
name = "xesite"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
authors = ["Xe Iaso <me@christine.website>"]
|
authors = ["Xe Iaso <me@christine.website>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
build = "src/build.rs"
|
build = "src/build.rs"
|
||||||
|
@ -9,13 +9,19 @@ repository = "https://github.com/Xe/site"
|
||||||
# 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 = "0.4"
|
||||||
|
axum-macros = "0.1"
|
||||||
|
axum-extra = "0.1"
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
comrak = "0.12.1"
|
comrak = "0.12.1"
|
||||||
|
derive_more = "0.99"
|
||||||
envy = "0.4"
|
envy = "0.4"
|
||||||
estimated_read_time = "1"
|
estimated_read_time = "1"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
http = "0.2"
|
||||||
|
http-body = "0.4"
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
kankyo = "0.3"
|
kankyo = "0.3"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
|
@ -34,24 +40,30 @@ tokio-stream = { version = "0.1", features = ["net"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
tracing-subscriber = { version = "0.3", features = ["fmt"] }
|
||||||
warp = "0.3"
|
|
||||||
xml-rs = "0.8"
|
xml-rs = "0.8"
|
||||||
url = "2"
|
url = "2"
|
||||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
|
|
||||||
# workspace dependencies
|
# workspace dependencies
|
||||||
cfcache = { path = "./lib/cfcache" }
|
cfcache = { path = "./lib/cfcache" }
|
||||||
go_vanity = { path = "./lib/go_vanity" }
|
|
||||||
jsonfeed = { path = "./lib/jsonfeed" }
|
jsonfeed = { path = "./lib/jsonfeed" }
|
||||||
mi = { path = "./lib/mi" }
|
mi = { path = "./lib/mi" }
|
||||||
patreon = { path = "./lib/patreon" }
|
patreon = { path = "./lib/patreon" }
|
||||||
|
|
||||||
|
[dependencies.tower]
|
||||||
|
version = "0.4"
|
||||||
|
features = [ "full" ]
|
||||||
|
|
||||||
|
[dependencies.tower-http]
|
||||||
|
version = "0.2"
|
||||||
|
features = [ "full" ]
|
||||||
|
|
||||||
# os-specific dependencies
|
# os-specific dependencies
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
sdnotify = { version = "0.2", default-features = false }
|
sdnotify = { version = "0.2", default-features = false }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
ructe = { version = "0.13", features = ["warp02"] }
|
ructe = { version = "0.13", features = [ "mime03" ] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pfacts = "0"
|
pfacts = "0"
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
---
|
||||||
|
title: "Site Update: Axum"
|
||||||
|
date: 2022-03-21
|
||||||
|
---
|
||||||
|
|
||||||
|
I have made a bunch of huge changes to my website that hopefully you won't
|
||||||
|
notice unless you read this post that points them out to you. I have redone how
|
||||||
|
the website's URL routing works to use
|
||||||
|
[axum](https://tokio.rs/blog/2021-07-announcing-axum) instead of
|
||||||
|
[warp](https://docs.rs/warp/latest/warp/).
|
||||||
|
|
||||||
|
I chose warp fairly arbitrarily when I was getting into the swing of Rust. This
|
||||||
|
choice turned out to be a bit of a mistake. Don't get me wrong, warp is a
|
||||||
|
fantastic framework, but overall I've not been happy with how it impacts compile
|
||||||
|
times. Warp works by pushing a lot of the complexities with HTTP routing into
|
||||||
|
the type system. This can lead to undebuggable inscruitable types that make it
|
||||||
|
really hard to understand what is wrong. Here is the autogenerated type for the
|
||||||
|
`/blog/:name` route:
|
||||||
|
|
||||||
|
```
|
||||||
|
warp::filter::and::And<warp::filter::and::And<impl warp::filter::FilterBase<Extract = (), Error = Infallible> + warp::Filter + std::marker::Copy, Exact<warp::path::internal::Opaque<main::{closure#0}::__StaticPath>>>, warp::filter::and_then::AndThen<warp::filter::and::And<warp::filter::and::And<warp::filter::and::And<warp::filter::and::And<impl warp::filter::FilterBase<Extract = (), Error = Infallible> + warp::Filter + std::marker::Copy, impl warp::filter::FilterBase<Extract = (std::string::String,), Error = Rejection> + warp::Filter + std::marker::Copy>, impl warp::filter::FilterBase<Extract = (), Error = Rejection> + warp::Filter + std::marker::Copy>, impl warp::filter::FilterBase<Extract = (Arc<app::State>,), Error = Infallible> + warp::Filter + Clone>, impl warp::filter::FilterBase<Extract = (), Error = Rejection> + warp::Filter + std::marker::Copy>, fn(std::string::String, Arc<app::State>) -> impl warp::Future<Output = Result<Opaque(DefId(0:1249 ~ xesite[3495]::handlers::blog::post_view::{opaque#0}::{opaque#0}), []), Rejection>> {blog::post_view}>>
|
||||||
|
```
|
||||||
|
|
||||||
|
[What the heck is that? Is that a binary tree?](conversation://Numa/delet)
|
||||||
|
|
||||||
|
[Yep. It's insufferable to try and debug too.](conversation://Cadey/coffee)
|
||||||
|
|
||||||
|
Yeah, it's really hard to understand what's going on in error messages because
|
||||||
|
of this. This also means that the routes are put into a binary tree in the type
|
||||||
|
system, which means if your tree is unbalanced then you get slower compile times
|
||||||
|
and a slight hit at runtime as the framework traverses your binary tree to
|
||||||
|
figure out what to do. This has also made it difficult for me to add features
|
||||||
|
such as [historical views of my RSS feed](https://github.com/Xe/site/issues/419)
|
||||||
|
or other things I want to add like the April Fools day feature I've been working
|
||||||
|
on.
|
||||||
|
|
||||||
|
When I went out framework shopping, I tried a few things and got reccomendations
|
||||||
|
from a trusted friend before I finally settled on axum as the heart of this
|
||||||
|
website. Axum has a few major advantages that bbrought me "in the door":
|
||||||
|
|
||||||
|
- It's maintained by the tokio team
|
||||||
|
- It leverages the type system of Rust to make writing handlers easier
|
||||||
|
- It uses extractors (think lenses) to help you pick out the subset of data you
|
||||||
|
need, not blindly giving you everything and hoping you figure it out
|
||||||
|
- It has sub-routers which can have different middleware stacks than the main
|
||||||
|
one (useful for things like API authentication)
|
||||||
|
|
||||||
|
And it has these disadvantages:
|
||||||
|
|
||||||
|
- Writing middleware is kinda weird (though this may be because I'm not used to
|
||||||
|
working with tower), but easy once you get the general flow of things
|
||||||
|
- I can't find a way to have the template data get continuously piped to client
|
||||||
|
connections instead of rendering it to a buffer and then writing that buffer
|
||||||
|
to the client
|
||||||
|
- It doesn't have the biggest mindshare and one of the best ways to get unstuck
|
||||||
|
at the time of writing this article is to ask on their Discord server
|
||||||
|
|
||||||
|
Overall, I've been happy with the experience of porting over my site to using
|
||||||
|
Axum. I did [a stream on Twitch](https://www.twitch.tv/videos/1429533858) where
|
||||||
|
I ported it all over if you want to watch the process and hear my thought
|
||||||
|
processes as I was figuring things out.
|
||||||
|
|
||||||
|
As users, nothing should have changed about this site. However I'm almost
|
||||||
|
certain that I did forget to port _something_ over, so if I missed something you
|
||||||
|
rely on, [get in contact](/contact). I have not gotten the Patreon API
|
||||||
|
interoperability code fixed yet, so that is the next major issue. I am going to
|
||||||
|
figure out how refresh tokens work the hard way and make the patrons page
|
||||||
|
auto-updating instead of having to [manually get a new API key every
|
||||||
|
month](https://github.com/Xe/site/blob/main/docs/patron-page.org). I am also
|
||||||
|
looking into having that patrons page be updated by a cronjob that emits json to
|
||||||
|
the disk and have my site load from that instead of just hoping that the patreon
|
||||||
|
API credentials are up to date. We'll see how that goes, but you can track that
|
||||||
|
[here](https://github.com/Xe/site/issues/442). I will likely do a livestream for
|
||||||
|
this.
|
||||||
|
|
||||||
|
I have also contacted a copyeditor for my blog. I am so happy with the results
|
||||||
|
so far. [My Devops post](/blog/social-quandry-devops-2022-03-17) was the first
|
||||||
|
thing that the editor reviewed and they absolutely tore my first draft in half
|
||||||
|
and helped me put the parts back together into something more palateable. I am
|
||||||
|
beyond satisfied with this and will continue to use this editor in the future. I
|
||||||
|
wish I had gotten a copyeditor sooner.
|
|
@ -1,14 +0,0 @@
|
||||||
use warp::Filter;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let hello = warp::path!("hello" / String)
|
|
||||||
.map(|name| format!("Hello, {}!", name));
|
|
||||||
let health = warp::path!(".within" / "health")
|
|
||||||
.map(|| "OK");
|
|
||||||
let routes = hello.or(health);
|
|
||||||
|
|
||||||
warp::serve(routes)
|
|
||||||
.run(([0, 0, 0, 0], 3030))
|
|
||||||
.await;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "go_vanity"
|
|
||||||
version = "0.2.0"
|
|
||||||
authors = ["Xe Iaso <me@christine.website>"]
|
|
||||||
edition = "2018"
|
|
||||||
build = "src/build.rs"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
mime = "0.3"
|
|
||||||
warp = "0.3"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
ructe = { version = "0.13", features = ["warp02"] }
|
|
|
@ -1,5 +0,0 @@
|
||||||
use ructe::{Result, Ructe};
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
Ructe::from_env()?.compile_templates("templates")
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
use crate::templates::RenderRucte;
|
|
||||||
use warp::{http::Response, Rejection, Reply};
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
|
||||||
|
|
||||||
pub async fn gitea(pkg_name: &str, git_repo: &str, branch: &str) -> Result<impl Reply, Rejection> {
|
|
||||||
Response::builder().html(|o| templates::gitea_html(o, pkg_name, git_repo, branch))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn github(pkg_name: &str, git_repo: &str, branch: &str) -> Result<impl Reply, Rejection> {
|
|
||||||
Response::builder().html(|o| templates::github_html(o, pkg_name, git_repo, branch))
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
@(pkg_name: &str, git_repo: &str, branch: &str)
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
||||||
<meta name="go-import" content="@pkg_name git @git_repo">
|
|
||||||
<meta name="go-source" content="@pkg_name @git_repo @git_repo/tree/@branch@{/dir@} @git_repo/blob/@branch@{/dir@}/@{file@}#L@{line@}">
|
|
||||||
<meta http-equiv="refresh" content="0; url=https://godoc.org/@pkg_name">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
Please see <a href="https://godoc.org/@pkg_name">here</a> for documentation on this package.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,6 +1,6 @@
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use reqwest::header;
|
use reqwest::header;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
const USER_AGENT_BASE: &str = concat!(
|
const USER_AGENT_BASE: &str = concat!(
|
||||||
|
@ -58,7 +58,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Eq, PartialEq, Clone)]
|
#[derive(Debug, Deserialize, Eq, PartialEq, Clone, Serialize)]
|
||||||
pub struct WebMention {
|
pub struct WebMention {
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
use axum::extract::connect_info;
|
||||||
|
use futures::ready;
|
||||||
|
use hyper::{
|
||||||
|
client::connect::{Connected, Connection},
|
||||||
|
server::accept::Accept,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
io::{AsyncRead, AsyncWrite},
|
||||||
|
net::{unix::UCred, UnixListener, UnixStream},
|
||||||
|
};
|
||||||
|
use tower::BoxError;
|
||||||
|
|
||||||
|
pub struct ServerAccept {
|
||||||
|
pub uds: UnixListener,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accept for ServerAccept {
|
||||||
|
type Conn = UnixStream;
|
||||||
|
type Error = BoxError;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
let (stream, _addr) = ready!(self.uds.poll_accept(cx))?;
|
||||||
|
Poll::Ready(Some(Ok(stream)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientConnection {
|
||||||
|
pub stream: UnixStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for ClientConnection {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<Result<usize, io::Error>> {
|
||||||
|
Pin::new(&mut self.stream).poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||||
|
Pin::new(&mut self.stream).poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), io::Error>> {
|
||||||
|
Pin::new(&mut self.stream).poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for ClientConnection {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut tokio::io::ReadBuf<'_>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
|
Pin::new(&mut self.stream).poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection for ClientConnection {
|
||||||
|
fn connected(&self) -> Connected {
|
||||||
|
Connected::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct UdsConnectInfo {
|
||||||
|
pub peer_addr: Arc<tokio::net::unix::SocketAddr>,
|
||||||
|
pub peer_cred: UCred,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl connect_info::Connected<&UnixStream> for UdsConnectInfo {
|
||||||
|
fn connect_info(target: &UnixStream) -> Self {
|
||||||
|
let peer_addr = target.peer_addr().unwrap();
|
||||||
|
let peer_cred = target.peer_cred().unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
peer_addr: Arc::new(peer_addr),
|
||||||
|
peer_cred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,13 @@
|
||||||
use super::{PostNotFound, SeriesNotFound, LAST_MODIFIED};
|
use super::Result;
|
||||||
use crate::{
|
use crate::{app::State, post::Post, templates};
|
||||||
app::State,
|
use axum::{
|
||||||
post::Post,
|
extract::{Extension, Path},
|
||||||
templates::{self, Html, RenderRucte},
|
response::Html,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
use warp::{http::Response, Rejection, Reply};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
||||||
|
@ -19,17 +18,18 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::blogindex_html(&mut result, state.blog.clone())?;
|
||||||
.html(|o| templates::blogindex_html(o, state.blog.clone()))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn series(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn series(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let mut series: Vec<String> = vec![];
|
let mut series: Vec<String> = vec![];
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
|
|
||||||
for post in &state.blog {
|
for post in &state.blog {
|
||||||
if post.front_matter.series.is_some() {
|
if post.front_matter.series.is_some() {
|
||||||
|
@ -40,15 +40,18 @@ pub async fn series(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
||||||
series.sort();
|
series.sort();
|
||||||
series.dedup();
|
series.dedup();
|
||||||
|
|
||||||
Response::builder()
|
templates::series_html(&mut result, series)?;
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
Ok(Html(result))
|
||||||
.html(|o| templates::series_html(o, series))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn series_view(series: String, state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn series_view(
|
||||||
|
Path(series): Path<String>,
|
||||||
|
Extension(state): Extension<Arc<State>>,
|
||||||
|
) -> Result {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let mut posts: Vec<Post> = vec![];
|
let mut posts: Vec<Post> = vec![];
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
|
|
||||||
for post in &state.blog {
|
for post in &state.blog {
|
||||||
if post.front_matter.series.is_none() {
|
if post.front_matter.series.is_none() {
|
||||||
|
@ -62,16 +65,18 @@ pub async fn series_view(series: String, state: Arc<State>) -> Result<impl Reply
|
||||||
|
|
||||||
if posts.len() == 0 {
|
if posts.len() == 0 {
|
||||||
error!("series not found");
|
error!("series not found");
|
||||||
Err(SeriesNotFound(series).into())
|
return Err(super::Error::SeriesNotFound(series));
|
||||||
} else {
|
|
||||||
Response::builder()
|
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
|
||||||
.html(|o| templates::series_posts_html(o, series, &posts))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templates::series_posts_html(&mut result, series, &posts).unwrap();
|
||||||
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn post_view(
|
||||||
|
Path(name): Path<String>,
|
||||||
|
Extension(state): Extension<Arc<State>>,
|
||||||
|
) -> Result {
|
||||||
let mut want: Option<Post> = None;
|
let mut want: Option<Post> = None;
|
||||||
|
|
||||||
for post in &state.blog {
|
for post in &state.blog {
|
||||||
|
@ -81,15 +86,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re
|
||||||
}
|
}
|
||||||
|
|
||||||
match want {
|
match want {
|
||||||
None => Err(PostNotFound("blog".into(), name).into()),
|
None => Err(super::Error::PostNotFound(name)),
|
||||||
Some(post) => {
|
Some(post) => {
|
||||||
HIT_COUNTER
|
HIT_COUNTER
|
||||||
.with_label_values(&[name.clone().as_str()])
|
.with_label_values(&[name.clone().as_str()])
|
||||||
.inc();
|
.inc();
|
||||||
let body = Html(post.body_html.clone());
|
let body = templates::Html(post.body_html.clone());
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::blogpost_html(&mut result, post, body)?;
|
||||||
.html(|o| templates::blogpost_html(o, post, body))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use super::LAST_MODIFIED;
|
use super::{Result, LAST_MODIFIED};
|
||||||
use crate::{app::State, post::Post, templates};
|
use crate::{
|
||||||
|
app::State,
|
||||||
|
post::{NewPost, Post},
|
||||||
|
templates,
|
||||||
|
};
|
||||||
|
use axum::{body, extract::Extension, response::Response, Json};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||||
use std::{io, sync::Arc};
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use warp::{http::Response, Rejection, Reply};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
pub static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
||||||
|
@ -16,103 +20,56 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn jsonfeed(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> {
|
pub async fn jsonfeed(Extension(state): Extension<Arc<State>>) -> Json<jsonfeed::Feed> {
|
||||||
HIT_COUNTER.with_label_values(&["json"]).inc();
|
HIT_COUNTER.with_label_values(&["json"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Ok(warp::reply::json(&state.jf))
|
Json(state.jf.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn new_post(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
#[axum_macros::debug_handler]
|
||||||
|
pub async fn new_post(Extension(state): Extension<Arc<State>>) -> Result<Json<NewPost>> {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let everything = state.everything.clone();
|
let p: Post = state.everything.iter().next().unwrap().clone();
|
||||||
let p: &Post = everything.iter().next().unwrap();
|
Ok(Json(p.new_post))
|
||||||
Ok(warp::reply::json(&p.new_post))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum RenderError {
|
|
||||||
Build(warp::http::Error),
|
|
||||||
IO(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl warp::reject::Reject for RenderError {}
|
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn atom(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> {
|
pub async fn atom(Extension(state): Extension<Arc<State>>) -> Result<Response> {
|
||||||
if let Some(etag) = since {
|
|
||||||
if etag == ETAG.clone() {
|
|
||||||
return Response::builder()
|
|
||||||
.status(304)
|
|
||||||
.header("Content-Type", "text/plain")
|
|
||||||
.body(
|
|
||||||
"You already have the newest version of this feed."
|
|
||||||
.to_string()
|
|
||||||
.into_bytes(),
|
|
||||||
)
|
|
||||||
.map_err(RenderError::Build)
|
|
||||||
.map_err(warp::reject::custom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HIT_COUNTER.with_label_values(&["atom"]).inc();
|
HIT_COUNTER.with_label_values(&["atom"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
templates::blog_atom_xml(&mut buf, state.everything.clone())
|
templates::blog_atom_xml(&mut buf, state.everything.clone())?;
|
||||||
.map_err(RenderError::IO)
|
Ok(Response::builder()
|
||||||
.map_err(warp::reject::custom)?;
|
|
||||||
Response::builder()
|
|
||||||
.status(200)
|
.status(200)
|
||||||
.header("Content-Type", "application/atom+xml")
|
.header("Content-Type", "application/atom+xml")
|
||||||
.header("ETag", ETAG.clone())
|
.header("ETag", ETAG.clone())
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
.header("Last-Modified", &*LAST_MODIFIED)
|
||||||
.body(buf)
|
.body(body::boxed(body::Full::from(buf)))?)
|
||||||
.map_err(RenderError::Build)
|
|
||||||
.map_err(warp::reject::custom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn rss(state: Arc<State>, since: Option<String>) -> Result<impl Reply, Rejection> {
|
pub async fn rss(Extension(state): Extension<Arc<State>>) -> Result<Response> {
|
||||||
if let Some(etag) = since {
|
|
||||||
if etag == ETAG.clone() {
|
|
||||||
return Response::builder()
|
|
||||||
.status(304)
|
|
||||||
.header("Content-Type", "text/plain")
|
|
||||||
.body(
|
|
||||||
"You already have the newest version of this feed."
|
|
||||||
.to_string()
|
|
||||||
.into_bytes(),
|
|
||||||
)
|
|
||||||
.map_err(RenderError::Build)
|
|
||||||
.map_err(warp::reject::custom);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HIT_COUNTER.with_label_values(&["rss"]).inc();
|
HIT_COUNTER.with_label_values(&["rss"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
templates::blog_rss_xml(&mut buf, state.everything.clone())
|
templates::blog_rss_xml(&mut buf, state.everything.clone())?;
|
||||||
.map_err(RenderError::IO)
|
Ok(Response::builder()
|
||||||
.map_err(warp::reject::custom)?;
|
|
||||||
Response::builder()
|
|
||||||
.status(200)
|
.status(200)
|
||||||
.header("Content-Type", "application/rss+xml")
|
.header("Content-Type", "application/rss+xml")
|
||||||
.header("ETag", ETAG.clone())
|
.header("ETag", ETAG.clone())
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
.header("Last-Modified", &*LAST_MODIFIED)
|
||||||
.body(buf)
|
.body(body::boxed(body::Full::from(buf)))?)
|
||||||
.map_err(RenderError::Build)
|
|
||||||
.map_err(warp::reject::custom)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn sitemap(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
#[axum_macros::debug_handler]
|
||||||
|
pub async fn sitemap(Extension(state): Extension<Arc<State>>) -> Result<Response> {
|
||||||
HIT_COUNTER.with_label_values(&["sitemap"]).inc();
|
HIT_COUNTER.with_label_values(&["sitemap"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(200)
|
.status(200)
|
||||||
.header("Content-Type", "application/xml")
|
.header("Content-Type", "application/xml")
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
.header("Last-Modified", &*LAST_MODIFIED)
|
||||||
.body(state.sitemap.clone())
|
.body(body::boxed(body::Full::from(state.sitemap.clone())))?)
|
||||||
.map_err(RenderError::Build)
|
|
||||||
.map_err(warp::reject::custom)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use super::PostNotFound;
|
use super::{Error::*, Result};
|
||||||
use crate::{
|
use crate::{app::State, post::Post, templates};
|
||||||
app::State,
|
use axum::{
|
||||||
post::Post,
|
extract::{Extension, Path},
|
||||||
templates::{self, Html, RenderRucte},
|
response::Html,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use warp::{http::Response, Rejection, Reply};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
||||||
|
@ -19,13 +18,18 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder().html(|o| templates::galleryindex_html(o, state.gallery.clone()))
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::galleryindex_html(&mut result, state.gallery.clone())?;
|
||||||
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn post_view(
|
||||||
|
Path(name): Path<String>,
|
||||||
|
Extension(state): Extension<Arc<State>>,
|
||||||
|
) -> Result {
|
||||||
let mut want: Option<Post> = None;
|
let mut want: Option<Post> = None;
|
||||||
|
|
||||||
for post in &state.gallery {
|
for post in &state.gallery {
|
||||||
|
@ -35,13 +39,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re
|
||||||
}
|
}
|
||||||
|
|
||||||
match want {
|
match want {
|
||||||
None => Err(PostNotFound("gallery".into(), name).into()),
|
None => Err(PostNotFound(name)),
|
||||||
Some(post) => {
|
Some(post) => {
|
||||||
HIT_COUNTER
|
HIT_COUNTER
|
||||||
.with_label_values(&[name.clone().as_str()])
|
.with_label_values(&[name.clone().as_str()])
|
||||||
.inc();
|
.inc();
|
||||||
let body = Html(post.body_html.clone());
|
let body = templates::Html(post.body_html.clone());
|
||||||
Response::builder().html(|o| templates::gallerypost_html(o, post, body))
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::gallerypost_html(&mut result, post, body)?;
|
||||||
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
use crate::{
|
use crate::{app::State, templates};
|
||||||
app::State,
|
use axum::{
|
||||||
templates::{self, Html, RenderRucte},
|
body,
|
||||||
|
extract::Extension,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{Html, IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use chrono::{Datelike, Timelike, Utc};
|
use chrono::{Datelike, Timelike, Utc};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||||
use std::{convert::Infallible, fmt, sync::Arc};
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use warp::{
|
|
||||||
http::{Response, StatusCode},
|
pub mod blog;
|
||||||
Rejection, Reply,
|
pub mod feeds;
|
||||||
};
|
pub mod gallery;
|
||||||
|
pub mod talks;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIT_COUNTER: IntCounterVec =
|
static ref HIT_COUNTER: IntCounterVec =
|
||||||
|
@ -32,139 +36,104 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn index() -> Result<impl Reply, Rejection> {
|
pub async fn index() -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["index"]).inc();
|
HIT_COUNTER.with_label_values(&["index"]).inc();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::index_html(&mut result)?;
|
||||||
.html(|o| templates::index_html(o))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn contact() -> Result<impl Reply, Rejection> {
|
pub async fn contact() -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["contact"]).inc();
|
HIT_COUNTER.with_label_values(&["contact"]).inc();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::contact_html(&mut result)?;
|
||||||
.html(|o| templates::contact_html(o))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn feeds() -> Result<impl Reply, Rejection> {
|
pub async fn feeds() -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["feeds"]).inc();
|
HIT_COUNTER.with_label_values(&["feeds"]).inc();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::feeds_html(&mut result)?;
|
||||||
.html(|o| templates::feeds_html(o))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum_macros::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn resume(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["resume"]).inc();
|
HIT_COUNTER.with_label_values(&["resume"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::resume_html(&mut result, templates::Html(state.resume.clone()))?;
|
||||||
.html(|o| templates::resume_html(o, Html(state.resume.clone())))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn patrons(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn patrons(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["patrons"]).inc();
|
HIT_COUNTER.with_label_values(&["patrons"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
match &state.patrons {
|
match &state.patrons {
|
||||||
None => Response::builder().status(500).html(|o| {
|
None => Err(Error::NoPatrons),
|
||||||
templates::error_html(
|
Some(patrons) => {
|
||||||
o,
|
templates::patrons_html(&mut result, patrons.clone())?;
|
||||||
"Could not load patrons, let me know the API token expired again".to_string(),
|
Ok(Html(result))
|
||||||
)
|
}
|
||||||
}),
|
|
||||||
Some(patrons) => Response::builder()
|
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
|
||||||
.html(|o| templates::patrons_html(o, patrons.clone())),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum_macros::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn signalboost(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn signalboost(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["signalboost"]).inc();
|
HIT_COUNTER.with_label_values(&["signalboost"]).inc();
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::signalboost_html(&mut result, state.signalboost.clone())?;
|
||||||
.html(|o| templates::signalboost_html(o, state.signalboost.clone()))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn not_found() -> Result<impl Reply, Rejection> {
|
pub async fn not_found() -> Result {
|
||||||
HIT_COUNTER.with_label_values(&["not_found"]).inc();
|
HIT_COUNTER.with_label_values(&["not_found"]).inc();
|
||||||
Response::builder()
|
let mut result: Vec<u8> = vec![];
|
||||||
.header("Last-Modified", &*LAST_MODIFIED)
|
templates::notfound_html(&mut result, "some path".into())?;
|
||||||
.html(|o| templates::notfound_html(o, "some path".into()))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod blog;
|
|
||||||
pub mod feeds;
|
|
||||||
pub mod gallery;
|
|
||||||
pub mod talks;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
struct PostNotFound(String, String);
|
pub enum Error {
|
||||||
|
#[error("series not found: {0}")]
|
||||||
|
SeriesNotFound(String),
|
||||||
|
|
||||||
impl fmt::Display for PostNotFound {
|
#[error("post not found: {0}")]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
PostNotFound(String),
|
||||||
write!(f, "not found: {}/{}", self.0, self.1)
|
|
||||||
}
|
#[error("patreon key not working, poke me to get this fixed")]
|
||||||
|
NoPatrons,
|
||||||
|
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("axum http error: {0}")]
|
||||||
|
AxumHTTP(#[from] axum::http::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl warp::reject::Reject for PostNotFound {}
|
pub type Result<T = Html<Vec<u8>>> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
impl IntoResponse for Error {
|
||||||
struct SeriesNotFound(String);
|
fn into_response(self) -> Response {
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::error_html(&mut result, format!("{}", self)).unwrap();
|
||||||
|
|
||||||
impl fmt::Display for SeriesNotFound {
|
let body = body::boxed(body::Full::from(result));
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl warp::reject::Reject for SeriesNotFound {}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref REJECTION_COUNTER: IntCounterVec = register_int_counter_vec!(
|
|
||||||
opts!("rejections", "Number of rejections by kind"),
|
|
||||||
&["kind"]
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument]
|
|
||||||
pub async fn rejection(err: Rejection) -> Result<impl Reply, Infallible> {
|
|
||||||
let path: String;
|
|
||||||
let code;
|
|
||||||
|
|
||||||
if err.is_not_found() {
|
|
||||||
REJECTION_COUNTER.with_label_values(&["404"]).inc();
|
|
||||||
path = "".into();
|
|
||||||
code = StatusCode::NOT_FOUND;
|
|
||||||
} else if let Some(SeriesNotFound(series)) = err.find() {
|
|
||||||
REJECTION_COUNTER
|
|
||||||
.with_label_values(&["SeriesNotFound"])
|
|
||||||
.inc();
|
|
||||||
log::error!("invalid series {}", series);
|
|
||||||
path = format!("/blog/series/{}", series);
|
|
||||||
code = StatusCode::NOT_FOUND;
|
|
||||||
} else if let Some(PostNotFound(kind, name)) = err.find() {
|
|
||||||
REJECTION_COUNTER.with_label_values(&["PostNotFound"]).inc();
|
|
||||||
log::error!("unknown post {}/{}", kind, name);
|
|
||||||
path = format!("/{}/{}", kind, name);
|
|
||||||
code = StatusCode::NOT_FOUND;
|
|
||||||
} else {
|
|
||||||
REJECTION_COUNTER.with_label_values(&["Other"]).inc();
|
|
||||||
log::error!("unhandled rejection: {:?}", err);
|
|
||||||
path = format!("weird rejection: {:?}", err);
|
|
||||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(warp::reply::with_status(
|
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.html(|o| templates::notfound_html(o, path))
|
.status(match self {
|
||||||
.unwrap(),
|
Error::SeriesNotFound(_) | Error::PostNotFound(_) => StatusCode::NOT_FOUND,
|
||||||
code,
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
))
|
})
|
||||||
|
.body(body)
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use super::PostNotFound;
|
use super::{Error::*, Result};
|
||||||
use crate::{
|
use crate::{app::State, post::Post, templates};
|
||||||
app::State,
|
use axum::{
|
||||||
post::Post,
|
extract::{Extension, Path},
|
||||||
templates::{self, Html, RenderRucte},
|
response::Html,
|
||||||
};
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
use prometheus::{opts, register_int_counter_vec, IntCounterVec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use warp::{http::Response, Rejection, Reply};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
static ref HIT_COUNTER: IntCounterVec = register_int_counter_vec!(
|
||||||
|
@ -19,13 +18,18 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn index(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
let state = state.clone();
|
let state = state.clone();
|
||||||
Response::builder().html(|o| templates::talkindex_html(o, state.talks.clone()))
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::talkindex_html(&mut result, state.talks.clone())?;
|
||||||
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Rejection> {
|
pub async fn post_view(
|
||||||
|
Path(name): Path<String>,
|
||||||
|
Extension(state): Extension<Arc<State>>,
|
||||||
|
) -> Result {
|
||||||
let mut want: Option<Post> = None;
|
let mut want: Option<Post> = None;
|
||||||
|
|
||||||
for post in &state.talks {
|
for post in &state.talks {
|
||||||
|
@ -35,13 +39,15 @@ pub async fn post_view(name: String, state: Arc<State>) -> Result<impl Reply, Re
|
||||||
}
|
}
|
||||||
|
|
||||||
match want {
|
match want {
|
||||||
None => Err(PostNotFound("talks".into(), name).into()),
|
None => Err(PostNotFound(name).into()),
|
||||||
Some(post) => {
|
Some(post) => {
|
||||||
HIT_COUNTER
|
HIT_COUNTER
|
||||||
.with_label_values(&[name.clone().as_str()])
|
.with_label_values(&[name.clone().as_str()])
|
||||||
.inc();
|
.inc();
|
||||||
let body = Html(post.body_html.clone());
|
let body = templates::Html(post.body_html.clone());
|
||||||
Response::builder().html(|o| templates::talkpost_html(o, post, body))
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::talkpost_html(&mut result, post, body)?;
|
||||||
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
401
src/main.rs
401
src/main.rs
|
@ -1,29 +1,64 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body,
|
||||||
|
extract::Extension,
|
||||||
|
http::header::{self, HeaderValue, CACHE_CONTROL, CONTENT_TYPE},
|
||||||
|
response::{Html, Response},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use hyper::{header::CONTENT_TYPE, Body, Response};
|
use hyper::StatusCode;
|
||||||
use prometheus::{Encoder, TextEncoder};
|
use prometheus::{Encoder, TextEncoder};
|
||||||
use std::net::IpAddr;
|
use std::{
|
||||||
use std::str::FromStr;
|
env,
|
||||||
use std::sync::Arc;
|
net::{IpAddr, SocketAddr},
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
use tokio_stream::wrappers::UnixListenerStream;
|
use tower_http::{
|
||||||
use warp::{path, Filter};
|
services::{ServeDir, ServeFile},
|
||||||
|
set_header::SetResponseHeaderLayer,
|
||||||
|
trace::TraceLayer,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod signalboost;
|
pub mod signalboost;
|
||||||
|
|
||||||
use app::State;
|
mod domainsocket;
|
||||||
|
use domainsocket::*;
|
||||||
|
|
||||||
const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
||||||
|
|
||||||
fn with_state(
|
async fn healthcheck() -> &'static str {
|
||||||
state: Arc<State>,
|
"OK"
|
||||||
) -> impl Filter<Extract = (Arc<State>,), Error = std::convert::Infallible> + Clone {
|
}
|
||||||
warp::any().map(move || state.clone())
|
|
||||||
|
fn cache_header(_: &Response) -> Option<header::HeaderValue> {
|
||||||
|
Some(header::HeaderValue::from_static(
|
||||||
|
"public, max-age=3600, stale-if-error=60",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn webmention_header(_: &Response) -> Option<HeaderValue> {
|
||||||
|
Some(header::HeaderValue::from_static(
|
||||||
|
r#"<https://mi.within.website/api/webmention/accept>; rel="webmention""#,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clacks_header(_: &Response) -> Option<HeaderValue> {
|
||||||
|
Some(HeaderValue::from_static("Ashlynn"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hacker_header(_: &Response) -> Option<HeaderValue> {
|
||||||
|
Some(header::HeaderValue::from_static(
|
||||||
|
"If you are reading this, check out /signalboost to find people for your team",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -35,7 +70,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
let state = Arc::new(
|
let state = Arc::new(
|
||||||
app::init(
|
app::init(
|
||||||
std::env::var("CONFIG_FNAME")
|
env::var("CONFIG_FNAME")
|
||||||
.unwrap_or("./config.dhall".into())
|
.unwrap_or("./config.dhall".into())
|
||||||
.as_str()
|
.as_str()
|
||||||
.into(),
|
.into(),
|
||||||
|
@ -43,178 +78,129 @@ async fn main() -> Result<()> {
|
||||||
.await?,
|
.await?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let healthcheck = warp::get().and(warp::path(".within").and(warp::path("health")).map(|| "OK"));
|
let middleware = tower::ServiceBuilder::new()
|
||||||
let new_post = warp::path!(".within" / "website.within.xesite" / "new_post")
|
.layer(TraceLayer::new_for_http())
|
||||||
.and(with_state(state.clone()))
|
.layer(Extension(state.clone()))
|
||||||
.and_then(handlers::feeds::new_post);
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
header::CACHE_CONTROL,
|
||||||
|
cache_header,
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::appending(
|
||||||
|
header::LINK,
|
||||||
|
webmention_header,
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::appending(
|
||||||
|
header::HeaderName::from_static("x-clacks-overhead"),
|
||||||
|
clacks_header,
|
||||||
|
))
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
header::HeaderName::from_static("x-hacker"),
|
||||||
|
hacker_header,
|
||||||
|
));
|
||||||
|
|
||||||
let base = warp::path!("blog" / ..);
|
let app = Router::new()
|
||||||
let blog_index = base
|
// meta
|
||||||
.and(warp::path::end())
|
.route("/.within/health", get(healthcheck))
|
||||||
.and(with_state(state.clone()))
|
.route(
|
||||||
.and_then(handlers::blog::index);
|
"/.within/website.within.xesite/new_post",
|
||||||
let series = base
|
get(handlers::feeds::new_post),
|
||||||
.and(warp::path!("series").and(with_state(state.clone()).and_then(handlers::blog::series)));
|
|
||||||
let series_view = base.and(
|
|
||||||
warp::path!("series" / String)
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::get())
|
|
||||||
.and_then(handlers::blog::series_view),
|
|
||||||
);
|
|
||||||
let post_view = base.and(
|
|
||||||
warp::path!(String)
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::get())
|
|
||||||
.and_then(handlers::blog::post_view),
|
|
||||||
);
|
|
||||||
|
|
||||||
let gallery_base = warp::path!("gallery" / ..);
|
|
||||||
let gallery_index = gallery_base
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::gallery::index);
|
|
||||||
let gallery_post_view = gallery_base.and(
|
|
||||||
warp::path!(String)
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::get())
|
|
||||||
.and_then(handlers::gallery::post_view),
|
|
||||||
);
|
|
||||||
|
|
||||||
let talk_base = warp::path!("talks" / ..);
|
|
||||||
let talk_index = talk_base
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::talks::index);
|
|
||||||
let talk_post_view = talk_base.and(
|
|
||||||
warp::path!(String)
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::get())
|
|
||||||
.and_then(handlers::talks::post_view),
|
|
||||||
);
|
|
||||||
|
|
||||||
let index = warp::get().and(path::end().and_then(handlers::index));
|
|
||||||
let contact = warp::path!("contact").and_then(handlers::contact);
|
|
||||||
let feeds = warp::path!("feeds").and_then(handlers::feeds);
|
|
||||||
let resume = warp::path!("resume")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::resume);
|
|
||||||
let signalboost = warp::path!("signalboost")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::signalboost);
|
|
||||||
let patrons = warp::path!("patrons")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::patrons);
|
|
||||||
|
|
||||||
let files = warp::path("static")
|
|
||||||
.and(warp::fs::dir("./static"))
|
|
||||||
.map(|reply| {
|
|
||||||
warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"Cache-Control",
|
|
||||||
"public, max-age=86400, stale-if-error=60",
|
|
||||||
)
|
)
|
||||||
});
|
.route("/jsonfeed", get(go_vanity))
|
||||||
|
.route("/metrics", get(metrics))
|
||||||
let css = warp::path("css").and(warp::fs::dir("./css")).map(|reply| {
|
.route(
|
||||||
warp::reply::with_header(
|
"/sw.js",
|
||||||
reply,
|
axum::routing::get_service(ServeFile::new("./static/js/sw.js")).handle_error(
|
||||||
"Cache-Control",
|
|err: std::io::Error| async move {
|
||||||
"public, max-age=86400, stale-if-error=60",
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
)
|
)
|
||||||
});
|
},
|
||||||
|
|
||||||
let sw = warp::path("sw.js").and(warp::fs::file("./static/js/sw.js"));
|
|
||||||
let robots = warp::path("robots.txt").and(warp::fs::file("./static/robots.txt"));
|
|
||||||
let favicon = warp::path("favicon.ico").and(warp::fs::file("./static/favicon/favicon.ico"));
|
|
||||||
|
|
||||||
let jsonfeed = warp::path("blog.json")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::header::optional("if-none-match"))
|
|
||||||
.and_then(handlers::feeds::jsonfeed);
|
|
||||||
let atom = warp::path("blog.atom")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::header::optional("if-none-match"))
|
|
||||||
.and_then(handlers::feeds::atom);
|
|
||||||
let rss = warp::path("blog.rss")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and(warp::header::optional("if-none-match"))
|
|
||||||
.and_then(handlers::feeds::rss);
|
|
||||||
let sitemap = warp::path("sitemap.xml")
|
|
||||||
.and(with_state(state.clone()))
|
|
||||||
.and_then(handlers::feeds::sitemap);
|
|
||||||
let asset_links = warp::path!(".well-known" / "assetlinks.json")
|
|
||||||
.and(warp::fs::file("./static/assetlinks.json"));
|
|
||||||
|
|
||||||
let go_vanity_jsonfeed = warp::path("jsonfeed")
|
|
||||||
.and(warp::any().map(move || "christine.website/jsonfeed"))
|
|
||||||
.and(warp::any().map(move || "https://tulpa.dev/Xe/jsonfeed"))
|
|
||||||
.and(warp::any().map(move || "master"))
|
|
||||||
.and_then(go_vanity::gitea);
|
|
||||||
|
|
||||||
let metrics_endpoint = warp::path("metrics").and(warp::path::end()).map(move || {
|
|
||||||
let encoder = TextEncoder::new();
|
|
||||||
let metric_families = prometheus::gather();
|
|
||||||
let mut buffer = vec![];
|
|
||||||
encoder.encode(&metric_families, &mut buffer).unwrap();
|
|
||||||
Response::builder()
|
|
||||||
.status(200)
|
|
||||||
.header(CONTENT_TYPE, encoder.format_type())
|
|
||||||
.body(Body::from(buffer))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
let static_pages = index
|
|
||||||
.or(feeds.or(asset_links))
|
|
||||||
.or(resume.or(signalboost))
|
|
||||||
.or(patrons)
|
|
||||||
.or(jsonfeed.or(atom.or(sitemap)).or(rss))
|
|
||||||
.or(favicon.or(robots).or(sw))
|
|
||||||
.or(contact.or(new_post))
|
|
||||||
.map(|reply| {
|
|
||||||
warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"Cache-Control",
|
|
||||||
"public, max-age=86400, stale-if-error=60",
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let dynamic_pages = blog_index
|
|
||||||
.or(series.or(series_view).or(post_view))
|
|
||||||
.or(gallery_index.or(gallery_post_view))
|
|
||||||
.or(talk_index.or(talk_post_view))
|
|
||||||
.map(|reply| {
|
|
||||||
warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"Cache-Control",
|
|
||||||
"public, max-age=600, stale-if-error=60",
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let site = static_pages
|
|
||||||
.or(dynamic_pages)
|
|
||||||
.or(healthcheck.or(metrics_endpoint).or(go_vanity_jsonfeed))
|
|
||||||
.or(files.or(css))
|
|
||||||
.map(|reply| {
|
|
||||||
warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"X-Hacker",
|
|
||||||
"If you are reading this, check out /signalboost to find people for your team",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map(|reply| warp::reply::with_header(reply, "X-Clacks-Overhead", "GNU Ashlynn"))
|
|
||||||
.map(|reply| {
|
|
||||||
warp::reply::with_header(
|
|
||||||
reply,
|
|
||||||
"Link",
|
|
||||||
format!(
|
|
||||||
r#"<{}>; rel="webmention""#,
|
|
||||||
std::env::var("WEBMENTION_URL")
|
|
||||||
.unwrap_or("https://mi.within.website/api/webmention/accept".to_string())
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
.route(
|
||||||
.with(warp::log(APPLICATION_NAME))
|
"/.well-known/assetlinks.json",
|
||||||
.recover(handlers::rejection);
|
axum::routing::get_service(ServeFile::new("./static/assetlinks.json")).handle_error(
|
||||||
|
|err: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/robots.txt",
|
||||||
|
axum::routing::get_service(ServeFile::new("./static/robots.txt")).handle_error(
|
||||||
|
|err: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/favicon.ico",
|
||||||
|
axum::routing::get_service(ServeFile::new("./static/favicon/favicon.ico"))
|
||||||
|
.handle_error(|err: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
// static pages
|
||||||
|
.route("/", get(handlers::index))
|
||||||
|
.route("/contact", get(handlers::contact))
|
||||||
|
.route("/feeds", get(handlers::feeds))
|
||||||
|
.route("/resume", get(handlers::resume))
|
||||||
|
.route("/patrons", get(handlers::patrons))
|
||||||
|
.route("/signalboost", get(handlers::signalboost))
|
||||||
|
// feeds
|
||||||
|
.route("/blog.json", get(handlers::feeds::jsonfeed))
|
||||||
|
.route("/blog.atom", get(handlers::feeds::atom))
|
||||||
|
.route("/blog.rss", get(handlers::feeds::rss))
|
||||||
|
// blog
|
||||||
|
.route("/blog", get(handlers::blog::index))
|
||||||
|
.route("/blog/", get(handlers::blog::index))
|
||||||
|
.route("/blog/:name", get(handlers::blog::post_view))
|
||||||
|
.route("/blog/series", get(handlers::blog::series))
|
||||||
|
.route("/blog/series/:series", get(handlers::blog::series_view))
|
||||||
|
// gallery
|
||||||
|
.route("/gallery", get(handlers::gallery::index))
|
||||||
|
.route("/gallery/", get(handlers::gallery::index))
|
||||||
|
.route("/gallery/:name", get(handlers::gallery::post_view))
|
||||||
|
// talks
|
||||||
|
.route("/talks", get(handlers::talks::index))
|
||||||
|
.route("/talks/", get(handlers::talks::index))
|
||||||
|
.route("/talks/:name", get(handlers::talks::post_view))
|
||||||
|
// junk google wants
|
||||||
|
.route("/sitemap.xml", get(handlers::feeds::sitemap))
|
||||||
|
// static files
|
||||||
|
.nest(
|
||||||
|
"/css",
|
||||||
|
axum::routing::get_service(ServeDir::new("./css")).handle_error(
|
||||||
|
|err: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.nest(
|
||||||
|
"/static",
|
||||||
|
axum::routing::get_service(ServeDir::new("./static")).handle_error(
|
||||||
|
|err: std::io::Error| async move {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("unhandled internal server error: {}", err),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.layer(middleware);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
|
@ -241,30 +227,51 @@ async fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = warp::serve(site);
|
|
||||||
|
|
||||||
match std::env::var("SOCKPATH") {
|
match std::env::var("SOCKPATH") {
|
||||||
Ok(sockpath) => {
|
Ok(sockpath) => {
|
||||||
let _ = std::fs::remove_file(&sockpath);
|
let uds = UnixListener::bind(&sockpath)?;
|
||||||
let listener = UnixListener::bind(sockpath)?;
|
axum::Server::builder(ServerAccept { uds })
|
||||||
let incoming = UnixListenerStream::new(listener);
|
.serve(app.into_make_service_with_connect_info::<UdsConnectInfo, _>())
|
||||||
server.run_incoming(incoming).await;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
server
|
let addr: SocketAddr = (
|
||||||
.run((
|
IpAddr::from_str(&env::var("HOST").unwrap_or("::".into()))?,
|
||||||
IpAddr::from_str(&std::env::var("HOST").unwrap_or("::".into()))?,
|
env::var("PORT").unwrap_or("3030".into()).parse::<u16>()?,
|
||||||
std::env::var("PORT")
|
)
|
||||||
.unwrap_or("3030".into())
|
.into();
|
||||||
.parse::<u16>()?,
|
info!("listening on {}", addr);
|
||||||
))
|
axum::Server::bind(&addr)
|
||||||
.await;
|
.serve(app.into_make_service())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async fn metrics() -> Response {
|
||||||
|
let encoder = TextEncoder::new();
|
||||||
|
let metric_families = prometheus::gather();
|
||||||
|
let mut buffer = vec![];
|
||||||
|
encoder.encode(&metric_families, &mut buffer).unwrap();
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header(CONTENT_TYPE, encoder.format_type())
|
||||||
|
.body(body::boxed(body::Full::from(buffer)))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn go_vanity() -> Html<Vec<u8>> {
|
||||||
|
let mut buffer: Vec<u8> = vec![];
|
||||||
|
templates::gitea_html(
|
||||||
|
&mut buffer,
|
||||||
|
"christine.website/jsonfeed",
|
||||||
|
"https://christine.website/metrics",
|
||||||
|
"master",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
Html(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use color_eyre::eyre::{eyre, Result, WrapErr};
|
use color_eyre::eyre::{eyre, Result, WrapErr};
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{borrow::Borrow, cmp::Ordering, path::PathBuf};
|
use std::{borrow::Borrow, cmp::Ordering, path::PathBuf};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
pub mod frontmatter;
|
pub mod frontmatter;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug, Clone)]
|
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub front_matter: frontmatter::Data,
|
pub front_matter: frontmatter::Data,
|
||||||
pub link: String,
|
pub link: String,
|
||||||
|
@ -19,7 +19,7 @@ pub struct Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used with the Android app to show information in a widget.
|
/// Used with the Android app to show information in a widget.
|
||||||
#[derive(Eq, PartialEq, Debug, Clone, Serialize)]
|
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct NewPost {
|
pub struct NewPost {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
|
|
Loading…
Reference in New Issue