diff --git a/.gitignore b/.gitignore index 2955c66..4a5a985 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target db.sqlite +data diff --git a/Cargo.lock b/Cargo.lock index e0fee13..d06be21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys 0.59.0", +] + [[package]] name = "argon2" version = "0.5.3" @@ -290,6 +339,7 @@ dependencies = [ "axum", "axum-extra", "chrono", + "clap", "r2d2", "r2d2_sqlite", "rand", @@ -354,6 +404,52 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clap" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8eb5e908ef3a6efbe1ed62520fb7287959888c88485abe072543190ecc66783" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b01801b5fc6a0a232407abc821660c9c6d25a1cafc0d4f85f29fb8d9afc121" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "cookie" version = "0.18.1" @@ -595,6 +691,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "http" version = "1.2.0" @@ -730,6 +832,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.14" @@ -836,7 +944,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1333,9 +1441,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -1457,7 +1571,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1671,6 +1785,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.11.0" @@ -1791,7 +1911,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1818,6 +1938,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 5a92953..d2eccc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ argon2 = "0.5.3" axum = { version = "0.8.0", features = ["macros"] } axum-extra = { version = "0.10.0", features = ["cookie"] } chrono = { version = "0.4.39", features = ["serde"] } +clap = { version = "4.5.26", features = ["derive", "env"] } r2d2 = "0.8.10" r2d2_sqlite = "0.25.0" rand = "0.8.5" @@ -24,3 +25,6 @@ tokio = { version = "1.42.0", features = ["full"] } tower-http = { version = "0.6.2", features = ["compression-br", "compression-gzip", "set-header", "fs"] } tracing = "0.1.41" tracing-subscriber = "0.3.19" + +[profile.release] +strip = true diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..f3b38bf --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,57 @@ +use std::path::PathBuf; + +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser)] +pub struct Cli { + #[arg( + long = "templates", + default_value = "./templates", + value_name = "TEMPLATES_DIR", + env = "TEMPLATES_DIR" + )] + pub templates_dir: PathBuf, + #[arg( + long = "static", + default_value = "./static", + value_name = "STATIC_DIR", + env = "STATIC_DIR" + )] + pub static_dir: PathBuf, + #[arg( + long = "data", + default_value = "./data", + value_name = "DATA_DIR", + env = "DATA_DIR" + )] + pub data_dir: PathBuf, + + #[command(subcommand)] + pub cmd: Subcommands, +} + +#[derive(Subcommand)] +pub enum Subcommands { + Serve { domain: String, port: u16 }, + User(UserCmd), +} + +#[derive(Args)] +pub struct UserCmd { + #[command(subcommand)] + cmd: UserSubCmd, +} + +#[derive(Subcommand)] +pub enum UserSubCmd { + List, + Remove { + username: String, + }, + Add { + username: String, + password: String, + #[arg(long, default_value_t = false)] + admin: bool, + }, +} diff --git a/src/db/session.rs b/src/db/session.rs index 8b63762..76ee09d 100644 --- a/src/db/session.rs +++ b/src/db/session.rs @@ -1,4 +1,3 @@ -use argon2::password_hash::rand_core::{OsRng, RngCore}; use rand::Rng; use rusqlite::Row; diff --git a/src/main.rs b/src/main.rs index f44edef..a989bb0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,12 @@ +mod cli; mod db; mod server; -use std::sync::Arc; +use std::{fs, path::Path, sync::Arc}; +use clap::Parser; +use cli::UserCmd; +use db::DbError; use r2d2_sqlite::SqliteConnectionManager; use tera::Tera; use tower_http::compression::CompressionLayer; @@ -14,6 +18,7 @@ const MIGRATIONS: [&str; 5] = [ include_str!("migrations/003_events.sql"), include_str!("migrations/004_auth.sql"), ]; +const DB_FILENAME: &str = "db.sqlite3"; #[derive(Clone)] pub struct Context { @@ -21,31 +26,47 @@ pub struct Context { tera: Arc, } +fn run_user_cli(data_dir: impl AsRef, cmd: UserCmd) -> Result<(), DbError> { + Ok(()) +} + #[tokio::main] async fn main() { - tracing_subscriber::fmt::init(); + let args = cli::Cli::parse(); - let manager = SqliteConnectionManager::file("db.sqlite"); - let pool = r2d2::Pool::new(manager).unwrap(); - db::run_migrations(&pool, &MIGRATIONS).unwrap(); + match args.cmd { + cli::Subcommands::Serve { domain, port } => { + tracing_subscriber::fmt::init(); - let template_dir = std::env::var("TEMPLATE_DIR").unwrap_or(String::from("./templates")); - let tera = Tera::new(&format!("{template_dir}/**/*")).unwrap(); + if !fs::exists(&args.data_dir).unwrap() { + fs::create_dir_all(&args.data_dir).unwrap(); + } - let static_dir = std::env::var("STATIC_DIR").unwrap_or(String::from("./static")); + let manager = SqliteConnectionManager::file(args.data_dir.join(DB_FILENAME)); + let pool = r2d2::Pool::new(manager).unwrap(); + db::run_migrations(&pool, &MIGRATIONS).unwrap(); - let ctx = Context { - pool, - tera: Arc::new(tera), - }; - let app = server::app(ctx, &static_dir).layer(CompressionLayer::new().br(true).gzip(true)); + let tera = + Tera::new(&format!("{}/**/*", args.templates_dir.to_string_lossy())).unwrap(); - let address = "0.0.0.0:8000"; + let ctx = Context { + pool, + tera: Arc::new(tera), + }; + let app = server::app(ctx, &args.static_dir) + .layer(CompressionLayer::new().br(true).gzip(true)); - tracing::info!("Starting server on {address}"); + let address = format!("{}:{}", domain, port); - let listener = tokio::net::TcpListener::bind(address).await.unwrap(); - axum::serve(listener, app.into_make_service()) - .await - .unwrap(); + tracing::info!("Starting server on {address}"); + + let listener = tokio::net::TcpListener::bind(address).await.unwrap(); + axum::serve(listener, app.into_make_service()) + .await + .unwrap(); + } + cli::Subcommands::User(cmd) => { + run_user_cli(&args.data_dir, cmd).unwrap(); + } + } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 70af08f..91f4396 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,6 +4,8 @@ mod error; mod events; mod plants; +use std::path::Path; + use axum::{ extract::State, http::{header::VARY, HeaderMap, HeaderValue}, @@ -50,7 +52,7 @@ pub fn render_view( } } -pub fn app(ctx: crate::Context, static_dir: &str) -> axum::Router { +pub fn app(ctx: crate::Context, static_dir: impl AsRef) -> axum::Router { let router = Router::new() .nest("/plants", plants::app()) .nest("/comments", comments::app())