From 10d48a91c3afbd8f7a3d7843c5ddf914f3e48684 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Sun, 12 Nov 2023 11:15:14 -0600 Subject: [PATCH] Cleanup and organize code into modules --- Cargo.lock | 236 +++++++++++++++++- Cargo.toml | 3 +- flake.nix | 5 +- .../2023-11-11-073646_create_users/up.sql | 2 +- src/app.rs | 7 + src/database.rs | 41 +++ src/feather_icons.rs | 8 + src/instrumentation.rs | 13 + src/main.rs | 178 +------------ src/partials.rs | 51 ++++ src/router.rs | 36 +++ src/schema.rs | 2 +- src/server.rs | 13 + src/state.rs | 15 ++ src/views.rs | 70 ++++++ 15 files changed, 509 insertions(+), 171 deletions(-) create mode 100644 src/app.rs create mode 100644 src/database.rs create mode 100644 src/feather_icons.rs create mode 100644 src/instrumentation.rs create mode 100644 src/partials.rs create mode 100644 src/router.rs create mode 100644 src/server.rs create mode 100644 src/state.rs create mode 100644 src/views.rs diff --git a/Cargo.lock b/Cargo.lock index 53df93c..b102f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,7 @@ dependencies = [ "bitflags 1.3.2", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -113,6 +114,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + [[package]] name = "bitflags" version = "1.3.2" @@ -125,6 +132,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytes" version = "1.5.0" @@ -146,6 +162,44 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "deadpool" version = "0.10.0" @@ -241,12 +295,34 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys", +] + [[package]] name = "fnv" version = "1.0.7" @@ -262,6 +338,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures-channel" version = "0.3.29" @@ -301,6 +386,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gimli" version = "0.28.0" @@ -313,6 +408,30 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "hermit-abi" version = "0.3.3" @@ -392,12 +511,52 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -447,6 +606,7 @@ dependencies = [ "diesel", "diesel_migrations", "maud", + "notify", "serde", "tokio", "tower-http", @@ -551,10 +711,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi", "windows-sys", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -614,7 +794,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets", ] @@ -711,6 +891,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -782,6 +971,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" @@ -850,6 +1048,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1173,6 +1382,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicase" version = "2.7.0" @@ -1206,6 +1421,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" @@ -1237,6 +1462,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 e883c04..f15e4c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,13 @@ edition = "2021" [dependencies] anyhow = "1.0.75" -axum = "0.6.20" +axum = { version = "0.6.20", features = ["headers"] } deadpool = "0.10.0" deadpool-diesel = { version = "0.5.0", features = ["rt_tokio_1", "sqlite", "tracing", "serde"] } diesel = { version = "2.1.3", features = ["sqlite"] } diesel_migrations = { version = "2.1.0", features = ["sqlite"] } maud = "0.25.0" +notify = "6.1.1" serde = { version = "1.0.192", features = ["derive"] } tokio = { version = "1.34.0", features = ["full", "macros", "rt-multi-thread"] } tower-http = { version = "0.4.4", features = ["fs"] } diff --git a/flake.nix b/flake.nix index 7b26a5c..fef5085 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,9 @@ devShell = with pkgs; mkShell { buildInputs = [ + # dedupe from package inputs? + sqlite + toolchain rustfmt @@ -50,8 +53,6 @@ rust-analyzer nodePackages_latest.vscode-langservers-extracted - # dedupe from package inputs? - sqlite diesel-cli hurl ]; diff --git a/migrations/2023-11-11-073646_create_users/up.sql b/migrations/2023-11-11-073646_create_users/up.sql index 88bd1c9..7db81c8 100644 --- a/migrations/2023-11-11-073646_create_users/up.sql +++ b/migrations/2023-11-11-073646_create_users/up.sql @@ -1,5 +1,5 @@ create table users ( id text primary key, name text, - hashed_password text + hashed_password binary ) diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..f3e0a1f --- /dev/null +++ b/src/app.rs @@ -0,0 +1,7 @@ +use crate::{instrumentation, router, server::listen}; + +pub async fn run() -> Result<(), anyhow::Error> { + instrumentation::setup_trace_logger(); + listen(router::new().await?).await; + Ok(()) +} diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..7ea11b9 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,41 @@ +use deadpool_diesel::sqlite::{Manager, Pool, Runtime}; +use diesel::sqlite::Sqlite; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use tracing::info; + +pub struct Database { + pub pool: Pool, +} + +const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + +impl Database { + // TODO: database seeding? + + fn run_migrations( + connection: &mut impl MigrationHarness, + ) -> Result<(), Box> { + // This will run the necessary migrations. + // + // See the documentation for `MigrationHarness` for + // all available methods. + connection.run_pending_migrations(MIGRATIONS)?; + + Ok(()) + } + + // TODO: make an actual error type + pub async fn new>(database_url: T) -> Result { + let manager = Manager::new(database_url.as_ref(), Runtime::Tokio1); + let pool = Pool::builder(manager).max_size(8).build().unwrap(); + + let conn = pool.get().await?; + let _ = conn + .interact(|c| Self::run_migrations(c)) + .await + .expect("Failed to run migrations"); + info!("Migrations completed!"); + + return Ok(Database { pool }); + } +} diff --git a/src/feather_icons.rs b/src/feather_icons.rs new file mode 100644 index 0000000..7d60ca8 --- /dev/null +++ b/src/feather_icons.rs @@ -0,0 +1,8 @@ +// TODO: perhaps just download all feather icons and include lazily or something? +// feather icons +pub const FEATHER_ICON_USER_PLUS: &str = r#""#; + +#[allow(dead_code)] +pub const FEATHER_ICON_LAYOUT: &str = r#""#; + +pub const FEATHER_ICON_LOGIN: &str = r#""#; diff --git a/src/instrumentation.rs b/src/instrumentation.rs new file mode 100644 index 0000000..25ed7d0 --- /dev/null +++ b/src/instrumentation.rs @@ -0,0 +1,13 @@ +use tracing::{instrument, trace}; +use tracing_subscriber::{filter::LevelFilter, EnvFilter}; + +#[instrument] +pub fn setup_trace_logger() { + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .parse_lossy("info,lyrs=trace"); + + tracing_subscriber::fmt().with_env_filter(filter).init(); + + trace!("Starting..."); +} diff --git a/src/main.rs b/src/main.rs index 1952ca7..02262c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,172 +1,20 @@ //! lyrs entrypoint -use axum::{http::StatusCode, response::Html, routing::get, Router}; -use deadpool_diesel::sqlite::{Manager, Pool, Runtime}; -use diesel::sqlite::Sqlite; -use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; -use maud::{html, PreEscaped}; -use std::{env, net::SocketAddr}; -use tower_http::services::ServeDir; -use tower_livereload::LiveReloadLayer; -use tracing::{info, instrument, span, trace, Level}; -use tracing_subscriber::{filter::LevelFilter, EnvFilter}; +// TODO: Break this module up +// TODO: Implement authn -#[instrument] -fn setup_trace_logger() { - let filter = EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .parse_lossy("info,lyrs=trace"); - - tracing_subscriber::fmt().with_env_filter(filter).init(); - - trace!("Starting..."); -} - -// struct State { -// pub pool: Pool, -// } - -fn run_migrations( - connection: &mut impl MigrationHarness, -) -> Result<(), Box> { - // This will run the necessary migrations. - // - // See the documentation for `MigrationHarness` for - // all available methods. - connection.run_pending_migrations(MIGRATIONS)?; - - Ok(()) -} - -#[instrument] -async fn router() -> Result { - let app_router = Router::new() - .route("/hello-world", get(greet_world)) - .route("/hello-world-text", get(greet_world_text)); - - let assets_dir = ServeDir::new("./assets"); - - let pool; - - let dbspan = span!(Level::WARN, "database_setup"); - { - let _s = dbspan.enter(); - - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let manager = Manager::new(database_url, Runtime::Tokio1); - pool = Pool::builder(manager).max_size(8).build().unwrap(); - info!("Database pool creation complete!"); - - let conn = pool.get().await?; - let mspan = span!(Level::INFO, "migrations"); - { - let _s = mspan.enter(); - let _ = conn - .interact(|c| run_migrations(c)) - .await - .expect("Failed to run migrations"); - info!("Migrations completed!"); - } - } - - // let state = Arc::new(State { pool }); - - Ok(Router::new() - .fallback(|| async { (StatusCode::NOT_FOUND, "404 page not found") }) - .nest("/app", app_router) - .nest_service("/assets", assets_dir) - .route("/", get(index)) - .layer(LiveReloadLayer::new())) - // .with_state(state)) -} - -async fn listen(r: Router) { - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - info!("Listening on '{}'", addr); - axum::Server::bind(&addr) - .serve(r.into_make_service()) - .await - .unwrap(); -} - -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); +mod app; +mod database; +mod feather_icons; +mod instrumentation; +mod partials; +mod router; +mod schema; +mod server; +mod state; +mod views; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - setup_trace_logger(); - listen(router().await?).await; - Ok(()) -} - -// feather icons -const FEATHER_ICON_LAYOUT: &str = r#""#; - -const FEATHER_ICON_LOGIN: &str = r#""#; - -#[instrument] -async fn index() -> Html { - Html( - html! { - head { - link rel="stylesheet" href="/assets/styles.css" {} - link rel="icon" href="/assets/favicon.svg" {} - } - header class="flex" { - nav class="flex" { - h1 { - a href="/" { "lyrs" } - } - // ul class="flex" - // { - // a href="/login" { "Login" } - // } - } - nav class="flex" { - ul class="flex" - { - a href="/dashboard" { - (PreEscaped(FEATHER_ICON_LAYOUT)) - "Dashboard" - } - a href="/login" { - (PreEscaped(FEATHER_ICON_LOGIN)) - "Login" - } - } - } - } - main class="prose" { - h1 { "Manage live lyrics and music displays" } - p { "Stop struggling to share the same messy set of PowerPoint files or Google Presentations. Make editing and controlling your live lyrics and music displays easy and simple." } - ul { - li { "Live collaboration with your team" } - li { "Fully compatible with any device" } - li { "Simple workflow" } - li { "Dark theme and light theme" } - li { "Generous free plan" } - li { "Lightweight and fast" } - } - section class="flex gap" { - a class="button bg-primary" href="/register/anonymous" { "Try now" } - a class="button" href="/login" { "Login" } - } - } - } - .into_string(), - ) -} - -#[instrument] -async fn greet_world() -> Html { - Html( - html! { - h1 { (greet_world_text().await) } - } - .into_string(), - ) -} - -#[instrument] -async fn greet_world_text() -> &'static str { - "Hello, World!" + app::run().await } diff --git a/src/partials.rs b/src/partials.rs new file mode 100644 index 0000000..ed8bfcd --- /dev/null +++ b/src/partials.rs @@ -0,0 +1,51 @@ +use maud::{html, Markup, PreEscaped}; + +use crate::feather_icons; + +pub fn header() -> Markup { + html! { + head { + link rel="stylesheet" href="/assets/styles.css" {} + link rel="icon" href="/assets/favicon.svg" {} + } + body hx-ext="preload" { + header class="flex" { + nav class="flex" { + h1 { + a href="/" { "lyrs" } + } + // ul class="flex" + // { + // a href="/login" { "Login" } + // } + } + nav class="flex" { + ul class="flex" + { + // a href="/dashboard" { + // (PreEscaped(FEATHER_ICON_LAYOUT)) + // "Dashboard" + // } + a href="/register" preload="" { + (PreEscaped(feather_icons::FEATHER_ICON_USER_PLUS)) + "Register" + } + a href="/login" preload="" { + (PreEscaped(feather_icons::FEATHER_ICON_LOGIN)) + "Login" + } + } + } + } + } + } +} + +pub fn footer() -> Markup { + html! { + footer { + script src="https://unpkg.com/htmx.org@1.9.8" crossorigin="anonymous" integrity="sha384-rgjA7mptc2ETQqXoYC3/zJvkU7K/aP44Y+z7xQuJiVnB/422P/Ak+F/AqFR7E4Wr" {} + script src="https://unpkg.com/htmx.org@1.9.8/dist/ext/preload.js" crossorigin="anonymous" {} + } + } +} diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..cee8719 --- /dev/null +++ b/src/router.rs @@ -0,0 +1,36 @@ +use std::{path::Path, sync::Arc}; + +use axum::{http::StatusCode, routing::get, Router}; +use notify::Watcher; +use tower_http::services::ServeDir; +use tower_livereload::LiveReloadLayer; +use tracing::instrument; + +use crate::{state::State, views}; + +#[instrument] +pub async fn new() -> Result { + let app_router = Router::new() + .route("/hello-world", get(views::greet_world)) + .route("/hello-world-text", get(views::greet_world_text)); + + let assets_dir = ServeDir::new("./assets"); + let state = Arc::new(State::new().await?); + + let live_reload_layer = LiveReloadLayer::new(); + let reloader = live_reload_layer.reloader(); + + let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?; + watcher.watch(Path::new("assets"), notify::RecursiveMode::Recursive)?; + + let router = Router::new() + .fallback(|| async { (StatusCode::NOT_FOUND, "404 page not found") }) + .nest("/app", app_router) + .nest_service("/assets", assets_dir) + .route("/", get(views::index)) + .route("/register", get(views::register)) + .with_state(state) + .layer(live_reload_layer); + + Ok(router) +} diff --git a/src/schema.rs b/src/schema.rs index b4dc1a5..439e88c 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -4,6 +4,6 @@ diesel::table! { users (id) { id -> Nullable, name -> Nullable, - hashed_password -> Nullable, + hashed_password -> Nullable, } } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..cd0cd05 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,13 @@ +use std::net::SocketAddr; + +use axum::Router; +use tracing::info; + +pub async fn listen(r: Router) { + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + info!("Listening on '{}'", addr); + axum::Server::bind(&addr) + .serve(r.into_make_service()) + .await + .unwrap(); +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..530143e --- /dev/null +++ b/src/state.rs @@ -0,0 +1,15 @@ +use std::env; + +use crate::database; + +pub struct State { + pub database: database::Database, +} + +impl State { + pub async fn new() -> Result { + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + let database = database::Database::new(database_url).await?; + Ok(State { database }) + } +} diff --git a/src/views.rs b/src/views.rs new file mode 100644 index 0000000..2319d47 --- /dev/null +++ b/src/views.rs @@ -0,0 +1,70 @@ +use axum::response::Html; +use maud::html; +use tracing::instrument; + +use crate::partials::{footer, header}; + +#[instrument] +pub async fn index() -> Html { + Html( + html! { + (header()) + main class="prose" { + h1 { "Manage live lyrics and music displays" } + p { "Stop struggling to share the same messy set of PowerPoint files or Google Presentations. Make editing and controlling your live lyrics and music displays easy and simple." } + ul { + li { "Live collaboration with your team" } + li { "Fully compatible with any device" } + li { "Simple workflow" } + li { "Dark theme and light theme" } + li { "Generous free plan" } + li { "Lightweight and fast" } + } + section class="flex gap" { + button hx-get="/register" hx-target="body>main" hx-push-url="true" class="button bg-primary" { "Try now" } + a class="button" href="/login" { "Login" } + } + } + (footer()) + } + .into_string(), + ) +} + +#[instrument] +pub async fn register() -> Html { + Html( + html! { + h1 { "Register an account" } + p { "Stop struggling to share the same messy set of PowerPoint files or Google Presentations. Make editing and controlling your live lyrics and music displays easy and simple." } + ul { + li { "Live collaboration with your team" } + li { "Fully compatible with any device" } + li { "Simple workflow" } + li { "Dark theme and light theme" } + li { "Generous free plan" } + li { "Lightweight and fast" } + } + section class="flex gap" { + a class="button bg-primary" href="/register/anonymous" { "Try now" } + a class="button" href="/login" { "Login" } + } + } + .into_string(), + ) +} + +#[instrument] +pub async fn greet_world() -> Html { + Html( + html! { + h1 { (greet_world_text().await) } + } + .into_string(), + ) +} + +#[instrument] +pub async fn greet_world_text() -> &'static str { + "Hello, World!" +}