This commit is contained in:
Dominic Grimm 2023-02-08 22:13:11 +01:00
parent 94fb270008
commit 584c07ff23
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
22 changed files with 539 additions and 187 deletions

View file

@ -31,3 +31,11 @@ insert_final_newline = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.scss]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
backend/templates

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"files.associations": {
"**/templates/**/*.html": "jinja-html"
}
}

View file

@ -5,3 +5,4 @@ Dockerfile
.dockerignore .dockerignore
vendor/ vendor/
example/ example/
static/

180
backend/Cargo.lock generated
View file

@ -183,6 +183,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "actix-web-static-files"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830ce9c2daaefe6fc8a484774b9fc9e79ab5b4685064c85d50fd55e820b9a6c5"
dependencies = [
"actix-web",
"derive_more",
"futures-util",
"static-files",
]
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.19.0" version = "0.19.0"
@ -251,6 +263,65 @@ dependencies = [
"backtrace", "backtrace",
] ]
[[package]]
name = "askama"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139"
dependencies = [
"askama_derive",
"askama_escape",
"askama_shared",
]
[[package]]
name = "askama_actix"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c52f74f8382a142ecfc052100b21abc33f2c069e20fe345808e7ed914b179449"
dependencies = [
"actix-web",
"askama",
"askama_shared",
]
[[package]]
name = "askama_derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71"
dependencies = [
"askama_shared",
"proc-macro2",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_shared"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0"
dependencies = [
"askama_escape",
"humansize",
"mime",
"mime_guess",
"nom",
"num-traits",
"percent-encoding",
"proc-macro2",
"quote",
"serde",
"syn",
"toml",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -262,7 +333,10 @@ name = "backend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"actix-web-static-files",
"anyhow", "anyhow",
"askama",
"askama_actix",
"chrono", "chrono",
"clap", "clap",
"diesel", "diesel",
@ -271,9 +345,11 @@ dependencies = [
"fronma", "fronma",
"lazy_static", "lazy_static",
"log", "log",
"pulldown-cmark",
"scan_dir", "scan_dir",
"serde", "serde",
"serde_yaml 0.9.17", "serde_yaml 0.9.17",
"static-files",
"tikv-jemallocator", "tikv-jemallocator",
] ]
@ -376,6 +452,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "change-detection"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a"
dependencies = [
"path-matchers",
"path-slash",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.23" version = "0.4.23"
@ -710,6 +796,7 @@ dependencies = [
"futures-task", "futures-task",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab",
] ]
[[package]] [[package]]
@ -739,6 +826,12 @@ version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec" checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.15" version = "0.3.15"
@ -808,6 +901,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humansize"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -992,6 +1091,22 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.6.2" version = "0.6.2"
@ -1013,6 +1128,16 @@ dependencies = [
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1092,6 +1217,21 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "path-matchers"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b"
dependencies = [
"glob",
]
[[package]]
name = "path-slash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.2.0"
@ -1164,6 +1304,17 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "pulldown-cmark"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
@ -1430,6 +1581,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "static-files"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64712ea1e3e140010e1d9605872ba205afa2ab5bd38191cc6ebd248ae1f6a06b"
dependencies = [
"change-detection",
"mime_guess",
"path-slash",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -1561,6 +1723,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"
@ -1588,6 +1759,15 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.10" version = "0.3.10"

View file

@ -6,21 +6,25 @@ authors = ["Dominic Grimm <dominic@dergrimm.net>"]
[[bin]] [[bin]]
name = "backend" name = "backend"
build = "build.rs"
[[bin]] [[bin]]
name = "blogctl" name = "blogctl"
[profile.release] # [profile.release]
codegen-units = 1 # codegen-units = 1
lto = "fat" # lto = "fat"
strip = true # strip = true
panic = "abort" # panic = "abort"
# 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]
actix-web = "4.3.0" actix-web = "4.3.0"
actix-web-static-files = "4.0.0"
anyhow = { version = "1.0.69", features = ["backtrace"] } anyhow = { version = "1.0.69", features = ["backtrace"] }
askama = "0.11.1"
askama_actix = "0.13.0"
chrono = { version = "0.4.23", features = ["serde"] } chrono = { version = "0.4.23", features = ["serde"] }
clap = { version = "4.1.4", features = ["derive"] } clap = { version = "4.1.4", features = ["derive"] }
diesel = { version = "2.0.2", features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes", "postgres", "chrono", "r2d2"] } diesel = { version = "2.0.2", features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes", "postgres", "chrono", "r2d2"] }
@ -29,9 +33,14 @@ envconfig = "0.10.0"
fronma = "0.1.1" fronma = "0.1.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4.17" log = "0.4.17"
pulldown-cmark = { version = "0.9.2", default-features = false, features = ["simd"] }
scan_dir = "0.3.3" scan_dir = "0.3.3"
serde = { version = "1.0.152", features = ["derive"] } serde = { version = "1.0.152", features = ["derive"] }
serde_yaml = "0.9.17" serde_yaml = "0.9.17"
static-files = "0.2.3"
[target.'cfg(not(target_env = "msvc"))'.dependencies] [target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = "0.5.0" tikv-jemallocator = "0.5.0"
[build-dependencies]
static-files = "0.2.3"

View file

@ -1,3 +1,15 @@
FROM codycraven/sassc:latest as css
WORKDIR /usr/src/scss
RUN mkdir dist
WORKDIR /usr/src/scss/src
COPY ./scss .
RUN find . -name "*.scss" -type f | xargs -I % sh -c 'sassc % > ../dist/$(basename -- "%" .scss).css'
FROM tdewolff/minify:latest as static
WORKDIR /usr/src/static
COPY --from=css /usr/src/scss/dist ./css
RUN minify . -r -o .
FROM docker.io/lukemathwalker/cargo-chef:latest-rust-1.67.0 as chef FROM docker.io/lukemathwalker/cargo-chef:latest-rust-1.67.0 as chef
FROM chef as diesel FROM chef as diesel
@ -12,10 +24,13 @@ RUN cargo chef prepare --recipe-path recipe.json
FROM chef as builder FROM chef as builder
WORKDIR /usr/src/backend WORKDIR /usr/src/backend
COPY --from=planner /usr/src/backend/recipe.json . COPY --from=planner /usr/src/backend/recipe.json .
RUN cargo chef cook --release --recipe-path recipe.json RUN cargo chef cook --recipe-path recipe.json
RUN rm -rf ./src RUN rm -rf ./src
COPY ./build.rs .
COPY --from=static /usr/src/static ./static
COPY ./templates ./templates
COPY ./src ./src COPY ./src ./src
RUN cargo build --release RUN cargo build
FROM docker.io/debian:bullseye-slim as runner FROM docker.io/debian:bullseye-slim as runner
RUN apt update RUN apt update
@ -31,6 +46,6 @@ WORKDIR /usr/src/backend
COPY ./run.sh . COPY ./run.sh .
RUN chmod +x ./run.sh RUN chmod +x ./run.sh
COPY ./migrations ./migrations COPY ./migrations ./migrations
COPY --from=builder /usr/src/backend/target/release/backend /usr/src/backend/target/release/blogctl ./bin/ COPY --from=builder /usr/src/backend/target/debug/backend /usr/src/backend/target/debug/blogctl ./bin/
EXPOSE 80 EXPOSE 80
ENTRYPOINT [ "./run.sh" ] ENTRYPOINT [ "./run.sh" ]

5
backend/build.rs Normal file
View file

@ -0,0 +1,5 @@
use static_files::resource_dir;
fn main() -> std::io::Result<()> {
resource_dir("./static").build()
}

View file

@ -1,7 +1,3 @@
DROP TABLE posts; DROP TABLE posts;
DROP TABLE tags; DROP TABLE tags;
DROP INDEX configs_active;
DROP TABLE configs;

View file

@ -1,37 +1,3 @@
CREATE TABLE configs(
id SERIAL PRIMARY KEY,
active BOOLEAN NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL,
copyright TEXT NOT NULL,
owner_name TEXT NOT NULL,
owner_email TEXT NOT NULL,
owner_website TEXT
);
CREATE UNIQUE INDEX configs_active ON configs(active)
WHERE
active;
INSERT INTO
configs(
active,
name,
description,
copyright,
owner_name,
owner_email
)
VALUES
(
TRUE,
'generic blog',
'just a generic blog',
'(C) just a generic blog',
'generic blog owner',
'blog@example.com'
);
CREATE TABLE tags( CREATE TABLE tags(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT UNIQUE NOT NULL name TEXT UNIQUE NOT NULL

View file

@ -4,4 +4,4 @@
DATABASE_URL="$BACKEND_DB_URL" diesel setup \ DATABASE_URL="$BACKEND_DB_URL" diesel setup \
--migration-dir ./migrations \ --migration-dir ./migrations \
--locked-schema && --locked-schema &&
./bin/backend run RUST_LOG=info ./bin/backend

65
backend/scss/styles.scss Normal file
View file

@ -0,0 +1,65 @@
html,
body {
width: 100vw;
min-height: 100vh;
}
* {
margin: 0;
padding: 0;
font-family: "JetBrains Mono", monospace;
}
#wrapper {
width: 75%;
margin: 0 auto;
}
#navbar {
display: flex;
margin-bottom: 1%;
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
}
li {
float: left;
}
a {
display: block;
text-align: center;
padding: 1em;
text-decoration: none;
}
}
#navbar-brand {
width: 50%;
a {
float: left;
text-decoration: underline;
}
}
#navbar-links {
width: 50%;
ul {
float: right;
}
}
#content {
padding: 3%;
}
.double-border {
border: 5px double black;
}

View file

@ -1,3 +1,4 @@
use diesel::QueryDsl;
#[cfg(not(target_env = "msvc"))] #[cfg(not(target_env = "msvc"))]
use tikv_jemallocator::Jemalloc; use tikv_jemallocator::Jemalloc;
@ -5,42 +6,105 @@ use tikv_jemallocator::Jemalloc;
#[global_allocator] #[global_allocator]
static GLOBAL: Jemalloc = Jemalloc; static GLOBAL: Jemalloc = Jemalloc;
use actix_web::{ use actix_web::{get, http::StatusCode, middleware, web, App, HttpResponse, HttpServer};
http::header, use actix_web_static_files::ResourceFiles;
middleware, use askama_actix::{Template, TemplateToResponse};
web::{self, Data}, use diesel::prelude::*;
App, Error, HttpResponse, HttpServer,
};
use clap::{Parser, Subcommand};
use backend::*; use backend::*;
#[derive(Debug, Parser)] include!(concat!(env!("OUT_DIR"), "/generated.rs"));
#[clap(author, version, about, long_about = None)]
struct Cli { mod filters {
#[clap(subcommand)] pub fn cmark<T: std::fmt::Display>(s: T) -> ::askama::Result<String> {
commands: Commands, 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(Debug, Subcommand)] #[derive(Template)]
enum Commands { #[template(path = "status_code.html")]
#[clap(about = "Starts webserver")] struct StatusCodeTemplate {
Run, 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
}
}
} }
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
match Cli::parse().commands { env_logger::init();
Commands::Run => { log::info!("Listening to requests at http://0.0.0.0:80");
std::env::set_var("RUST_LOG", "info");
env_logger::init();
let server = HttpServer::new(move || { HttpServer::new(move || {
App::new() let generated = generate();
.wrap(middleware::Compress::default())
.wrap(middleware::Logger::default()) App::new()
}); .wrap(middleware::Compress::default())
server.bind("0.0.0.0:80").unwrap().run().await .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))
})
.bind("0.0.0.0:80")
.unwrap()
.run()
.await
} }

View file

@ -10,8 +10,9 @@ use chrono::prelude::*;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use diesel::prelude::*; use diesel::prelude::*;
use scan_dir::ScanDir; use scan_dir::ScanDir;
use serde::Deserialize; use serde::{Deserialize, Serialize};
use std::fs; use std::fs;
use std::path::PathBuf;
use backend::*; use backend::*;
@ -28,23 +29,9 @@ enum Commands {
Import, Import,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
struct Blog {
name: String,
description: String,
copyright: String,
owner: BlogOwner,
}
#[derive(Deserialize, Debug)]
struct BlogOwner {
name: String,
email: String,
website: Option<String>,
}
#[derive(Deserialize, Debug)]
struct PostFrontmatter { struct PostFrontmatter {
id: Option<i32>,
name: String, name: String,
slug: String, slug: String,
description: String, description: String,
@ -55,6 +42,7 @@ struct PostFrontmatter {
#[derive(Debug)] #[derive(Debug)]
struct Post { struct Post {
path: PathBuf,
frontmatter: PostFrontmatter, frontmatter: PostFrontmatter,
content: String, content: String,
} }
@ -64,61 +52,86 @@ fn main() -> Result<()> {
Commands::Import => { Commands::Import => {
let conn = &mut db::establish_connection()?; let conn = &mut db::establish_connection()?;
let blog: Blog = serde_yaml::from_str(&fs::read_to_string("/blog/blog.yml")?)?; for post in ScanDir::dirs().read("/blog", |iter| {
iter.map(|(entry, _)| {
diesel::delete(db::schema::configs::table) let path = entry.path().join("post.md");
.filter(db::schema::configs::active.eq(true)) let src = fs::read_to_string(&path)?;
.execute(conn)?;
diesel::insert_into(db::schema::configs::table)
.values(db::models::NewConfig {
active: true,
name: &blog.name,
description: &blog.description,
copyright: &blog.copyright,
owner_name: &blog.owner.name,
owner_email: &blog.owner.email,
owner_website: blog.owner.website.as_deref(),
})
.execute(conn)?;
let posts = ScanDir::dirs().read("/blog/posts", |iter| {
iter.map(|(entry, name)| {
dbg!(&entry, &name);
let src = fs::read_to_string(entry.path().join("post.md"))?;
let frontmatter = match fronma::parser::parse::<PostFrontmatter>(&src) { let frontmatter = match fronma::parser::parse::<PostFrontmatter>(&src) {
Ok(x) => x, Ok(x) => x,
Err(x) => bail!("Error parsing frontmatter: {:?}", x), Err(x) => bail!("Error parsing frontmatter: {:?}", x),
}; };
Ok(Post { Ok(Post {
frontmatter: PostFrontmatter { path,
name: frontmatter.headers.name.trim().to_string(), frontmatter: frontmatter.headers,
slug: frontmatter.headers.slug.trim().to_string(), content: frontmatter.body.to_string(),
description: frontmatter.headers.description.trim().to_string(),
..frontmatter.headers
},
content: frontmatter.body.trim().to_string(),
}) })
}) })
.collect::<Result<Vec<_>>>() .collect::<Result<Vec<_>>>()
})??; })?? {
dbg!(&posts); let content = post.content.trim();
for post in posts { if let Some(id) = post.frontmatter.id {
diesel::delete(db::schema::posts::table) diesel::update(db::schema::posts::table)
.filter(db::schema::posts::slug.eq(&post.frontmatter.slug)) .filter(db::schema::posts::id.eq(id))
.execute(conn)?; .set(db::models::UpdatePost {
diesel::insert_into(db::schema::posts::table) name: Some(&post.frontmatter.name),
.values(db::models::NewPost { slug: Some(&post.frontmatter.slug),
name: &post.frontmatter.name, description: Some(&post.frontmatter.description),
slug: &post.frontmatter.slug, content: Some(content),
description: &post.frontmatter.description, published_at: Some(post.frontmatter.published_at),
content: &post.content, edited_at: Some(post.frontmatter.edited_at),
published_at: post.frontmatter.published_at, active: Some(post.frontmatter.active),
edited_at: post.frontmatter.edited_at, })
active: post.frontmatter.active, .execute(conn)?;
}) } else {
.execute(conn)?; 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()?
{
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),
content: Some(content),
published_at: Some(post.frontmatter.published_at),
edited_at: Some(post.frontmatter.edited_at),
active: Some(post.frontmatter.active),
})
.execute(conn)?;
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)?
};
fs::write(
post.path,
format!(
"---\n{}---\n{}",
serde_yaml::to_string(&PostFrontmatter {
id: Some(id),
..post.frontmatter
})?,
post.content
),
)?;
}
} }
Ok(()) Ok(())

View file

@ -3,31 +3,6 @@ use diesel::prelude::*;
use crate::db::schema; use crate::db::schema;
#[derive(Identifiable, Queryable, Debug)]
#[diesel(table_name = schema::configs)]
pub struct Config {
pub id: i32,
pub active: bool,
pub name: String,
pub description: String,
pub copyright: String,
pub owner_name: String,
pub owner_email: String,
pub owner_website: Option<String>,
}
#[derive(Insertable, Debug)]
#[diesel(table_name = schema::configs)]
pub struct NewConfig<'a> {
pub active: bool,
pub name: &'a str,
pub description: &'a str,
pub copyright: &'a str,
pub owner_name: &'a str,
pub owner_email: &'a str,
pub owner_website: Option<&'a str>,
}
#[derive(Identifiable, Queryable, Debug)] #[derive(Identifiable, Queryable, Debug)]
#[diesel(table_name = schema::tags)] #[diesel(table_name = schema::tags)]
pub struct Tag { pub struct Tag {
@ -65,3 +40,15 @@ pub struct NewPost<'a> {
pub edited_at: Option<NaiveDate>, pub edited_at: Option<NaiveDate>,
pub active: bool, pub active: bool,
} }
#[derive(AsChangeset, Debug)]
#[diesel(table_name = schema::posts)]
pub struct UpdatePost<'a> {
pub name: Option<&'a str>,
pub slug: Option<&'a str>,
pub description: Option<&'a str>,
pub content: Option<&'a str>,
pub published_at: Option<NaiveDate>,
pub edited_at: Option<Option<NaiveDate>>,
pub active: Option<bool>,
}

View file

@ -1,16 +1,3 @@
diesel::table! {
configs {
id -> Integer,
active -> Bool,
name -> Text,
description -> Text,
copyright -> Text,
owner_name -> Text,
owner_email -> Text,
owner_website -> Nullable<Text>,
}
}
diesel::table! { diesel::table! {
tags { tags {
id -> Integer, id -> Integer,

0
backend/static/.keep Normal file
View file

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}{% endblock %} | dergrimm's blog</title>
<link rel="preconnect" href="https://fonts.bunny.net" />
<link href="https://fonts.bunny.net/css?family=jetbrains-mono:400" rel="stylesheet" />
<link rel="stylesheet" href="/static/css/styles.css" />
{% block head %}{% endblock %}
</head>
<body>
<div id="wrapper">
<nav id="navbar" class="double-border">
<div id="navbar-brand"><a href="/">dergrimm's blog</a></div>
<div id="navbar-links">
<ul>
<li><a href="/posts">posts</a></li>
<li><a href="/about">about</a></li>
</ul>
</div>
</nav>
<div id="content" class="double-border">
{% block content %}{% endblock %}
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block title %}{{ post.name }}{% endblock %}
{% block head %}{% endblock %}
{% block content %}
<div class="blog">
{{ post.content|cmark|safe }}
</div>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}{{ status_code }}{% endblock %}
{% block head %}{% endblock %}
{% block content %}
<h1>{{ status_code }}!</h1>
{% match message %}
{% when Some with (x) %}
<p>{{ x }}</p>
{% when None %}
{% endmatch %}
{% endblock %}

View file

@ -1,9 +0,0 @@
name: dergrimm's blog
description: |
Just some personal expericences about technology and programming.
Follow my RSS feed for more.
copyright: (C) 2022 - Dominic Grimm
owner:
name: Dominic Grimm
email: dominic@dergrimm.net
website: https://git.dergrimm.net/dergrimm

View file

@ -1,4 +1,5 @@
--- ---
id: 1
name: Hello world! name: Hello world!
slug: hello_world slug: hello_world
description: Hello world to the internet. Set up my first blog! description: Hello world to the internet. Set up my first blog!
@ -9,6 +10,6 @@ active: true
# Hello world! # Hello world!
I just set up my first blog an am really proud of it! I just set up my first blog and am really proud of it!
So anyway, here I am. So anyway, here I am.