Cleanup and organize code into modules

This commit is contained in:
Daniel Flanagan 2023-11-12 11:15:14 -06:00
parent 827a519ca5
commit 10d48a91c3
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
15 changed files with 509 additions and 171 deletions

236
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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
];

View file

@ -1,5 +1,5 @@
create table users (
id text primary key,
name text,
hashed_password text
hashed_password binary
)

7
src/app.rs Normal file
View file

@ -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(())
}

41
src/database.rs Normal file
View file

@ -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<Sqlite>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// 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<T: AsRef<str>>(database_url: T) -> Result<Self, anyhow::Error> {
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 });
}
}

8
src/feather_icons.rs Normal file
View file

@ -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#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-plus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>"#;
#[allow(dead_code)]
pub const FEATHER_ICON_LAYOUT: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layout"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>"#;
pub const FEATHER_ICON_LOGIN: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-in"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>"#;

13
src/instrumentation.rs Normal file
View file

@ -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...");
}

View file

@ -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<Sqlite>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
// 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<Router, anyhow::Error> {
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#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-layout"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>"#;
const FEATHER_ICON_LOGIN: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-in"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>"#;
#[instrument]
async fn index() -> Html<String> {
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<String> {
Html(
html! {
h1 { (greet_world_text().await) }
}
.into_string(),
)
}
#[instrument]
async fn greet_world_text() -> &'static str {
"Hello, World!"
app::run().await
}

51
src/partials.rs Normal file
View file

@ -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" {}
}
}
}

36
src/router.rs Normal file
View file

@ -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<Router, anyhow::Error> {
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)
}

View file

@ -4,6 +4,6 @@ diesel::table! {
users (id) {
id -> Nullable<Text>,
name -> Nullable<Text>,
hashed_password -> Nullable<Text>,
hashed_password -> Nullable<Binary>,
}
}

13
src/server.rs Normal file
View file

@ -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();
}

15
src/state.rs Normal file
View file

@ -0,0 +1,15 @@
use std::env;
use crate::database;
pub struct State {
pub database: database::Database,
}
impl State {
pub async fn new() -> Result<Self, anyhow::Error> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let database = database::Database::new(database_url).await?;
Ok(State { database })
}
}

70
src/views.rs Normal file
View file

@ -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<String> {
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<String> {
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<String> {
Html(
html! {
h1 { (greet_world_text().await) }
}
.into_string(),
)
}
#[instrument]
pub async fn greet_world_text() -> &'static str {
"Hello, World!"
}