This commit is contained in:
Dominic Grimm 2023-02-11 12:48:39 +01:00
parent 584c07ff23
commit 53e144d9a7
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
24 changed files with 1236 additions and 254 deletions

View file

@ -1,4 +1,3 @@
use diesel::QueryDsl;
#[cfg(not(target_env = "msvc"))]
use tikv_jemallocator::Jemalloc;
@ -6,104 +5,23 @@ use tikv_jemallocator::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
use actix_web::{get, http::StatusCode, middleware, web, App, HttpResponse, HttpServer};
use actix_web_static_files::ResourceFiles;
use askama_actix::{Template, TemplateToResponse};
use diesel::prelude::*;
use backend::*;
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
mod filters {
pub fn cmark<T: std::fmt::Display>(s: T) -> ::askama::Result<String> {
let s = s.to_string();
let options = pulldown_cmark::Options::empty();
let parser = pulldown_cmark::Parser::new_ext(&s, options);
let mut html_output = String::with_capacity(s.len() * 3 / 2);
pulldown_cmark::html::push_html(&mut html_output, parser);
Ok(html_output)
}
}
#[derive(Template)]
#[template(path = "status_code.html")]
struct StatusCodeTemplate {
status_code: StatusCode,
message: Option<String>,
}
#[derive(Template)]
#[template(path = "posts/{slug}.html")]
struct PostBySlugTemplate {
post: db::models::Post,
}
async fn not_found() -> HttpResponse {
StatusCodeTemplate {
status_code: StatusCode::NOT_FOUND,
message: None,
}
.to_response()
}
#[get("/posts/{slug}")]
async fn post_by_slug(db_pool: web::Data<db::DbPool>, path: web::Path<String>) -> HttpResponse {
let slug = path.into_inner();
let conn = &mut match db_pool.get() {
Ok(x) => x,
Err(e) => return HttpResponse::InternalServerError().body(format!("{:?}", e)),
};
let post = match db::schema::posts::table
.filter(db::schema::posts::slug.eq(&slug))
.filter(db::schema::posts::active)
.first::<db::models::Post>(conn)
.optional()
{
Ok(x) => x,
Err(e) => {
dbg!(&e);
return HttpResponse::InternalServerError().body(format!("{:?}", e));
}
};
match post {
Some(x) => PostBySlugTemplate { post: x }.to_response(),
None => {
let mut resp = StatusCodeTemplate {
status_code: StatusCode::NOT_FOUND,
message: None,
}
.to_response();
*resp.status_mut() = StatusCode::NOT_FOUND;
resp
}
}
}
use actix_web::{middleware, App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
log::info!("Listening to requests at http://0.0.0.0:80");
log::info!(
"Listening to requests at {}",
backend::config::CONFIG.bind_url
);
HttpServer::new(move || {
let generated = generate();
App::new()
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default())
.app_data(web::Data::new(db::pool().unwrap()))
.service(post_by_slug)
.service(ResourceFiles::new("/static", generated))
.default_service(web::route().to(not_found))
.configure(backend::web::init)
})
.bind("0.0.0.0:80")
.bind(&backend::config::CONFIG.bind_url)
.unwrap()
.run()
.await

View file

@ -9,6 +9,7 @@ use anyhow::{bail, Result};
use chrono::prelude::*;
use clap::{Parser, Subcommand};
use diesel::prelude::*;
use r2d2_redis::redis;
use scan_dir::ScanDir;
use serde::{Deserialize, Serialize};
use std::fs;
@ -27,6 +28,9 @@ struct Cli {
enum Commands {
#[clap(about = "Imports new posts")]
Import,
#[clap(about = "Clears redis cache")]
Clear,
}
#[derive(Deserialize, Serialize, Debug)]
@ -50,88 +54,137 @@ struct Post {
fn main() -> Result<()> {
match Cli::parse().commands {
Commands::Import => {
let conn = &mut db::establish_connection()?;
let db_conn = &mut db::establish_connection()?;
let redis_conn = &mut cache::establish_connection()?;
for post in ScanDir::dirs().read("/blog", |iter| {
iter.map(|(entry, _)| {
let path = entry.path().join("post.md");
let src = fs::read_to_string(&path)?;
let frontmatter = match fronma::parser::parse::<PostFrontmatter>(&src) {
Ok(x) => x,
Err(x) => bail!("Error parsing frontmatter: {:?}", x),
};
let posts = ScanDir::dirs()
.read("/blog", |iter| {
iter.map(|(entry, _)| {
let path = entry.path().join("post.md");
let src = fs::read_to_string(&path)?;
let frontmatter = match fronma::parser::parse::<PostFrontmatter>(&src) {
Ok(x) => x,
Err(x) => bail!("Error parsing frontmatter: {:?}", x),
};
Ok(Post {
path,
frontmatter: frontmatter.headers,
content: frontmatter.body.to_string(),
})
})
.collect::<Result<Vec<_>>>()
})?? {
let content = post.content.trim();
if let Some(id) = post.frontmatter.id {
diesel::update(db::schema::posts::table)
.filter(db::schema::posts::id.eq(id))
.set(db::models::UpdatePost {
name: Some(&post.frontmatter.name),
slug: Some(&post.frontmatter.slug),
description: Some(&post.frontmatter.description),
content: Some(content),
published_at: Some(post.frontmatter.published_at),
edited_at: Some(post.frontmatter.edited_at),
active: Some(post.frontmatter.active),
Ok(Post {
path,
frontmatter: frontmatter.headers,
content: frontmatter.body.to_string(),
})
.execute(conn)?;
} else {
let id = if let Some(id) = db::schema::posts::table
.select(db::schema::posts::id)
.filter(db::schema::posts::slug.eq(&post.frontmatter.slug))
.first::<i32>(conn)
.optional()?
{
})
.collect::<Result<Vec<_>>>()
})??
.into_iter()
.map(|post| -> Result<_> {
let trimmed = PostFrontmatter {
id: post.frontmatter.id,
name: post.frontmatter.name.trim().to_string(),
slug: post.frontmatter.slug.trim().to_string(),
description: post.frontmatter.description.trim().to_string(),
published_at: post.frontmatter.published_at,
edited_at: post.frontmatter.edited_at,
active: post.frontmatter.active,
};
let content = post.content.trim();
if let Some(id) = trimmed.id {
diesel::update(db::schema::posts::table)
.filter(db::schema::posts::id.eq(id))
.set(db::models::UpdatePost {
name: Some(&post.frontmatter.name),
slug: None,
description: Some(&post.frontmatter.description),
name: Some(&trimmed.name),
slug: Some(&trimmed.slug),
description: Some(&trimmed.description),
content: Some(content),
published_at: Some(post.frontmatter.published_at),
edited_at: Some(post.frontmatter.edited_at),
active: Some(post.frontmatter.active),
published_at: Some(trimmed.published_at),
edited_at: Some(trimmed.edited_at),
active: Some(trimmed.active),
})
.execute(conn)?;
.execute(db_conn)?;
id
Ok(id)
} else {
diesel::insert_into(db::schema::posts::table)
.values(db::models::NewPost {
name: &post.frontmatter.name,
slug: &post.frontmatter.slug,
description: &post.frontmatter.description,
content: content,
published_at: post.frontmatter.published_at,
edited_at: post.frontmatter.edited_at,
active: post.frontmatter.active,
})
.returning(db::schema::posts::id)
.get_result::<i32>(conn)?
};
let id = if let Some(id) = db::schema::posts::table
.select(db::schema::posts::id)
.filter(db::schema::posts::slug.eq(&trimmed.slug))
.first::<i32>(db_conn)
.optional()?
{
diesel::update(db::schema::posts::table)
.filter(db::schema::posts::id.eq(id))
.set(db::models::UpdatePost {
name: Some(&trimmed.name),
slug: None,
description: Some(&trimmed.description),
content: Some(content),
published_at: Some(trimmed.published_at),
edited_at: Some(trimmed.edited_at),
active: Some(trimmed.active),
})
.execute(db_conn)?;
fs::write(
post.path,
format!(
"---\n{}---\n{}",
serde_yaml::to_string(&PostFrontmatter {
id: Some(id),
..post.frontmatter
})?,
post.content
),
)?;
}
id
} else {
diesel::insert_into(db::schema::posts::table)
.values(db::models::NewPost {
name: &trimmed.name,
slug: &trimmed.slug,
description: &trimmed.description,
content: content,
published_at: trimmed.published_at,
edited_at: trimmed.edited_at,
active: trimmed.active,
})
.returning(db::schema::posts::id)
.get_result::<i32>(db_conn)?
};
fs::write(
post.path,
format!(
"---\n{}---\n\n{}\n",
serde_yaml::to_string(&PostFrontmatter {
id: Some(id),
..trimmed
})?,
content
),
)?;
Ok(id)
}
})
.collect::<Result<Vec<_>>>()?;
let ids = db::schema::posts::table
.select(db::schema::posts::id)
.load::<i32>(db_conn)?;
diesel::delete(
db::schema::posts::table
.filter(diesel::dsl::not(db::schema::posts::id.eq_any(posts))),
)
.execute(db_conn)?;
for id in ids {
redis::cmd("DEL")
.arg(cache::keys::post_content(id))
.query::<()>(redis_conn)?;
}
Ok(())
}
Commands::Clear => {
let db_conn = &mut db::establish_connection()?;
let redis_conn = &mut cache::establish_connection()?;
for id in db::schema::posts::table
.select(db::schema::posts::id)
.load::<i32>(db_conn)?
{
redis::cmd("DEL")
.arg(cache::keys::post_content(id))
.query::<()>(redis_conn)?;
}
Ok(())