Ready for a database
This commit is contained in:
parent
bfc5a6f90d
commit
0782e82d6e
214
Cargo.lock
generated
214
Cargo.lock
generated
|
@ -358,49 +358,6 @@ dependencies = [
|
||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deadpool"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
|
|
||||||
dependencies = [
|
|
||||||
"async-trait",
|
|
||||||
"deadpool-runtime",
|
|
||||||
"num_cpus",
|
|
||||||
"serde",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deadpool-diesel"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa8404d25ddc6cb0676d4a863bbd007613ee3fffb54db23e0e6341e1fe61c3e"
|
|
||||||
dependencies = [
|
|
||||||
"deadpool",
|
|
||||||
"deadpool-sync",
|
|
||||||
"diesel",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deadpool-runtime"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49"
|
|
||||||
dependencies = [
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "deadpool-sync"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f8db70494c13cae4ce67b4b4dafdaf828cf0df7237ab5b9e2fcabee4965d0a0a"
|
|
||||||
dependencies = [
|
|
||||||
"deadpool-runtime",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -410,50 +367,6 @@ dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel"
|
|
||||||
version = "2.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2268a214a6f118fce1838edba3d1561cf0e78d8de785475957a580a7f8c69d33"
|
|
||||||
dependencies = [
|
|
||||||
"diesel_derives",
|
|
||||||
"libsqlite3-sys",
|
|
||||||
"time",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel_derives"
|
|
||||||
version = "2.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44"
|
|
||||||
dependencies = [
|
|
||||||
"diesel_table_macro_syntax",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel_migrations"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac"
|
|
||||||
dependencies = [
|
|
||||||
"diesel",
|
|
||||||
"migrations_internals",
|
|
||||||
"migrations_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diesel_table_macro_syntax"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
|
||||||
dependencies = [
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -465,12 +378,6 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "equivalent"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eyre"
|
name = "eyre"
|
||||||
version = "0.6.8"
|
version = "0.6.8"
|
||||||
|
@ -593,12 +500,6 @@ version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.14.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "headers"
|
name = "headers"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -713,16 +614,6 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indexmap"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
|
||||||
dependencies = [
|
|
||||||
"equivalent",
|
|
||||||
"hashbrown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
|
@ -790,16 +681,6 @@ version = "0.2.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libsqlite3-sys"
|
|
||||||
version = "0.26.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
|
|
||||||
dependencies = [
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -828,10 +709,6 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
"cookie",
|
"cookie",
|
||||||
"deadpool",
|
|
||||||
"deadpool-diesel",
|
|
||||||
"diesel",
|
|
||||||
"diesel_migrations",
|
|
||||||
"maud",
|
"maud",
|
||||||
"notify",
|
"notify",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -886,27 +763,6 @@ version = "2.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "migrations_internals"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "migrations_macros"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08"
|
|
||||||
dependencies = [
|
|
||||||
"migrations_internals",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
@ -1088,12 +944,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -1326,15 +1176,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
@ -1549,40 +1390,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.7.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.19.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"winnow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
@ -1759,24 +1566,12 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "uuid"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -1904,12 +1699,3 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.48.5"
|
version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.5.19"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
|
@ -20,12 +20,6 @@ tower-http = { version = "0.4.4", features = ["fs"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||||
|
|
||||||
# database
|
|
||||||
deadpool = "0.10.0"
|
|
||||||
deadpool-diesel = { version = "0.5.0", features = ["rt_tokio_1", "sqlite", "tracing", "serde"] }
|
|
||||||
diesel = { version = "2.1.3", features = ["sqlite", "uuid"] }
|
|
||||||
diesel_migrations = { version = "2.1.0", features = ["sqlite"] }
|
|
||||||
|
|
||||||
# fancy during-development stuff
|
# fancy during-development stuff
|
||||||
notify = "6.1.1"
|
notify = "6.1.1"
|
||||||
tower-livereload = "0.8.2"
|
tower-livereload = "0.8.2"
|
||||||
|
@ -34,7 +28,6 @@ thiserror = "1.0.50"
|
||||||
axum-macros = "0.3.8"
|
axum-macros = "0.3.8"
|
||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
|
|
||||||
# color-eyre
|
|
||||||
# irust
|
# irust
|
||||||
# bacon
|
# bacon
|
||||||
# sqlx (sea orm?)
|
# sqlx (sea orm?)
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[print_schema]
|
|
||||||
file = "src/schema.rs"
|
|
||||||
custom_type_derives = ["diesel::query_builder::QueryId"]
|
|
||||||
|
|
||||||
[migrations_directory]
|
|
||||||
dir = "migrations"
|
|
|
@ -50,11 +50,9 @@
|
||||||
|
|
||||||
rustfmt
|
rustfmt
|
||||||
rustPackages.clippy
|
rustPackages.clippy
|
||||||
rustPackages.bacon
|
|
||||||
rust-analyzer
|
rust-analyzer
|
||||||
nodePackages_latest.vscode-langservers-extracted
|
nodePackages_latest.vscode-langservers-extracted
|
||||||
|
|
||||||
diesel-cli
|
|
||||||
hurl
|
hurl
|
||||||
];
|
];
|
||||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
drop table users
|
|
|
@ -1,7 +0,0 @@
|
||||||
create table users (
|
|
||||||
id text primary key,
|
|
||||||
username text
|
|
||||||
name text,
|
|
||||||
password_digest binary
|
|
||||||
);
|
|
||||||
create unique index users_username on users(username);
|
|
|
@ -1,42 +1 @@
|
||||||
use deadpool_diesel::sqlite::{Manager, Pool, Runtime};
|
|
||||||
use diesel::sqlite::Sqlite;
|
|
||||||
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
|
||||||
use tracing::info;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
15
src/error.rs
15
src/error.rs
|
@ -1,13 +1,9 @@
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
pub struct AppError(anyhow::Error);
|
||||||
pub struct AppError(#[from] anyhow::Error);
|
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError {
|
||||||
fn into_response(self) -> Response {
|
fn into_response(self) -> Response {
|
||||||
|
@ -19,8 +15,11 @@ impl IntoResponse for AppError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for AppError {
|
impl<E> From<E> for AppError
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
where
|
||||||
f.write_fmt(format!("app error"))
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
fn from(err: E) -> Self {
|
||||||
|
Self(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use tracing::{instrument, trace};
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
color_eyre::install();
|
color_eyre::install().expect("Failed to install color_eyre");
|
||||||
setup_trace_logger();
|
setup_trace_logger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ mod instrumentation;
|
||||||
mod models;
|
mod models;
|
||||||
mod partials;
|
mod partials;
|
||||||
mod router;
|
mod router;
|
||||||
mod schema;
|
|
||||||
mod server;
|
mod server;
|
||||||
mod state;
|
mod state;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
use diesel::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Queryable, Selectable)]
|
|
||||||
#[diesel(table_name = crate::schema::users)]
|
|
||||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
|
||||||
pub struct User {
|
pub struct User {
|
||||||
// pub id: Text,
|
|
||||||
// pub username: sql_types::Text,
|
|
||||||
// pub name: sql_types::Text,
|
|
||||||
// pub password_digest: sql_types::Binary,
|
|
||||||
pub id: Vec<u8>,
|
pub id: Vec<u8>,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub password_digest: Vec<u8>,
|
pub password_digest: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
#[diesel(table_name = crate::schema::users)]
|
|
||||||
pub struct NewUser<'a> {
|
pub struct NewUser<'a> {
|
||||||
pub username: &'a str,
|
pub username: &'a str,
|
||||||
pub name: Option<&'a str>,
|
pub name: Option<&'a str>,
|
||||||
pub password_digest: &'a [u8],
|
pub password_digest: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{env, path::Path, sync::Arc};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use argon2::{
|
use argon2::{
|
||||||
password_hash::{rand_core::OsRng, SaltString},
|
password_hash::{rand_core::OsRng, SaltString},
|
||||||
|
@ -29,7 +29,7 @@ pub async fn new() -> Result<Router, anyhow::Error> {
|
||||||
.route("/hello-world-text", get(views::greet_world_text));
|
.route("/hello-world-text", get(views::greet_world_text));
|
||||||
|
|
||||||
let assets_dir = ServeDir::new("./assets");
|
let assets_dir = ServeDir::new("./assets");
|
||||||
let state = Arc::new(State::new().await?);
|
let state = State::new().await?;
|
||||||
|
|
||||||
let live_reload_layer = LiveReloadLayer::new();
|
let live_reload_layer = LiveReloadLayer::new();
|
||||||
let reloader = live_reload_layer.reloader();
|
let reloader = live_reload_layer.reloader();
|
||||||
|
@ -48,6 +48,7 @@ pub async fn new() -> Result<Router, anyhow::Error> {
|
||||||
.nest("/app", app_router)
|
.nest("/app", app_router)
|
||||||
.nest_service("/assets", assets_dir)
|
.nest_service("/assets", assets_dir)
|
||||||
.route("/", get(views::index))
|
.route("/", get(views::index))
|
||||||
|
.route("/login", get(views::login).post(login))
|
||||||
.route("/register", get(views::register).post(register))
|
.route("/register", get(views::register).post(register))
|
||||||
.route("/all_users", get(views::all_users))
|
.route("/all_users", get(views::all_users))
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
|
@ -64,11 +65,23 @@ struct Register {
|
||||||
password: String,
|
password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryInto<NewUser<'a>> for Register {
|
impl<'a> TryInto<NewUser<'a>> for &'a Register {
|
||||||
type Error = ();
|
type Error = argon2::password_hash::Error;
|
||||||
|
|
||||||
fn try_into(self) -> Result<NewUser<'a>, Self::Error> {
|
fn try_into(self: &'a Register) -> Result<NewUser<'a>, Self::Error> {
|
||||||
todo!()
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
let password_digest: Vec<u8> = argon2
|
||||||
|
.hash_password(self.password.as_bytes(), &salt)?
|
||||||
|
.hash
|
||||||
|
.expect("no password hash")
|
||||||
|
.as_bytes()
|
||||||
|
.into();
|
||||||
|
Ok(NewUser {
|
||||||
|
username: &self.username,
|
||||||
|
name: None,
|
||||||
|
password_digest,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +91,40 @@ async fn register(
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
// TODO: https://docs.rs/axum_csrf/latest/axum_csrf/#prevent-post-replay-attacks-with-csrf
|
// TODO: https://docs.rs/axum_csrf/latest/axum_csrf/#prevent-post-replay-attacks-with-csrf
|
||||||
|
|
||||||
|
let v = csrf_token.verify(®ister.authenticity_token);
|
||||||
|
if v.is_err() {
|
||||||
|
return Ok((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Html(html! { "invalid request" }.into_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_user: NewUser = (®ister).try_into()?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
StatusCode::CREATED,
|
||||||
|
Html(
|
||||||
|
html! {
|
||||||
|
h1 { (new_user.username) }
|
||||||
|
}
|
||||||
|
.into_string(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Login {
|
||||||
|
authenticity_token: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn login(
|
||||||
|
csrf_token: CsrfToken,
|
||||||
|
Form(register): Form<Login>,
|
||||||
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
|
// TODO: https://docs.rs/axum_csrf/latest/axum_csrf/#prevent-post-replay-attacks-with-csrf
|
||||||
|
|
||||||
let v = csrf_token.verify(®ister.authenticity_token);
|
let v = csrf_token.verify(®ister.authenticity_token);
|
||||||
println!("{:?} {:?}", register.authenticity_token, v);
|
println!("{:?} {:?}", register.authenticity_token, v);
|
||||||
if v.is_err() {
|
if v.is_err() {
|
||||||
|
@ -87,16 +134,6 @@ async fn register(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
|
||||||
let argon2 = Argon2::default();
|
|
||||||
let password_digest = argon2.hash_password(register.password.as_bytes(), &salt)?;
|
|
||||||
|
|
||||||
let _new_user = NewUser {
|
|
||||||
username: ®ister.username,
|
|
||||||
name: None,
|
|
||||||
password_digest: password_digest.to_string().as_bytes(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
StatusCode::CREATED,
|
StatusCode::CREATED,
|
||||||
Html(
|
Html(
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// @generated automatically by Diesel CLI.
|
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
users (id) {
|
|
||||||
id -> Binary,
|
|
||||||
username -> Text,
|
|
||||||
name -> Nullable<Text>,
|
|
||||||
password_digest -> Binary,
|
|
||||||
}
|
|
||||||
}
|
|
11
src/state.rs
11
src/state.rs
|
@ -1,17 +1,12 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use crate::database;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct State {
|
pub struct State {}
|
||||||
pub database: database::Database,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub async fn new() -> Result<Self, anyhow::Error> {
|
pub async fn new() -> Result<Self, anyhow::Error> {
|
||||||
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let _database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
let database = database::Database::new(database_url).await?;
|
|
||||||
|
|
||||||
Ok(State { database })
|
Ok(State {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
src/views.rs
40
src/views.rs
|
@ -1,15 +1,9 @@
|
||||||
use std::{error::Error, sync::Arc};
|
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
};
|
};
|
||||||
use axum_csrf::CsrfToken;
|
use axum_csrf::CsrfToken;
|
||||||
use axum_macros::debug_handler;
|
|
||||||
use deadpool_diesel::InteractError;
|
|
||||||
use diesel::{QueryDsl, RunQueryDsl, SelectableHelper};
|
|
||||||
use maud::html;
|
use maud::html;
|
||||||
use thiserror::Error;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -102,32 +96,11 @@ pub async fn login(csrf: CsrfToken) -> impl IntoResponse {
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[allow(unreachable_code)]
|
||||||
enum AllUsersError {
|
|
||||||
#[error("other application error")]
|
|
||||||
App(#[from] AppError),
|
|
||||||
#[error("failed to retrieve users")]
|
|
||||||
DB(#[from] InteractError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[debug_handler]
|
|
||||||
pub async fn all_users(
|
pub async fn all_users(
|
||||||
State(state): State<crate::state::State>,
|
State(_state): State<crate::state::State>,
|
||||||
) -> Result<Html<String>, AllUsersError> {
|
) -> Result<Html<String>, AppError> {
|
||||||
use crate::schema::users::dsl::*;
|
let all_users: Vec<User> = vec![];
|
||||||
|
|
||||||
let conn = state.database.pool.get().await?;
|
|
||||||
let cc = |c| -> Vec<User> {
|
|
||||||
users
|
|
||||||
.select(User::as_select())
|
|
||||||
.load(c)
|
|
||||||
.expect("error loading users")
|
|
||||||
};
|
|
||||||
|
|
||||||
let all_users: Vec<User> = match conn.interact(cc).await {
|
|
||||||
Ok(u) => u,
|
|
||||||
Err(e) => return Err(("failed to retrieve users")),
|
|
||||||
};
|
|
||||||
|
|
||||||
// @if let Some(name) = u.name {
|
// @if let Some(name) = u.name {
|
||||||
// name
|
// name
|
||||||
|
@ -142,13 +115,16 @@ pub async fn all_users(
|
||||||
ul {
|
ul {
|
||||||
@for u in all_users {
|
@for u in all_users {
|
||||||
li {
|
li {
|
||||||
(u)
|
(u.username)
|
||||||
|
@if let Some(name) = u.name {
|
||||||
" ("
|
" ("
|
||||||
|
(name)
|
||||||
")"
|
")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
(footer())
|
(footer())
|
||||||
}
|
}
|
||||||
.into_string(),
|
.into_string(),
|
||||||
|
|
Loading…
Reference in a new issue