diff --git a/.env.example b/.env.example index 960939c..f4b2798 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ -DATABASE=postgresql://postgres:postgres@localhost/postgres +DATABASE_URL=postgresql://postgres:postgres@localhost/postgres PORT=3000 diff --git a/Cargo.lock b/Cargo.lock index e64f326..ff8a4e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.16" @@ -226,6 +235,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -269,10 +288,33 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets", ] +[[package]] +name = "chrono-tz" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "4.4.6" @@ -395,6 +437,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deunicode" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dbf1bf89c23e9cd1baf5e654f622872655f195b36588dc9dc38f7eda30758c" +dependencies = [ + "deunicode 1.4.1", +] + +[[package]] +name = "deunicode" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" + [[package]] name = "digest" version = "0.10.7" @@ -455,6 +512,16 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -592,6 +659,46 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "handlebars" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" +dependencies = [ + "log", + "pest", + "pest_derive", + "rust-embed", + "serde", + "serde_json", + "thiserror", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.14.1" @@ -699,6 +806,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "0.14.27" @@ -755,6 +871,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "imgboard" version = "0.1.0" @@ -763,14 +896,24 @@ dependencies = [ "chrono", "clap", "dotenvy", + "eyre", + "handlebars", + "rust-embed", "serde", "sqlx", + "tera", "tokio", "tower-http", "tracing", "tracing-subscriber", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.0.2" @@ -1035,6 +1178,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.14" @@ -1056,6 +1208,89 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pest_meta" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -1187,6 +1422,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "ring" version = "0.16.20" @@ -1224,6 +1488,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-embed" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.38", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1285,6 +1584,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1395,6 +1703,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1404,6 +1718,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode 0.4.5", +] + [[package]] name = "smallvec" version = "1.11.1" @@ -1732,6 +2055,28 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "thiserror" version = "1.0.49" @@ -1933,6 +2278,62 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2007,6 +2408,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2117,6 +2528,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index cc4dca0..a1b363e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,15 @@ edition = "2021" [dependencies] axum = { version = "0.6.20", features = ["tracing"] } -chrono = "0.4.31" +chrono = { version = "0.4.31", features = ["serde"] } clap = { version = "4.4.6", features = ["derive", "env"] } dotenvy = "0.15.7" +eyre = "0.6.8" +handlebars = { version = "4.4.0", features = ["rust-embed", "dir_source"] } +rust-embed = "8.0.0" serde = { version = "1.0.189", features = ["derive"] } sqlx = { version = "0.7.2", features = ["runtime-tokio", "tls-rustls", "postgres", "macros", "migrate", "chrono"] } +tera = "1.19.1" tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } tower-http = { version = "0.4.4", features = ["trace"] } tracing = "0.1.39" diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/20231018134946_init.sql b/migrations/20231018134946_init.sql index 242b28d..2aeb942 100644 --- a/migrations/20231018134946_init.sql +++ b/migrations/20231018134946_init.sql @@ -1,4 +1,3 @@ --- Add migration script here CREATE TABLE users ( id SERIAL PRIMARY KEY, username TEXT NOT NULL UNIQUE, @@ -23,5 +22,5 @@ CREATE TABLE posts ( CREATE TABLE tags ( id SERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, - description TEXT NOT NULL, + description TEXT NOT NULL ); diff --git a/src/config.rs b/src/config.rs index 985e308..7b14d60 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,7 +3,9 @@ use clap::Parser; #[derive(Debug, Parser)] pub struct Config { #[arg(long, env)] - pub database: String, - #[arg(long, env)] + pub database_url: String, + #[arg(long, env, default_value_t = 3000)] pub port: u16, + #[arg(long, env, default_value_t = false)] + pub dev_mode: bool, } diff --git a/src/main.rs b/src/main.rs index 74471d7..35fc4be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,28 @@ pub mod config; -pub mod state; pub mod model; +pub mod state; +pub mod templates; -use std::net::{SocketAddr, IpAddr, Ipv4Addr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::sync::Arc; +use axum::extract::State; +use axum::http::{header, StatusCode}; +use axum::response::IntoResponse; use axum::{routing::get, Router}; use clap::Parser; +use eyre::{Result, WrapErr}; +use sqlx::migrate; use sqlx::postgres::PgPoolOptions; use tower_http::trace::TraceLayer; use tracing::{debug, error, info}; use crate::config::Config; use crate::state::AppState; +use crate::templates::handlebars; #[tokio::main] -async fn main() { +async fn main() -> Result<()> { dotenvy::dotenv().ok(); tracing_subscriber::fmt() @@ -26,20 +33,24 @@ async fn main() { let config = Config::parse(); - let pool = match PgPoolOptions::new() - .max_connections(500) - .connect(&config.database) - .await { - Ok(pool) => pool, - Err(err) => return error!("Initializing database: {}", err) - }; + let hbs = handlebars(config.dev_mode)?; - let state = Arc::new(AppState { pool }); + debug!("Initializing database"); + + let pool = PgPoolOptions::new() + .max_connections(500) + .connect(&config.database_url) + .await + .wrap_err("creating database pool")?; + + migrate!().run(&pool).await.wrap_err("running migrations")?; + + let state = Arc::new(AppState { pool, hbs }); debug!("Building router"); let app = Router::new() - .route("/", get(|| async { "Hello, World!" })) + .route("/", get(index)) .layer(TraceLayer::new_for_http()) .with_state(state); @@ -49,5 +60,17 @@ async fn main() { axum::Server::bind(&socket) .serve(app.into_make_service()) .await - .unwrap(); + .wrap_err("running server") +} + +async fn index(State(state): State>) -> impl IntoResponse { + let out = match state.hbs.render("root.hbs", &16) { + Ok(s) => s, + Err(err) => { + error!("Rendering index page: {}", err); + return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response(); + } + }; + + return ([(header::CONTENT_TYPE, "text/html")], out).into_response(); } diff --git a/src/model/post.rs b/src/model/post.rs index 6effd95..faa0a0e 100644 --- a/src/model/post.rs +++ b/src/model/post.rs @@ -1,5 +1,7 @@ -use sqlx::types::chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc}; +use serde::Serialize; +#[derive(Debug, Serialize)] pub struct Post { pub id: i32, pub tags: String, diff --git a/src/model/tag.rs b/src/model/tag.rs index 94d5bf1..1c9719b 100644 --- a/src/model/tag.rs +++ b/src/model/tag.rs @@ -1,3 +1,6 @@ +use serde::Serialize; + +#[derive(Debug, Serialize)] pub struct Tag { pub id: i32, pub name: String, diff --git a/src/model/user.rs b/src/model/user.rs index 79a6013..c020ced 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -1,5 +1,7 @@ -use sqlx::types::chrono::{DateTime, Utc}; +use chrono::{DateTime, Utc}; +use serde::Serialize; +#[derive(Debug, Serialize)] pub struct User { pub id: i32, pub username: String, diff --git a/src/state.rs b/src/state.rs index 55b5f7c..455eeff 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,4 +2,5 @@ use sqlx::{postgres::Postgres, Pool}; pub struct AppState { pub pool: Pool, + pub hbs: handlebars::Handlebars<'static>, } diff --git a/src/templates.rs b/src/templates.rs new file mode 100644 index 0000000..95f13a1 --- /dev/null +++ b/src/templates.rs @@ -0,0 +1,25 @@ +use eyre::{Context, Result}; +use handlebars::Handlebars; +use rust_embed::RustEmbed; +use tracing::info; + +#[derive(RustEmbed)] +#[folder = "templates/"] +struct Templates; + +pub fn handlebars(dev_mode: bool) -> Result> { + let mut hbs = Handlebars::new(); + + if dev_mode { + info!("Reading templates from file system, as dev mode is enabled"); + + hbs.set_dev_mode(true); + hbs.register_templates_directory("", "templates/") + .wrap_err("registering templates from directory")?; + } else { + hbs.register_embed_templates::() + .wrap_err("registering templates from embed")?; + } + + Ok(hbs) +} diff --git a/templates/root.hbs b/templates/root.hbs new file mode 100644 index 0000000..f0f25da --- /dev/null +++ b/templates/root.hbs @@ -0,0 +1,11 @@ + + + + + + imgboard + + +

Hello world!!!

+ + \ No newline at end of file