i don't know
This commit is contained in:
parent
69680f227c
commit
28a2b3ca43
473
Cargo.lock
generated
473
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,7 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.6.12", features = ["http2"] }
|
axum = { version = "0.6.12", features = ["http2"] }
|
||||||
cached = "0.42.0"
|
cached = "0.44.0"
|
||||||
chrono = { version = "0.4.24", features = ["serde"] }
|
chrono = { version = "0.4.24", features = ["serde"] }
|
||||||
color-eyre = "0.6.1"
|
color-eyre = "0.6.1"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
|
@ -27,5 +27,5 @@ toml = "0.7.3"
|
||||||
tower = { version = "0.4.12", features = ["full"] }
|
tower = { version = "0.4.12", features = ["full"] }
|
||||||
tower-http = { version = "0.4.0", features = ["full"] }
|
tower-http = { version = "0.4.0", features = ["full"] }
|
||||||
tracing = "0.1.35"
|
tracing = "0.1.35"
|
||||||
tracing-opentelemetry = "0.18.0"
|
tracing-opentelemetry = "0.19.0"
|
||||||
tracing-subscriber = { version = "0.3.11", features = ["fmt", "env-filter", "json", "tracing-log"] }
|
tracing-subscriber = { version = "0.3.11", features = ["fmt", "env-filter", "json", "tracing-log"] }
|
||||||
|
|
12
src/feed.rs
12
src/feed.rs
|
@ -62,3 +62,15 @@ pub fn render_atom_tag_feed(tag: &Tag, state: &AppState) -> Result<String> {
|
||||||
|
|
||||||
Ok(state.tera.render("atom.xml", &ctx)?)
|
Ok(state.tera.render("atom.xml", &ctx)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct JsonFeed<'a> {
|
||||||
|
version: &'a str,
|
||||||
|
title: &'a str,
|
||||||
|
home_page_url: &'a str,
|
||||||
|
feed_url: &'a str,
|
||||||
|
items: Vec<JsonFeedItem<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct JsonFeedItem<'a> {
|
||||||
|
id: &'a str,
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use hyper::{header::CONTENT_TYPE, Request, StatusCode};
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
use hyper::{
|
||||||
|
header::{self, CONTENT_TYPE},
|
||||||
|
HeaderMap, Request, StatusCode,
|
||||||
|
};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
use prometheus::{opts, Encoder, IntCounterVec, TextEncoder};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -52,13 +56,19 @@ pub fn routes(state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> std::result::Result<Html<String>, WebsiteError> {
|
) -> std::result::Result<Response, 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);
|
||||||
WebsiteError::NotFound
|
WebsiteError::NotFound
|
||||||
})?;
|
})?;
|
||||||
Ok(Html(res))
|
Ok(
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::LAST_MODIFIED, state.startup_time.to_rfc2822())],
|
||||||
|
Html(res)
|
||||||
|
).into_response()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn healthcheck() -> &'static str {
|
async fn healthcheck() -> &'static str {
|
||||||
|
@ -101,6 +111,24 @@ pub async fn metrics_middleware<B>(request: Request<B>, next: Next<B>) -> Respon
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_return_304(headers: HeaderMap, last_changed: Option<DateTime<FixedOffset>>) -> bool {
|
||||||
|
let Some(date) = last_changed else {
|
||||||
|
info!("no last modified date");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(since) = headers.get(header::IF_MODIFIED_SINCE) else {
|
||||||
|
info!("no IF_MODIFIED_SINCE header");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(parsed) = DateTime::<FixedOffset>::parse_from_rfc2822(since.to_str().unwrap()) else {
|
||||||
|
info!("failed to parse IF_MODIFIED_SINCE header");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
date > parsed
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for WebsiteError {
|
impl IntoResponse for WebsiteError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
match self {
|
match self {
|
||||||
|
@ -139,6 +167,7 @@ mod tests {
|
||||||
// aliases overlap with themselves or other routes
|
// aliases overlap with themselves or other routes
|
||||||
let posts = crate::post::load_all().await.unwrap();
|
let posts = crate::post::load_all().await.unwrap();
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
|
startup_time: chrono::offset::Utc::now(),
|
||||||
base_url: "http://localhost:8180".into(),
|
base_url: "http://localhost:8180".into(),
|
||||||
tera: tera::Tera::new("templates/**/*").unwrap(),
|
tera: tera::Tera::new("templates/**/*").unwrap(),
|
||||||
tags: crate::tag::get_tags(posts.values()),
|
tags: crate::tag::get_tags(posts.values()),
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use std::sync::Arc;
|
use std::{sync::Arc};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
response::{Html, IntoResponse, Redirect},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use hyper::{header::CONTENT_TYPE, StatusCode};
|
use hyper::{
|
||||||
|
header::{self, CONTENT_TYPE},
|
||||||
|
HeaderMap, StatusCode,
|
||||||
|
};
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use tracing::{instrument, log::*};
|
use tracing::{instrument, log::*};
|
||||||
|
|
||||||
|
@ -15,6 +18,8 @@ use crate::{
|
||||||
AppState, WebsiteError,
|
AppState, WebsiteError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::should_return_304;
|
||||||
|
|
||||||
pub fn router() -> Router<Arc<AppState>> {
|
pub fn router() -> Router<Arc<AppState>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/posts", get(|| async { Redirect::permanent("/") }))
|
.route("/posts", get(|| async { Redirect::permanent("/") }))
|
||||||
|
@ -49,9 +54,18 @@ struct PageContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(State(state): State<Arc<AppState>>) -> Result<Html<String>, WebsiteError> {
|
pub async fn index(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<Response, WebsiteError> {
|
||||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
||||||
|
|
||||||
|
let last_changed = posts.iter().flat_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
if should_return_304(headers, last_changed) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
posts.reverse();
|
posts.reverse();
|
||||||
|
|
||||||
|
@ -64,15 +78,39 @@ pub async fn index(State(state): State<Arc<AppState>>) -> Result<Html<String>, W
|
||||||
|
|
||||||
let res = state.tera.render("posts_index.html", &c)?;
|
let res = state.tera.render("posts_index.html", &c)?;
|
||||||
|
|
||||||
Ok(Html(res))
|
let mut headers = vec![];
|
||||||
|
|
||||||
|
if let Some(date) = last_changed {
|
||||||
|
headers.push((header::LAST_MODIFIED, date.to_rfc2822()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
[(
|
||||||
|
header::LAST_MODIFIED,
|
||||||
|
last_changed
|
||||||
|
.map(|d| d.to_rfc2822())
|
||||||
|
.unwrap_or_else(|| chrono::offset::Utc::now().to_rfc2822()),
|
||||||
|
)],
|
||||||
|
Html(res),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Html<String>, WebsiteError> {
|
headers: HeaderMap,
|
||||||
|
) -> Result<axum::response::Response, WebsiteError> {
|
||||||
let post = state.posts.get(&slug).ok_or(WebsiteError::NotFound)?;
|
let post = state.posts.get(&slug).ok_or(WebsiteError::NotFound)?;
|
||||||
|
|
||||||
|
let last_changed = post.last_modified();
|
||||||
|
|
||||||
|
if should_return_304(headers, last_changed) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
if !post.is_published() {
|
if !post.is_published() {
|
||||||
warn!("attempted to view post before it has been published!");
|
warn!("attempted to view post before it has been published!");
|
||||||
return Err(WebsiteError::NotFound);
|
return Err(WebsiteError::NotFound);
|
||||||
|
@ -80,21 +118,49 @@ pub async fn view(
|
||||||
|
|
||||||
let res = render_post(&state, post).await?;
|
let res = render_post(&state, post).await?;
|
||||||
|
|
||||||
Ok(Html(res))
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
[(
|
||||||
|
header::LAST_MODIFIED,
|
||||||
|
last_changed
|
||||||
|
.map(|d| d.to_rfc2822())
|
||||||
|
.unwrap_or_else(|| chrono::offset::Utc::now().to_rfc2822()),
|
||||||
|
)],
|
||||||
|
Html(res),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn feed(State(state): State<Arc<AppState>>) -> Result<impl IntoResponse, WebsiteError> {
|
pub async fn feed(
|
||||||
|
State(state): State<Arc<AppState>>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> Result<Response, WebsiteError> {
|
||||||
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
let mut posts: Vec<&Post> = state.posts.values().filter(|p| p.is_published()).collect();
|
||||||
|
|
||||||
|
let last_changed = posts.iter().flat_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
if should_return_304(headers, last_changed) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
posts.reverse();
|
posts.reverse();
|
||||||
posts.truncate(10);
|
posts.truncate(10);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(CONTENT_TYPE, "application/atom+xml")],
|
[
|
||||||
|
(CONTENT_TYPE, "application/atom+xml"),
|
||||||
|
(
|
||||||
|
header::LAST_MODIFIED,
|
||||||
|
&last_changed
|
||||||
|
.map(|d| d.to_rfc2822())
|
||||||
|
.unwrap_or_else(|| chrono::offset::Utc::now().to_rfc2822()),
|
||||||
|
),
|
||||||
|
],
|
||||||
crate::feed::render_atom_feed(&state)?,
|
crate::feed::render_atom_feed(&state)?,
|
||||||
))
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
|
|
@ -2,16 +2,18 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
response::{Html, IntoResponse, Redirect},
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use hyper::{header::CONTENT_TYPE, StatusCode};
|
use hyper::{header::{CONTENT_TYPE, self}, HeaderMap, StatusCode};
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::{post::Post, AppState, WebsiteError};
|
use crate::{post::Post, AppState, WebsiteError};
|
||||||
|
|
||||||
|
use super::should_return_304;
|
||||||
|
|
||||||
pub fn router() -> Router<Arc<AppState>> {
|
pub fn router() -> Router<Arc<AppState>> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/tags", get(|| async { Redirect::permanent("/") }))
|
.route("/tags", get(|| async { Redirect::permanent("/") }))
|
||||||
|
@ -27,7 +29,7 @@ struct TagContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn index(State(state): State<Arc<AppState>>) -> Result<Html<String>, WebsiteError> {
|
pub async fn index(State(state): State<Arc<AppState>>) -> Result<Response, WebsiteError> {
|
||||||
let tags: Vec<_> = state.tags.values().collect();
|
let tags: Vec<_> = state.tags.values().collect();
|
||||||
let ctx = TagContext { title: "Tags" };
|
let ctx = TagContext { title: "Tags" };
|
||||||
|
|
||||||
|
@ -37,20 +39,31 @@ pub async fn index(State(state): State<Arc<AppState>>) -> Result<Html<String>, W
|
||||||
|
|
||||||
let res = state.tera.render("tags_index.html", &c)?;
|
let res = state.tera.render("tags_index.html", &c)?;
|
||||||
|
|
||||||
Ok(Html(res))
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::LAST_MODIFIED, state.startup_time.to_rfc2822())],
|
||||||
|
Html(res)
|
||||||
|
).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
Path(tag): Path<String>,
|
Path(tag): Path<String>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<Html<String>, WebsiteError> {
|
headers: HeaderMap,
|
||||||
|
) -> Result<Response, WebsiteError> {
|
||||||
let mut posts: Vec<&Post> = state
|
let mut posts: Vec<&Post> = state
|
||||||
.posts
|
.posts
|
||||||
.values()
|
.values()
|
||||||
.filter(|p| p.is_published() && p.tags.contains(&tag))
|
.filter(|p| p.is_published() && p.tags.contains(&tag))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let last_changed = posts.iter().flat_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
if should_return_304(headers, last_changed) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
posts.reverse();
|
posts.reverse();
|
||||||
|
|
||||||
|
@ -65,13 +78,27 @@ pub async fn view(
|
||||||
|
|
||||||
let res = state.tera.render("tag.html", &c)?;
|
let res = state.tera.render("tag.html", &c)?;
|
||||||
|
|
||||||
Ok(Html(res))
|
Ok(
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[
|
||||||
|
(
|
||||||
|
header::LAST_MODIFIED,
|
||||||
|
&last_changed
|
||||||
|
.map(|d| d.to_rfc2822())
|
||||||
|
.unwrap_or_else(|| chrono::offset::Utc::now().to_rfc2822()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
Html(res)
|
||||||
|
).into_response()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn feed(
|
pub async fn feed(
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
) -> Result<impl IntoResponse, WebsiteError> {
|
headers: HeaderMap,
|
||||||
|
) -> Result<Response, WebsiteError> {
|
||||||
let tag = state.tags.get(&slug).ok_or(WebsiteError::NotFound)?;
|
let tag = state.tags.get(&slug).ok_or(WebsiteError::NotFound)?;
|
||||||
|
|
||||||
let mut posts: Vec<&Post> = state
|
let mut posts: Vec<&Post> = state
|
||||||
|
@ -80,15 +107,29 @@ pub async fn feed(
|
||||||
.filter(|p| p.is_published() && p.tags.contains(&slug))
|
.filter(|p| p.is_published() && p.tags.contains(&slug))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let last_changed = posts.iter().flat_map(|p| p.last_modified()).max();
|
||||||
|
|
||||||
|
if should_return_304(headers, last_changed) {
|
||||||
|
return Ok(StatusCode::NOT_MODIFIED.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
posts.sort_by_key(|p| &p.date);
|
posts.sort_by_key(|p| &p.date);
|
||||||
posts.reverse();
|
posts.reverse();
|
||||||
posts.truncate(10);
|
posts.truncate(10);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(CONTENT_TYPE, "application/atom+xml")],
|
[(CONTENT_TYPE, "application/atom+xml"),
|
||||||
|
(
|
||||||
|
header::LAST_MODIFIED,
|
||||||
|
&last_changed
|
||||||
|
.map(|d| d.to_rfc2822())
|
||||||
|
.unwrap_or_else(|| chrono::offset::Utc::now().to_rfc2822()),
|
||||||
|
)
|
||||||
|
],
|
||||||
crate::feed::render_atom_tag_feed(tag, &state)?,
|
crate::feed::render_atom_tag_feed(tag, &state)?,
|
||||||
))
|
)
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub fn hilight(content: &str, lang: &str) -> color_eyre::Result<String> {
|
pub fn hilight(content: &str, lang: &str, theme: Option<&str>) -> color_eyre::Result<String> {
|
||||||
let ss = SyntaxSet::load_defaults_newlines();
|
let ss = SyntaxSet::load_defaults_newlines();
|
||||||
let s = ss.find_syntax_by_extension(lang).unwrap_or_else(|| {
|
let s = ss
|
||||||
|
.find_syntax_by_extension(lang)
|
||||||
|
.or_else(|| ss.find_syntax_by_name(lang))
|
||||||
|
.unwrap_or_else(|| {
|
||||||
error!("Syntax not found for language: {}", lang);
|
error!("Syntax not found for language: {}", lang);
|
||||||
ss.find_syntax_plain_text()
|
ss.find_syntax_plain_text()
|
||||||
});
|
});
|
||||||
let ts = ThemeSet::load_defaults();
|
let ts = ThemeSet::load_defaults();
|
||||||
let theme = ts.themes.first_key_value().unwrap().1; // TODO
|
let theme = if let Some(t) = theme {
|
||||||
|
ts.themes
|
||||||
|
.get(t)
|
||||||
|
.unwrap_or_else(|| ts.themes.first_key_value().unwrap().1)
|
||||||
|
} else {
|
||||||
|
ts.themes.first_key_value().unwrap().1
|
||||||
|
}; // TODO
|
||||||
|
|
||||||
let res = syntect::html::highlighted_html_for_string(content, &ss, s, theme)?;
|
let res = syntect::html::highlighted_html_for_string(content, &ss, s, theme)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
|
use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use axum::extract::MatchedPath;
|
use axum::extract::MatchedPath;
|
||||||
|
use chrono::DateTime;
|
||||||
use color_eyre::eyre::{Error, Result};
|
use color_eyre::eyre::{Error, Result};
|
||||||
|
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
|
@ -21,6 +22,7 @@ mod tag;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
startup_time: DateTime<chrono::offset::Utc>,
|
||||||
base_url: String,
|
base_url: String,
|
||||||
posts: HashMap<String, Post>,
|
posts: HashMap<String, Post>,
|
||||||
tags: HashMap<String, Tag>,
|
tags: HashMap<String, Tag>,
|
||||||
|
@ -35,12 +37,13 @@ async fn main() -> Result<()> {
|
||||||
info!("Starting server...");
|
info!("Starting server...");
|
||||||
|
|
||||||
let base_url = option_env!("SITE_BASE_URL")
|
let base_url = option_env!("SITE_BASE_URL")
|
||||||
.unwrap_or("http://localhost:8180")
|
.unwrap_or("http://localhost:8080")
|
||||||
.to_string();
|
.to_string();
|
||||||
let tera = Tera::new("templates/**/*")?;
|
let tera = Tera::new("templates/**/*")?;
|
||||||
let posts = post::load_all().await?;
|
let posts = post::load_all().await?;
|
||||||
let tags = tag::get_tags(posts.values());
|
let tags = tag::get_tags(posts.values());
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
|
startup_time: chrono::offset::Utc::now(),
|
||||||
base_url,
|
base_url,
|
||||||
tera,
|
tera,
|
||||||
posts,
|
posts,
|
||||||
|
@ -59,7 +62,7 @@ async fn main() -> Result<()> {
|
||||||
|
|
||||||
info!("Now listening at {}", state.base_url);
|
info!("Now listening at {}", state.base_url);
|
||||||
|
|
||||||
axum::Server::bind(&"0.0.0.0:8180".parse().unwrap())
|
axum::Server::bind(&"0.0.0.0:8080".parse().unwrap())
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,8 @@ pub fn render_markdown_to_html(markdown: &str) -> Result<String> {
|
||||||
code_block = false;
|
code_block = false;
|
||||||
|
|
||||||
let lang = code_lang.take().unwrap_or("".into());
|
let lang = code_lang.take().unwrap_or("".into());
|
||||||
let res = hilighting::hilight(&code_accumulator, &lang).unwrap();
|
let res = hilighting::hilight(&code_accumulator, &lang, Some("base16-ocean.dark"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
events.push(Event::Html(res.into()));
|
events.push(Event::Html(res.into()));
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,10 @@ impl Post {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_modified(&self) -> Option<DateTime<FixedOffset>> {
|
||||||
|
self.updated.or(self.date)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
|
|
|
@ -2,13 +2,30 @@ body {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags > li {
|
.tags > li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
height: 2em;
|
||||||
|
margin-bottom: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<li>✅ rss/atom/jsonfeed (atom is good enough for now)</li>
|
<li>✅ rss/atom/jsonfeed (atom is good enough for now)</li>
|
||||||
<li>✅ proper error handling (i guess??)</li>
|
<li>✅ proper error handling (i guess??)</li>
|
||||||
<li>✅ code hilighting? (good enough for now, gotta figure out themes n' stuff later)</li>
|
<li>✅ code hilighting? (good enough for now, gotta figure out themes n' stuff later)</li>
|
||||||
|
<li>⬜ cache headers (etag, last-modified, returning 304, other related headers)
|
||||||
<li>⬜ sass compilation (using rsass? grass?)</li>
|
<li>⬜ sass compilation (using rsass? grass?)</li>
|
||||||
<li>⬜ fancy styling</li>
|
<li>⬜ fancy styling</li>
|
||||||
<li>⬜ other pages???</li>
|
<li>⬜ other pages???</li>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/">tollyx</a> - <a href="/posts/">posts</a>
|
<img src="/static/avatar.png" class="avatar"/> <a href="/">tollyx</a> - <a href="/posts/">posts</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
Loading…
Reference in a new issue