This commit is contained in:
parent
152ccf8ba5
commit
692e765561
157
src/main.rs
157
src/main.rs
|
@ -15,8 +15,9 @@ use axum::{
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use file_format::{FileFormat, Kind};
|
use file_format::{FileFormat, Kind};
|
||||||
use hyper::body::Bytes;
|
use hyper::body::Bytes;
|
||||||
use maud::{html, Markup, DOCTYPE, PreEscaped, Render};
|
use maud::{html, Markup, PreEscaped, Render, DOCTYPE};
|
||||||
use orgize::Org;
|
use orgize::Org;
|
||||||
|
use rss::ChannelBuilder;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
|
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions};
|
||||||
use sqlx::{FromRow, Pool, Sqlite};
|
use sqlx::{FromRow, Pool, Sqlite};
|
||||||
|
@ -25,10 +26,9 @@ use std::fs::{self, File};
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use time::{self, format_description, format_description::well_known::Rfc2822};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use rss::ChannelBuilder;
|
|
||||||
use time::{self, format_description, format_description::well_known::Rfc2822};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CACHE: Mutex<HashMap<String, CachedPage>> = Mutex::new(HashMap::new());
|
static ref CACHE: Mutex<HashMap<String, CachedPage>> = Mutex::new(HashMap::new());
|
||||||
|
@ -76,22 +76,22 @@ struct ProjectConfig {
|
||||||
|
|
||||||
struct Post {
|
struct Post {
|
||||||
name: String,
|
name: String,
|
||||||
route: String,
|
route: String,
|
||||||
date: String,
|
date: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for Project {
|
impl Render for Project {
|
||||||
fn render(&self) -> Markup {
|
fn render(&self) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
div .project {
|
div .project {
|
||||||
h4 { ( self.name ) " " }
|
h4 { ( self.name ) " " }
|
||||||
@for language in &self.languages {
|
@for language in &self.languages {
|
||||||
span .language .(language.to_lowercase()) { ( language ) }
|
span .language .(language.to_lowercase()) { ( language ) }
|
||||||
}
|
}
|
||||||
p { ( self.description ) " " a href=(self.link.link) { (self.link.display_text) }}
|
p { ( self.description ) " " a href=(self.link.link) { (self.link.display_text) }}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -109,7 +109,7 @@ async fn main() -> Result<(), sqlx::Error> {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(homepage))
|
.route("/", get(homepage))
|
||||||
.route("/rss", get(rss))
|
.route("/rss", get(rss))
|
||||||
.route("/blog", get(list_blog_posts))
|
.route("/blog", get(list_blog_posts))
|
||||||
.route("/blog/:post", get(blog_post))
|
.route("/blog/:post", get(blog_post))
|
||||||
.nest_service("/assets", ServeDir::new("assets"))
|
.nest_service("/assets", ServeDir::new("assets"))
|
||||||
|
@ -127,7 +127,7 @@ async fn main() -> Result<(), sqlx::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_posts() -> Vec<Post> {
|
fn get_posts() -> Vec<Post> {
|
||||||
let mut posts: Vec<Post> = Vec::new();
|
let mut posts: Vec<Post> = Vec::new();
|
||||||
for entry in fs::read_dir("./posts").unwrap() {
|
for entry in fs::read_dir("./posts").unwrap() {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
@ -143,8 +143,8 @@ fn get_posts() -> Vec<Post> {
|
||||||
content.read(&mut buffer).unwrap();
|
content.read(&mut buffer).unwrap();
|
||||||
// Match date data of `date: YYYY-MM-DD` in the first 100 bytes
|
// Match date data of `date: YYYY-MM-DD` in the first 100 bytes
|
||||||
let metadata = String::from_utf8_lossy(&buffer);
|
let metadata = String::from_utf8_lossy(&buffer);
|
||||||
let metadata_lines = metadata.split("\n").collect::<Vec<&str>>();
|
let metadata_lines = metadata.split("\n").collect::<Vec<&str>>();
|
||||||
// dbg!(&metadata);
|
// dbg!(&metadata);
|
||||||
// Split by --- and get the second element
|
// Split by --- and get the second element
|
||||||
let date = metadata_lines
|
let date = metadata_lines
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -152,28 +152,29 @@ fn get_posts() -> Vec<Post> {
|
||||||
.unwrap_or(&"")
|
.unwrap_or(&"")
|
||||||
.split(":")
|
.split(":")
|
||||||
.collect::<Vec<&str>>()[1];
|
.collect::<Vec<&str>>()[1];
|
||||||
let title = metadata_lines
|
let title = metadata_lines
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&x| x.contains("title:"))
|
.find(|&x| x.contains("title:"))
|
||||||
.unwrap_or(&"")
|
.unwrap_or(&"")
|
||||||
.split(":")
|
.split(":")
|
||||||
.collect::<Vec<&str>>()[1].trim();
|
.collect::<Vec<&str>>()[1]
|
||||||
|
.trim();
|
||||||
let date = date.trim();
|
let date = date.trim();
|
||||||
|
|
||||||
posts.push(Post {
|
posts.push(Post {
|
||||||
name: title.to_owned(),
|
name: title.to_owned(),
|
||||||
route: fname,
|
route: fname,
|
||||||
date: date.to_owned(),
|
date: date.to_owned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
posts.sort_by(|a, b| b.date.cmp(&a.date));
|
posts.sort_by(|a, b| b.date.cmp(&a.date));
|
||||||
posts
|
posts
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rss() -> Result<impl IntoResponse, StatusCode> {
|
async fn rss() -> Result<impl IntoResponse, StatusCode> {
|
||||||
let posts = get_posts();
|
let posts = get_posts();
|
||||||
let rss_posts: Vec<rss::Item> = posts.into_iter().map(|p| {
|
let rss_posts: Vec<rss::Item> = posts.into_iter().map(|p| {
|
||||||
let date = format!("{} 00:00:00 +00:00:00", p.date);
|
let date = format!("{} 00:00:00 +00:00:00", p.date);
|
||||||
let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]:[offset_second]").unwrap();
|
let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]:[offset_second]").unwrap();
|
||||||
let pub_date = match time::OffsetDateTime::parse(&date, &format).unwrap().format(&Rfc2822) {
|
let pub_date = match time::OffsetDateTime::parse(&date, &format).unwrap().format(&Rfc2822) {
|
||||||
|
@ -186,28 +187,28 @@ async fn rss() -> Result<impl IntoResponse, StatusCode> {
|
||||||
.pub_date(Some(pub_date))
|
.pub_date(Some(pub_date))
|
||||||
.build()
|
.build()
|
||||||
}).collect();
|
}).collect();
|
||||||
let channel = ChannelBuilder::default()
|
let channel = ChannelBuilder::default()
|
||||||
.title("Gabriel Simmer's Blog".to_owned())
|
.title("Gabriel Simmer's Blog".to_owned())
|
||||||
.link("https://gabrielsimmer.com/blog".to_owned())
|
.link("https://gabrielsimmer.com/blog".to_owned())
|
||||||
.description("Gabriel Simmer's Blog Posts.".to_owned())
|
.description("Gabriel Simmer's Blog Posts.".to_owned())
|
||||||
.items(rss_posts)
|
.items(rss_posts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Ok(Response::builder()
|
return Ok(Response::builder()
|
||||||
.header("content-type", "application/rss+xml")
|
.header("content-type", "application/rss+xml")
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
.body(Full::from(channel.to_string()))
|
.body(Full::from(channel.to_string()))
|
||||||
.unwrap());
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header(page_title: &str) -> Markup {
|
fn header(page_title: &str) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
(DOCTYPE)
|
(DOCTYPE)
|
||||||
meta charset="utf-8";
|
meta charset="utf-8";
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1";
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
||||||
title { (page_title) }
|
title { (page_title) }
|
||||||
link rel="stylesheet" href="/assets/styles.css";
|
link rel="stylesheet" href="/assets/styles.css";
|
||||||
script defer data-domain="gabrielsimmer.com" src="https://plausible.io/js/script.js" { };
|
script defer data-domain="gabrielsimmer.com" src="https://plausible.io/js/script.js" { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,11 +226,11 @@ async fn homepage() -> Markup {
|
||||||
p { a href = "/blog" { "Blog" } " " a href = "https://floofy.tech/@arch" rel = "me" { "Fediverse" } }
|
p { a href = "/blog" { "Blog" } " " a href = "https://floofy.tech/@arch" rel = "me" { "Fediverse" } }
|
||||||
h3 { "Projects" }
|
h3 { "Projects" }
|
||||||
@for project in projects.projects {
|
@for project in projects.projects {
|
||||||
(project)
|
(project)
|
||||||
}
|
}
|
||||||
h3 { "Experiments" }
|
h3 { "Experiments" }
|
||||||
@for project in projects.experiments {
|
@for project in projects.experiments {
|
||||||
(project)
|
(project)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,52 +271,52 @@ async fn blog_post(Path(post): Path<String>) -> Result<impl IntoResponse, Status
|
||||||
let content = fs::read_to_string(&path).unwrap();
|
let content = fs::read_to_string(&path).unwrap();
|
||||||
|
|
||||||
let mut html = "".to_owned();
|
let mut html = "".to_owned();
|
||||||
let mut date = "".to_owned();
|
let mut date = "".to_owned();
|
||||||
let mut title = "".to_owned();
|
let mut title = "".to_owned();
|
||||||
|
|
||||||
if ext == "md" {
|
if ext == "md" {
|
||||||
let (parsed, content) = frontmatter::parse_and_find_content(&content).unwrap();
|
let (parsed, content) = frontmatter::parse_and_find_content(&content).unwrap();
|
||||||
let metadata = parsed.unwrap();
|
let metadata = parsed.unwrap();
|
||||||
date = metadata["date"].as_str().unwrap().to_owned();
|
date = metadata["date"].as_str().unwrap().to_owned();
|
||||||
title = metadata["title"].as_str().unwrap().to_owned();
|
title = metadata["title"].as_str().unwrap().to_owned();
|
||||||
html = comrak::markdown_to_html(&content, &comrak::ComrakOptions::default());
|
html = comrak::markdown_to_html(&content, &comrak::ComrakOptions::default());
|
||||||
} else if ext == "org" {
|
} else if ext == "org" {
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
let parsed = Org::parse(&content);
|
let parsed = Org::parse(&content);
|
||||||
let keywords = parsed.keywords();
|
let keywords = parsed.keywords();
|
||||||
// Get date and title from keywords iterator
|
// Get date and title from keywords iterator
|
||||||
|
|
||||||
for keyword in keywords {
|
for keyword in keywords {
|
||||||
if keyword.key == "date" {
|
if keyword.key == "date" {
|
||||||
date = keyword.value.to_string();
|
date = keyword.value.to_string();
|
||||||
} else if keyword.key == "title" {
|
} else if keyword.key == "title" {
|
||||||
title = keyword.value.to_string();
|
title = keyword.value.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parsed.write_html(&mut writer).unwrap();
|
parsed.write_html(&mut writer).unwrap();
|
||||||
html = String::from_utf8(writer).unwrap();
|
html = String::from_utf8(writer).unwrap();
|
||||||
}
|
}
|
||||||
let html_maud = PreEscaped(html);
|
let html_maud = PreEscaped(html);
|
||||||
let html = html! {
|
let html = html! {
|
||||||
(header(title.as_str()))
|
(header(title.as_str()))
|
||||||
body {
|
body {
|
||||||
main {
|
main {
|
||||||
h1 { (title) }
|
h1 { (title) }
|
||||||
p { (date) }
|
p { (date) }
|
||||||
(html_maud)
|
(html_maud)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(Response::builder()
|
return Ok(Response::builder()
|
||||||
.header("content-type", "text/html")
|
.header("content-type", "text/html")
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
.body(Full::from(html.into_string()))
|
.body(Full::from(html.into_string()))
|
||||||
.unwrap());
|
.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(StatusCode::NOT_FOUND);
|
return Err(StatusCode::NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn cached_page<T>(
|
async fn cached_page<T>(
|
||||||
|
@ -420,7 +421,7 @@ async fn cached_page<T>(
|
||||||
.header("cache", "not")
|
.header("cache", "not")
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
.body(Full::from(bytes))
|
.body(Full::from(bytes))
|
||||||
.unwrap()
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = String::from_utf8(res).unwrap();
|
let content = String::from_utf8(res).unwrap();
|
||||||
|
|
Loading…
Reference in a new issue