diff --git a/.editorconfig b/.editorconfig index bfb95f2..b072741 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..9f5a075 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +backend/templates diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8a84367 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "**/templates/**/*.html": "jinja-html" + } +} diff --git a/backend/.dockerignore b/backend/.dockerignore index 1a0c4a1..97c2bc9 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -5,3 +5,4 @@ Dockerfile .dockerignore vendor/ example/ +static/ diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8b2ffdf..0d8aa51 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -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" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4b65119..871a94c 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -6,21 +6,25 @@ authors = ["Dominic Grimm "] [[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" diff --git a/backend/Dockerfile b/backend/Dockerfile index 55acf34..6cff241 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -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" ] diff --git a/backend/build.rs b/backend/build.rs new file mode 100644 index 0000000..acc2dcb --- /dev/null +++ b/backend/build.rs @@ -0,0 +1,5 @@ +use static_files::resource_dir; + +fn main() -> std::io::Result<()> { + resource_dir("./static").build() +} diff --git a/backend/migrations/2023-02-06-134456_init/down.sql b/backend/migrations/2023-02-06-134456_init/down.sql index 154e0c1..271ceaa 100644 --- a/backend/migrations/2023-02-06-134456_init/down.sql +++ b/backend/migrations/2023-02-06-134456_init/down.sql @@ -1,7 +1,3 @@ DROP TABLE posts; -DROP TABLE tags; - -DROP INDEX configs_active; - -DROP TABLE configs; +DROP TABLE tags; \ No newline at end of file diff --git a/backend/migrations/2023-02-06-134456_init/up.sql b/backend/migrations/2023-02-06-134456_init/up.sql index 9ed9cb1..2fa4c6f 100644 --- a/backend/migrations/2023-02-06-134456_init/up.sql +++ b/backend/migrations/2023-02-06-134456_init/up.sql @@ -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 @@ -46,4 +12,4 @@ CREATE TABLE posts( published_at DATE NOT NULL, edited_at DATE, active BOOLEAN NOT NULL -); +); \ No newline at end of file diff --git a/backend/run.sh b/backend/run.sh index dbb3d32..9e47f43 100644 --- a/backend/run.sh +++ b/backend/run.sh @@ -4,4 +4,4 @@ DATABASE_URL="$BACKEND_DB_URL" diesel setup \ --migration-dir ./migrations \ --locked-schema && - ./bin/backend run + RUST_LOG=info ./bin/backend diff --git a/backend/scss/styles.scss b/backend/scss/styles.scss new file mode 100644 index 0000000..e09b872 --- /dev/null +++ b/backend/scss/styles.scss @@ -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; +} diff --git a/backend/src/bin/backend.rs b/backend/src/bin/backend.rs index d623664..539d84b 100644 --- a/backend/src/bin/backend.rs +++ b/backend/src/bin/backend.rs @@ -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(s: T) -> ::askama::Result { + 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, +} + +#[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, path: web::Path) -> 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::(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 } diff --git a/backend/src/bin/blogctl.rs b/backend/src/bin/blogctl.rs index 12ef98e..5a7f298 100644 --- a/backend/src/bin/blogctl.rs +++ b/backend/src/bin/blogctl.rs @@ -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, -} - -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Serialize, Debug)] struct PostFrontmatter { + id: Option, 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::(&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::>>() - })??; - 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::(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::(conn)? + }; + + fs::write( + post.path, + format!( + "---\n{}---\n{}", + serde_yaml::to_string(&PostFrontmatter { + id: Some(id), + ..post.frontmatter + })?, + post.content + ), + )?; + } } Ok(()) diff --git a/backend/src/db/models.rs b/backend/src/db/models.rs index 9953f5e..a004047 100644 --- a/backend/src/db/models.rs +++ b/backend/src/db/models.rs @@ -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, -} - -#[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, 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, + pub edited_at: Option>, + pub active: Option, +} diff --git a/backend/src/db/schema.rs b/backend/src/db/schema.rs index 300036c..f0293db 100644 --- a/backend/src/db/schema.rs +++ b/backend/src/db/schema.rs @@ -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, - } -} - diesel::table! { tags { id -> Integer, diff --git a/backend/static/.keep b/backend/static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/backend/templates/base.html b/backend/templates/base.html new file mode 100644 index 0000000..c238438 --- /dev/null +++ b/backend/templates/base.html @@ -0,0 +1,33 @@ + + + + + + + {% block title %}{% endblock %} | dergrimm's blog + + + + + + + {% block head %}{% endblock %} + + +
+ + +
+ {% block content %}{% endblock %} +
+
+ + diff --git a/backend/templates/posts/{slug}.html b/backend/templates/posts/{slug}.html new file mode 100644 index 0000000..e43530a --- /dev/null +++ b/backend/templates/posts/{slug}.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% block title %}{{ post.name }}{% endblock %} + +{% block head %}{% endblock %} + +{% block content %} +
+ {{ post.content|cmark|safe }} +
+{% endblock %} diff --git a/backend/templates/status_code.html b/backend/templates/status_code.html new file mode 100644 index 0000000..d208f86 --- /dev/null +++ b/backend/templates/status_code.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}{{ status_code }}{% endblock %} + +{% block head %}{% endblock %} + +{% block content %} +

{{ status_code }}!

+ {% match message %} + {% when Some with (x) %} +

{{ x }}

+ {% when None %} + {% endmatch %} +{% endblock %} diff --git a/blog/blog.yml b/blog/blog.yml deleted file mode 100644 index 09dbbcc..0000000 --- a/blog/blog.yml +++ /dev/null @@ -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 diff --git a/blog/posts/hello_world/post.md b/blog/hello_world/post.md similarity index 76% rename from blog/posts/hello_world/post.md rename to blog/hello_world/post.md index f3d7782..3fc95f6 100644 --- a/blog/posts/hello_world/post.md +++ b/blog/hello_world/post.md @@ -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.