Update
This commit is contained in:
parent
94fb270008
commit
584c07ff23
22 changed files with 539 additions and 187 deletions
|
@ -31,3 +31,11 @@ insert_final_newline = true
|
|||
indent_style = space
|
||||
indent_size = 4
|
||||
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
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
|||
backend/templates
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"**/templates/**/*.html": "jinja-html"
|
||||
}
|
||||
}
|
|
@ -5,3 +5,4 @@ Dockerfile
|
|||
.dockerignore
|
||||
vendor/
|
||||
example/
|
||||
static/
|
||||
|
|
180
backend/Cargo.lock
generated
180
backend/Cargo.lock
generated
|
@ -183,6 +183,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "addr2line"
|
||||
version = "0.19.0"
|
||||
|
@ -251,6 +263,65 @@ dependencies = [
|
|||
"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]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -262,7 +333,10 @@ name = "backend"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"actix-web-static-files",
|
||||
"anyhow",
|
||||
"askama",
|
||||
"askama_actix",
|
||||
"chrono",
|
||||
"clap",
|
||||
"diesel",
|
||||
|
@ -271,9 +345,11 @@ dependencies = [
|
|||
"fronma",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"pulldown-cmark",
|
||||
"scan_dir",
|
||||
"serde",
|
||||
"serde_yaml 0.9.17",
|
||||
"static-files",
|
||||
"tikv-jemallocator",
|
||||
]
|
||||
|
||||
|
@ -376,6 +452,16 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
|
@ -710,6 +796,7 @@ dependencies = [
|
|||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -739,6 +826,12 @@ version = "0.27.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
|
@ -808,6 +901,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -992,6 +1091,22 @@ version = "0.3.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.6.2"
|
||||
|
@ -1013,6 +1128,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -1092,6 +1217,21 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
|
@ -1164,6 +1304,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
|
@ -1430,6 +1581,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -1561,6 +1723,15 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
|
@ -1588,6 +1759,15 @@ version = "1.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.10"
|
||||
|
|
|
@ -6,21 +6,25 @@ authors = ["Dominic Grimm <dominic@dergrimm.net>"]
|
|||
|
||||
[[bin]]
|
||||
name = "backend"
|
||||
build = "build.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "blogctl"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
strip = true
|
||||
panic = "abort"
|
||||
# [profile.release]
|
||||
# codegen-units = 1
|
||||
# lto = "fat"
|
||||
# strip = true
|
||||
# panic = "abort"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.3.0"
|
||||
actix-web-static-files = "4.0.0"
|
||||
anyhow = { version = "1.0.69", features = ["backtrace"] }
|
||||
askama = "0.11.1"
|
||||
askama_actix = "0.13.0"
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
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"] }
|
||||
|
@ -29,9 +33,14 @@ envconfig = "0.10.0"
|
|||
fronma = "0.1.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.17"
|
||||
pulldown-cmark = { version = "0.9.2", default-features = false, features = ["simd"] }
|
||||
scan_dir = "0.3.3"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_yaml = "0.9.17"
|
||||
static-files = "0.2.3"
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = "0.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
static-files = "0.2.3"
|
||||
|
|
|
@ -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 chef as diesel
|
||||
|
@ -12,10 +24,13 @@ RUN cargo chef prepare --recipe-path recipe.json
|
|||
FROM chef as builder
|
||||
WORKDIR /usr/src/backend
|
||||
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
|
||||
COPY ./build.rs .
|
||||
COPY --from=static /usr/src/static ./static
|
||||
COPY ./templates ./templates
|
||||
COPY ./src ./src
|
||||
RUN cargo build --release
|
||||
RUN cargo build
|
||||
|
||||
FROM docker.io/debian:bullseye-slim as runner
|
||||
RUN apt update
|
||||
|
@ -31,6 +46,6 @@ WORKDIR /usr/src/backend
|
|||
COPY ./run.sh .
|
||||
RUN chmod +x ./run.sh
|
||||
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
|
||||
ENTRYPOINT [ "./run.sh" ]
|
||||
|
|
5
backend/build.rs
Normal file
5
backend/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use static_files::resource_dir;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
resource_dir("./static").build()
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
DROP TABLE posts;
|
||||
|
||||
DROP TABLE tags;
|
||||
|
||||
DROP INDEX configs_active;
|
||||
|
||||
DROP TABLE configs;
|
||||
|
|
|
@ -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(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT UNIQUE NOT NULL
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
DATABASE_URL="$BACKEND_DB_URL" diesel setup \
|
||||
--migration-dir ./migrations \
|
||||
--locked-schema &&
|
||||
./bin/backend run
|
||||
RUST_LOG=info ./bin/backend
|
||||
|
|
65
backend/scss/styles.scss
Normal file
65
backend/scss/styles.scss
Normal 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;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
use diesel::QueryDsl;
|
||||
#[cfg(not(target_env = "msvc"))]
|
||||
use tikv_jemallocator::Jemalloc;
|
||||
|
||||
|
@ -5,42 +6,105 @@ use tikv_jemallocator::Jemalloc;
|
|||
#[global_allocator]
|
||||
static GLOBAL: Jemalloc = Jemalloc;
|
||||
|
||||
use actix_web::{
|
||||
http::header,
|
||||
middleware,
|
||||
web::{self, Data},
|
||||
App, Error, HttpResponse, HttpServer,
|
||||
};
|
||||
use clap::{Parser, Subcommand};
|
||||
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::*;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
commands: Commands,
|
||||
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(Debug, Subcommand)]
|
||||
enum Commands {
|
||||
#[clap(about = "Starts webserver")]
|
||||
Run,
|
||||
#[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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
match Cli::parse().commands {
|
||||
Commands::Run => {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
env_logger::init();
|
||||
env_logger::init();
|
||||
log::info!("Listening to requests at http://0.0.0.0:80");
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
});
|
||||
server.bind("0.0.0.0:80").unwrap().run().await
|
||||
}
|
||||
}
|
||||
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))
|
||||
})
|
||||
.bind("0.0.0.0:80")
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@ use chrono::prelude::*;
|
|||
use clap::{Parser, Subcommand};
|
||||
use diesel::prelude::*;
|
||||
use scan_dir::ScanDir;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use backend::*;
|
||||
|
||||
|
@ -28,23 +29,9 @@ enum Commands {
|
|||
Import,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, 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)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct PostFrontmatter {
|
||||
id: Option<i32>,
|
||||
name: String,
|
||||
slug: String,
|
||||
description: String,
|
||||
|
@ -55,6 +42,7 @@ struct PostFrontmatter {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct Post {
|
||||
path: PathBuf,
|
||||
frontmatter: PostFrontmatter,
|
||||
content: String,
|
||||
}
|
||||
|
@ -64,61 +52,86 @@ fn main() -> Result<()> {
|
|||
Commands::Import => {
|
||||
let conn = &mut db::establish_connection()?;
|
||||
|
||||
let blog: Blog = serde_yaml::from_str(&fs::read_to_string("/blog/blog.yml")?)?;
|
||||
|
||||
diesel::delete(db::schema::configs::table)
|
||||
.filter(db::schema::configs::active.eq(true))
|
||||
.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"))?;
|
||||
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),
|
||||
};
|
||||
|
||||
Ok(Post {
|
||||
frontmatter: PostFrontmatter {
|
||||
name: frontmatter.headers.name.trim().to_string(),
|
||||
slug: frontmatter.headers.slug.trim().to_string(),
|
||||
description: frontmatter.headers.description.trim().to_string(),
|
||||
..frontmatter.headers
|
||||
},
|
||||
content: frontmatter.body.trim().to_string(),
|
||||
path,
|
||||
frontmatter: frontmatter.headers,
|
||||
content: frontmatter.body.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})??;
|
||||
dbg!(&posts);
|
||||
})?? {
|
||||
let content = post.content.trim();
|
||||
|
||||
for post in posts {
|
||||
diesel::delete(db::schema::posts::table)
|
||||
.filter(db::schema::posts::slug.eq(&post.frontmatter.slug))
|
||||
.execute(conn)?;
|
||||
diesel::insert_into(db::schema::posts::table)
|
||||
.values(db::models::NewPost {
|
||||
name: &post.frontmatter.name,
|
||||
slug: &post.frontmatter.slug,
|
||||
description: &post.frontmatter.description,
|
||||
content: &post.content,
|
||||
published_at: post.frontmatter.published_at,
|
||||
edited_at: post.frontmatter.edited_at,
|
||||
active: post.frontmatter.active,
|
||||
})
|
||||
.execute(conn)?;
|
||||
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),
|
||||
})
|
||||
.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()?
|
||||
{
|
||||
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(())
|
||||
|
|
|
@ -3,31 +3,6 @@ use diesel::prelude::*;
|
|||
|
||||
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)]
|
||||
#[diesel(table_name = schema::tags)]
|
||||
pub struct Tag {
|
||||
|
@ -65,3 +40,15 @@ pub struct NewPost<'a> {
|
|||
pub edited_at: Option<NaiveDate>,
|
||||
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>,
|
||||
}
|
||||
|
|
|
@ -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! {
|
||||
tags {
|
||||
id -> Integer,
|
||||
|
|
0
backend/static/.keep
Normal file
0
backend/static/.keep
Normal file
33
backend/templates/base.html
Normal file
33
backend/templates/base.html
Normal 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>
|
11
backend/templates/posts/{slug}.html
Normal file
11
backend/templates/posts/{slug}.html
Normal 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 %}
|
14
backend/templates/status_code.html
Normal file
14
backend/templates/status_code.html
Normal 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 %}
|
|
@ -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
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
id: 1
|
||||
name: Hello world!
|
||||
slug: hello_world
|
||||
description: Hello world to the internet. Set up my first blog!
|
||||
|
@ -9,6 +10,6 @@ active: true
|
|||
|
||||
# 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.
|
Loading…
Reference in a new issue