diff --git a/Cargo.lock b/Cargo.lock index ee31571..7df19bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,17 +234,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "auto-future" version = "1.0.0" @@ -450,29 +439,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.60.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" -dependencies = [ - "bitflags 1.3.2", - "cexpr", - "clang-sys", - "clap 3.2.25", - "env_logger", - "lazy_static", - "lazycell", - "log", - "peeking_take_while", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "which", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -554,15 +520,6 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - [[package]] name = "cfg-if" version = "0.1.10" @@ -585,35 +542,10 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", + "time 0.1.45", "winapi", ] -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex 0.2.4", - "indexmap 1.9.3", - "strsim", - "termcolor", - "textwrap", -] - [[package]] name = "clap" version = "4.3.19" @@ -633,7 +565,7 @@ checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ "anstream", "anstyle", - "clap_lex 0.5.0", + "clap_lex", "strsim", "unicase", "unicode-width", @@ -651,15 +583,6 @@ dependencies = [ "syn 2.0.27", ] -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.5.0" @@ -696,7 +619,7 @@ dependencies = [ "rand", "sha2 0.10.7", "subtle", - "time", + "time 0.3.23", "version_check", ] @@ -832,19 +755,6 @@ dependencies = [ "serde", ] -[[package]] -name = "env_logger" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -1040,7 +950,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -1049,18 +959,6 @@ version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.0" @@ -1077,7 +975,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -1114,15 +1012,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.2" @@ -1221,12 +1110,6 @@ dependencies = [ "libm", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.27" @@ -1283,16 +1166,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.0.0" @@ -1300,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown", ] [[package]] @@ -1309,7 +1182,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "rustix", "windows-sys", ] @@ -1340,11 +1213,13 @@ dependencies = [ [[package]] name = "julid-rs" -version = "0.1.6" +version = "1.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2081aa5e2e85795e4e2b36e95df914655cf281d0e3eafb7e538754d704ac18e3" dependencies = [ + "chrono", "rand", "serde", - "sqlite-loadable", "sqlx", ] @@ -1368,28 +1243,12 @@ dependencies = [ "spin 0.5.2", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - [[package]] name = "libm" version = "0.2.7" @@ -1497,7 +1356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1575,7 +1434,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.2", + "hermit-abi", "libc", ] @@ -1608,12 +1467,6 @@ dependencies = [ "syn 2.0.27", ] -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - [[package]] name = "overload" version = "0.1.1" @@ -1660,12 +1513,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1909,12 +1756,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustix" version = "0.38.4" @@ -1997,18 +1838,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.176" +version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" +checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.176" +version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f" +checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3" dependencies = [ "proc-macro2", "quote", @@ -2101,12 +1942,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2187,40 +2022,6 @@ dependencies = [ "unicode_categories", ] -[[package]] -name = "sqlite-loadable" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a916b7bb8738eef189dea88731b619b80bf3f62b3acf05138fa43fbf8621cc94" -dependencies = [ - "bitflags 1.3.2", - "serde", - "serde_json", - "sqlite-loadable-macros", - "sqlite3ext-sys", -] - -[[package]] -name = "sqlite-loadable-macros" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98bc75a8d6fd24f6a2cfea34f28758780fa17279d3051eec926efa381971e48" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "sqlite3ext-sys" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afdc2b3dc08f16d6eecf8aa07d19975a268603ab1cca67d3f9b4172c507cf16" -dependencies = [ - "bindgen", - "cc", -] - [[package]] name = "sqlx" version = "0.7.1" @@ -2256,7 +2057,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.0", + "indexmap", "log", "memchr", "once_cell", @@ -2481,21 +2282,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - [[package]] name = "thiserror" version = "1.0.44" @@ -2526,6 +2312,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.23" @@ -2844,6 +2641,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2936,7 +2739,7 @@ dependencies = [ "axum-macros", "axum-test", "chrono", - "clap 4.3.19", + "clap", "julid-rs", "justerror", "optional_optional_user", @@ -2957,17 +2760,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - [[package]] name = "whoami" version = "1.4.1" @@ -2990,15 +2782,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 456de10..7c78337 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ axum-login = { git = "https://github.com/nebkor/axum-login", branch = "sqlx-0.7" axum-macros = "0.3" chrono = { version = "0.4", default-features = false, features = ["std", "clock"] } clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] } -julid-rs = { path = "../julid" } +julid-rs = "1" justerror = "1" password-hash = { version = "0.5", features = ["std", "getrandom"] } rand = "0.8" diff --git a/src/bin/import_omega.rs b/src/bin/import_omega.rs index 065cc35..a151395 100644 --- a/src/bin/import_omega.rs +++ b/src/bin/import_omega.rs @@ -1,18 +1,29 @@ use std::{ffi::OsString, time::Duration}; use clap::Parser; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; +use sqlx::{ + query_as, + sqlite::{SqliteConnectOptions, SqlitePoolOptions}, +}; use what2watch::{get_db_pool, import_utils::add_omega_watches}; #[derive(Debug, Parser)] struct Cli { #[clap(long, short)] pub db_path: OsString, + #[clap( + long, + short, + help = "Number of movies to add in the benchmark.", + default_value_t = 10_000 + )] + pub number: u32, } fn main() { let cli = Cli::parse(); let path = cli.db_path; + let num = cli.number; let opts = SqliteConnectOptions::new().filename(path).read_only(true); let movie_db = { @@ -31,26 +42,45 @@ fn main() { let w2w_db = get_db_pool(); - let start = std::time::Instant::now(); - let rows = { + let dur = { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); rt.block_on(async { - add_omega_watches(&w2w_db, &movie_db).await.unwrap(); - - let rows: i32 = sqlx::query_scalar("select count(*) from watches") - .fetch_one(&w2w_db) - .await - .unwrap(); + let dur = add_omega_watches(&w2w_db, &movie_db, num).await.unwrap(); w2w_db.close().await; - rows + dur }) }; - let end = std::time::Instant::now(); - let dur = (end - start).as_secs_f32(); - println!("Added {rows} movies in {dur} seconds"); + let w2w_db = get_db_pool(); + let movies: Vec<(String, f64, i64)> = { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + let movies = rt.block_on( + query_as( + "select julid_string(id), julid_seconds(id) * 1000, julid_counter(id) from watches", + ) + .fetch_all(&w2w_db), + ) + .unwrap(); + rt.block_on(w2w_db.close()); + movies + }; + let rows = movies.len(); + + for m in movies.iter() { + println!("{}: {}ms, {} count", m.0, m.1, m.2); + } + + println!( + "Added {rows} movies in {} seconds ({}ms, {}us)", + dur.as_secs_f64(), + dur.as_millis(), + dur.as_micros() + ); } diff --git a/src/bin/import_users.rs b/src/bin/import_users.rs index 4f0b8b0..0522079 100644 --- a/src/bin/import_users.rs +++ b/src/bin/import_users.rs @@ -1,9 +1,11 @@ use std::{ffi::OsString, time::Duration}; use clap::Parser; +use julid::Julid; use rand::{seq::SliceRandom, thread_rng, Rng}; use rand_distr::Normal; use sqlx::{ + query_as, sqlite::{SqliteConnectOptions, SqlitePoolOptions}, SqlitePool, }; @@ -12,7 +14,7 @@ use tokio_retry::Retry; use what2watch::{ get_db_pool, import_utils::{add_omega_watches, add_users, add_watch_quests}, - DbId, User, WatchQuest, + User, Watch, WatchQuest, }; fn main() { @@ -29,7 +31,7 @@ fn main() { let words = std::fs::read_to_string(dict).expect("tried to open {dict:?}"); let words: Vec<&str> = words.split('\n').collect(); - let opts = SqliteConnectOptions::new().filename(&path).read_only(true); + let opts = SqliteConnectOptions::new().filename(path).read_only(true); let rt = runtime::Builder::new_multi_thread() .enable_all() @@ -47,7 +49,14 @@ fn main() { let users = &rt.block_on(gen_users(num_users, &words, &w2w_db)); - let movies = &rt.block_on(add_omega_watches(&w2w_db, &movie_db)).unwrap(); + let _ = &rt + .block_on(add_omega_watches(&w2w_db, &movie_db, 10_000)) + .unwrap(); + + let movies: Vec = rt + .block_on(query_as("select * from watches").fetch_all(&w2w_db)) + .unwrap(); + let movies: Vec = movies.into_iter().map(|m| m.id).collect(); let rng = &mut thread_rng(); @@ -55,7 +64,7 @@ fn main() { let start = std::time::Instant::now(); rt.block_on(async { for &user in users { - add_quests(user, movies, &w2w_db, rng, normal).await; + add_quests(user, &movies, &w2w_db, rng, normal).await; } }); let rows: i32 = rt @@ -70,7 +79,7 @@ fn main() { //-************************************************************************ // add the users //-************************************************************************ -async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec { +async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec { let mut rng = thread_rng(); let rng = &mut rng; let range = 0usize..(words.len()); @@ -88,9 +97,9 @@ async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec { let username = format!("{n1}_{n2}{nn}"); let displayname = Some(format!("{n1} {n2}")); let email = Some(format!("{username}@{email_domain}")); - let id = DbId::new(); + let user = User { - id, + id: 0.into(), username, displayname, email, @@ -108,8 +117,8 @@ async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec { // batch add quests //-************************************************************************ async fn add_quests( - user: DbId, - movies: &[DbId], + user: Julid, + movies: &[Julid], w2w_db: &SqlitePool, rng: &mut R, normal: Normal, diff --git a/src/db.rs b/src/db.rs index 1a34afa..464db67 100644 --- a/src/db.rs +++ b/src/db.rs @@ -5,6 +5,7 @@ use axum_login::{ axum_sessions::{PersistencePolicy, SessionLayer}, AuthLayer, SqliteStore, SqlxStore, }; +use julid::Julid; use session_store::SqliteSessionStore; use sqlx::{ migrate::Migrator, @@ -12,7 +13,7 @@ use sqlx::{ SqlitePool, }; -use crate::{DbId, User}; +use crate::User; const MAX_CONNS: u32 = 200; const MIN_CONNS: u32 = 5; @@ -111,7 +112,7 @@ pub async fn session_layer(pool: SqlitePool, secret: &[u8]) -> SessionLayer AuthLayer, DbId, User> { +) -> AuthLayer, Julid, User> { const QUERY: &str = "select * from users where id = $1"; let store = SqliteStore::::new(pool).with_query(QUERY); AuthLayer::new(store, secret) @@ -449,14 +450,14 @@ mod session_store { let mut session = Session::new(); session.expire_in(Duration::from_secs(10)); let original_id = session.id().to_owned(); - let original_expires = session.expiry().unwrap().clone(); + let original_expires = *session.expiry().unwrap(); let cookie_value = store.store_session(session).await?.unwrap(); let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); assert_eq!(session.expiry().unwrap(), &original_expires); session.expire_in(Duration::from_secs(20)); - let new_expires = session.expiry().unwrap().clone(); + let new_expires = *session.expiry().unwrap(); store.store_session(session).await?; let session = store.load_session(cookie_value.clone()).await?.unwrap(); diff --git a/src/import_utils.rs b/src/import_utils.rs index 2772538..d18909d 100644 --- a/src/import_utils.rs +++ b/src/import_utils.rs @@ -1,16 +1,18 @@ +use std::time::{Duration, Instant}; + +use julid::Julid; use sqlx::{query_scalar, SqlitePool}; -use crate::{util::year_to_epoch, DbId, ShowKind, User, Watch, WatchQuest}; +use crate::{util::year_to_epoch, ShowKind, User, Watch, WatchQuest}; const USER_EXISTS_QUERY: &str = "select count(*) from users where id = $1"; -const MOVIE_QUERY: &str = "select * from movies order by random() limit 10000"; +const MOVIE_QUERY: &str = "select * from movies order by random() limit ?"; //-************************************************************************ // the omega user is the system ID, but has no actual power in the app //-************************************************************************ -const OMEGA_ID: u128 = u128::MAX; - +const OMEGA_ID: Julid = Julid::omega(); const BULK_INSERT: usize = 2_000; #[derive(Debug, sqlx::FromRow, Clone)] @@ -23,13 +25,13 @@ pub struct ImportMovieOmega { impl From for Watch { fn from(value: ImportMovieOmega) -> Self { Watch { + id: Julid::omega(), // this is ignored by the inserter title: value.title, release_date: year_to_epoch(value.year.as_deref()), length: value.length.and_then(|v| v.parse::().ok()), - id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, - added_by: OMEGA_ID.into(), + added_by: OMEGA_ID, } } } @@ -37,13 +39,13 @@ impl From for Watch { impl From<&ImportMovieOmega> for Watch { fn from(value: &ImportMovieOmega) -> Self { Watch { + id: Julid::omega(), title: value.title.to_string(), release_date: year_to_epoch(value.year.as_deref()), length: value.length.as_ref().and_then(|v| v.parse::().ok()), - id: DbId::new(), kind: ShowKind::Movie, metadata_url: None, - added_by: OMEGA_ID.into(), + added_by: OMEGA_ID, } } } @@ -68,78 +70,70 @@ pub async fn add_watch_quests(pool: &SqlitePool, quests: &[WatchQuest]) -> Resul Ok(()) } -pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result<(), ()> { +pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result { let mut builder = - sqlx::QueryBuilder::new("insert into users (id, username, displayname, email, pwhash) "); + sqlx::QueryBuilder::new("insert into users (username, displayname, email, pwhash)"); + let start = Instant::now(); builder.push_values(users.iter(), |mut b, user| { - b.push_bind(user.id) - .push_bind(&user.username) + b.push_bind(&user.username) .push_bind(&user.displayname) .push_bind(&user.email) .push_bind(&user.pwhash); }); let q = builder.build(); q.execute(db_pool).await.map_err(|_| ())?; - Ok(()) + let end = Instant::now(); + let dur = end - start; + + Ok(dur) } pub async fn add_omega_watches( w2w_db: &SqlitePool, movie_db: &SqlitePool, -) -> Result, ()> { - ensure_omega(w2w_db).await; + num: u32, +) -> Result { + let omega = ensure_omega(w2w_db).await; let movies: Vec = sqlx::query_as(MOVIE_QUERY) + .bind(num) .fetch_all(movie_db) .await .unwrap(); - let mut ids = Vec::with_capacity(10_000); - let omega: DbId = OMEGA_ID.into(); - + let start = Instant::now(); for movies in movies.as_slice().chunks(BULK_INSERT) { let mut builder = sqlx::QueryBuilder::new( - "insert into watches (id, kind, title, length, release_date, added_by) ", + "insert into watches (kind, title, length, release_date, added_by) ", ); builder.push_values(movies, |mut b, movie| { - let id = DbId::new(); - ids.push(id); let title = &movie.title; - - b.push_bind(id) - .push_bind(ShowKind::Movie) + b.push_bind(ShowKind::Movie) .push_bind(title) .push_bind(movie.length.as_ref().and_then(|l| l.parse::().ok())) .push_bind(year_to_epoch(movie.year.as_deref())) .push_bind(omega); }); let q = builder.build(); - q.execute(w2w_db).await.map_err(|_| ())?; } + let end = Instant::now(); + let dur = end - start; - Ok(ids) + Ok(dur) } -pub async fn ensure_omega(db_pool: &SqlitePool) -> DbId { +pub async fn ensure_omega(db_pool: &SqlitePool) -> Julid { if !check_omega_exists(db_pool).await { - let omega = User { - id: OMEGA_ID.into(), - username: "The Omega User".to_string(), - displayname: Some("I am the end of all watches".to_string()), - email: None, - last_seen: None, - pwhash: "you shall not password".to_string(), - }; - add_users(db_pool, &[omega]).await.unwrap(); + sqlx::query("insert into users (id, username, pwhash) values (?, 'the omega user', 'you shall not password')").bind(Julid::omega()).execute(db_pool).await.unwrap(); } - OMEGA_ID.into() + OMEGA_ID } async fn check_omega_exists(db_pool: &SqlitePool) -> bool { - let id: DbId = OMEGA_ID.into(); + let id = Julid::omega(); let count = query_scalar(USER_EXISTS_QUERY) .bind(id) .fetch_one(db_pool) @@ -165,7 +159,8 @@ mod test { rt.block_on(async { assert!(!check_omega_exists(&p).await); ensure_omega(&p).await; - assert!(check_omega_exists(&p).await); }); + + assert!(rt.block_on(check_omega_exists(&p))); } } diff --git a/src/lib.rs b/src/lib.rs index ff5d1b6..6556ae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,8 +30,8 @@ use optional_optional_user::OptionalOptionalUser; use templates::*; use watches::templates::*; -pub type DbId = julid::Julid; -type AuthContext = axum_login::extractors::AuthContext>; +type AuthContext = + axum_login::extractors::AuthContext>; /// Returns the router to be used as a service or test object, you do you. pub async fn app(db_pool: sqlx::SqlitePool, secret: &[u8]) -> IntoMakeService { diff --git a/src/login.rs b/src/login.rs index 87eb549..32d807c 100644 --- a/src/login.rs +++ b/src/login.rs @@ -114,7 +114,8 @@ mod test { use crate::{ get_db_pool, templates::{LoginPage, LogoutPage, LogoutSuccessPage, MainPage}, - test_utils::{get_test_user, massage, server_with_pool, FORM_CONTENT_TYPE}, + test_utils::{massage, server_with_pool, FORM_CONTENT_TYPE}, + User, }; const LOGIN_FORM: &str = "username=test_user&password=a"; @@ -257,10 +258,9 @@ mod test { .await; assert_eq!(resp.status_code(), 303); - let logged_in = MainPage { - user: Some(get_test_user()), - } - .to_string(); + let user = User::try_get("test_user", &db).await.unwrap(); + + let logged_in = MainPage { user: Some(user) }.to_string(); let main_page = s.get("/").await; let body = std::str::from_utf8(main_page.bytes()).unwrap(); diff --git a/src/signup.rs b/src/signup.rs index 096cef3..873f76f 100644 --- a/src/signup.rs +++ b/src/signup.rs @@ -7,14 +7,15 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; +use julid::Julid; use serde::Deserialize; use sqlx::{query_as, SqlitePool}; use unicode_segmentation::UnicodeSegmentation; -use crate::{util::empty_string_as_none, DbId, SignupPage, SignupSuccessPage, User}; +use crate::{util::empty_string_as_none, SignupPage, SignupSuccessPage, User}; pub(crate) const CREATE_QUERY: &str = - "insert into users (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)"; + "insert into users (username, displayname, email, pwhash) values ($1, $2, $3, $4) returning *"; const ID_QUERY: &str = "select * from users where id = $1"; //-************************************************************************ @@ -107,11 +108,9 @@ pub async fn post_create_user( let email = validate_optional_length(&signup.email, 5..30, CreateUserErrorKind::BadEmail)?; - let id = DbId::new(); + let user = create_user(username, &displayname, &email, password, &pool).await?; - let user = create_user(username, &displayname, &email, password, &pool, id).await?; - let now = user.id.timestamp(); - tracing::debug!("created {user:?} at {now:?}"); + tracing::debug!("created {user:?}"); let id = user.id.as_string(); let location = format!("/signup_success/{id}"); @@ -126,7 +125,7 @@ pub async fn get_signup_success( State(pool): State, ) -> Response { let id = id.trim(); - let id = DbId::from_string(id).unwrap_or_default(); + let id = Julid::from_string(id).unwrap_or_default(); let user: User = { query_as(ID_QUERY) .bind(id) @@ -156,7 +155,6 @@ pub(crate) async fn create_user( email: &Option, password: &[u8], pool: &SqlitePool, - id: DbId, ) -> Result { // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); @@ -166,27 +164,16 @@ pub(crate) async fn create_user( .unwrap() // safe to unwrap, we know the salt is valid .to_string(); - let query = sqlx::query(CREATE_QUERY) - .bind(id) + let res = sqlx::query_as(CREATE_QUERY) .bind(username) .bind(displayname) .bind(email) - .bind(&pwhash); - - let res = query.execute(pool).await; + .bind(&pwhash) + .fetch_one(pool) + .await; match res { - Ok(_) => { - let user = User { - id, - username: username.to_string(), - displayname: displayname.to_owned(), - email: email.to_owned(), - last_seen: None, - pwhash, - }; - Ok(user) - } + Ok(user) => Ok(user), Err(sqlx::Error::Database(db)) => { if let Some(exit) = db.code() { let exit = exit.parse().unwrap_or(0u32); @@ -216,11 +203,11 @@ mod test { use crate::{ db::get_db_pool, templates::{SignupPage, SignupSuccessPage}, - test_utils::{get_test_user, massage, server_with_pool, FORM_CONTENT_TYPE}, + test_utils::{massage, server_with_pool, FORM_CONTENT_TYPE}, User, }; - const GOOD_FORM: &str = "username=good_user&displayname=Test+User&password=aaaa&pw_verify=aaaa"; + const GOOD_FORM: &str = "username=good_user&displayname=Good+User&password=aaaa&pw_verify=aaaa"; #[test] fn post_create_user() { @@ -265,10 +252,20 @@ mod test { let rt = Runtime::new().unwrap(); rt.block_on(async { let server = server_with_pool(&pool).await; + let body = massage(GOOD_FORM); - let user = get_test_user(); - let id = user.id.to_string(); - dbg!(&id); + let resp = server + .post("/signup") + .expect_failure() // 303 is "failure" + .bytes(body) + .content_type(FORM_CONTENT_TYPE) + .await; + + assert_eq!(StatusCode::SEE_OTHER, resp.status_code()); + + // get the new user from the db + let user = User::try_get("good_user", &pool).await.unwrap(); + let id = user.id; let path = format!("/signup_success/{id}"); @@ -288,15 +285,15 @@ mod test { // various ways to fuck up signup const PASSWORD_MISMATCH_FORM: &str = - "username=bad_user&displayname=Test+User&password=aaaa&pw_verify=bbbb"; + "username=bad_user&displayname=Bad+User&password=aaaa&pw_verify=bbbb"; const PASSWORD_SHORT_FORM: &str = - "username=bad_user&displayname=Test+User&password=a&pw_verify=a"; - const PASSWORD_LONG_FORM: &str = "username=bad_user&displayname=Test+User&password=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda&pw_verify=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda"; + "username=bad_user&displayname=Bad+User&password=a&pw_verify=a"; + const PASSWORD_LONG_FORM: &str = "username=bad_user&displayname=Bad+User&password=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda&pw_verify=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda"; const USERNAME_SHORT_FORM: &str = - "username=&displayname=Test+User&password=aaaa&pw_verify=aaaa"; + "username=&displayname=Bad+User&password=aaaa&pw_verify=aaaa"; const USERNAME_LONG_FORM: &str = - "username=bad_user12345678901234567890&displayname=Test+User&password=aaaa&pw_verify=aaaa"; - const DISPLAYNAME_LONG_FORM: &str = "username=test_user&displayname=Since+time+immemorial%2C+display+names+have+been+subject+to+a+number+of+conventions%2C+restrictions%2C+usages%2C+and+even+incentives.+Have+we+finally+gone+too+far%3F+In+this+essay%2C+&password=aaaa&pw_verify=aaaa"; + "username=bad_user12345678901234567890&displayname=Bad+User&password=aaaa&pw_verify=aaaa"; + const DISPLAYNAME_LONG_FORM: &str = "username=bad_user&displayname=Since+time+immemorial%2C+display+names+have+been+subject+to+a+number+of+conventions%2C+restrictions%2C+usages%2C+and+even+incentives.+Have+we+finally+gone+too+far%3F+In+this+essay%2C+&password=aaaa&pw_verify=aaaa"; #[test] fn password_mismatch() { diff --git a/src/test_utils.rs b/src/test_utils.rs index d758522..f8b9c60 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,28 +1,18 @@ use axum::body::Bytes; use axum_test::{TestServer, TestServerConfig}; +use julid::Julid; use sqlx::SqlitePool; -use crate::{DbId, User}; +use crate::User; pub const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded"; -pub fn get_test_user() -> User { - User { - username: "test_user".to_string(), - // corresponding to a password of "a": - pwhash: "$argon2id$v=19$m=19456,t=2,p=1$GWsCH1w5RYaP9WWmq+xw0g$hmOEqC+MU+vnEk3bOdkoE+z01mOmmOeX08XyPyjqua8".to_string(), - id: DbId::from_string("00041061050R3GG28A1C60T3GF").unwrap(), - displayname: Some("Test User".to_string()), - ..Default::default() - } -} - pub async fn server_with_pool(pool: &SqlitePool) -> TestServer { let secret = [0u8; 64]; let user = get_test_user(); - insert_user(&user, pool).await; + insert_user_with_id(&user, pool).await; let r: i32 = sqlx::query_scalar("select count(*) from users") .fetch_one(pool) .await @@ -38,16 +28,29 @@ pub async fn server_with_pool(pool: &SqlitePool) -> TestServer { TestServer::new_with_config(app, config).unwrap() } -pub async fn insert_user(user: &User, pool: &SqlitePool) { - sqlx::query(crate::signup::CREATE_QUERY) - .bind(user.id) - .bind(&user.username) - .bind(&user.displayname) - .bind(&user.email) - .bind(&user.pwhash) - .execute(pool) - .await - .unwrap(); +fn get_test_user() -> User { + User { + username: "test_user".to_string(), + // corresponding to a password of "a": + pwhash: "$argon2id$v=19$m=19456,t=2,p=1$GWsCH1w5RYaP9WWmq+xw0g$hmOEqC+MU+vnEk3bOdkoE+z01mOmmOeX08XyPyjqua8".to_string(), + id: Julid::omega(), + displayname: Some("Test User".to_string()), + ..Default::default() + } +} + +async fn insert_user_with_id(user: &User, pool: &SqlitePool) { + sqlx::query( + "insert into users (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)", + ) + .bind(user.id) + .bind(&user.username) + .bind(&user.displayname) + .bind(&user.email) + .bind(&user.pwhash) + .execute(pool) + .await + .unwrap(); } // https://www.youtube.com/watch?v=29MJySO7PGg diff --git a/src/users.rs b/src/users.rs index f0821d2..82db64b 100644 --- a/src/users.rs +++ b/src/users.rs @@ -5,17 +5,18 @@ use std::{ use axum::{extract::State, http::Request, middleware::Next, response::IntoResponse}; use axum_login::{secrecy::SecretVec, AuthUser}; +use julid::Julid; use serde::{Deserialize, Serialize}; use sqlx::SqlitePool; -use crate::{AuthContext, DbId}; +use crate::AuthContext; const USERNAME_QUERY: &str = "select * from users where username = $1"; const LAST_SEEN_QUERY: &str = "update users set last_seen = (select unixepoch()) where id = $1"; #[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] pub struct User { - pub id: DbId, + pub id: Julid, pub username: String, pub displayname: Option, pub email: Option, @@ -49,8 +50,8 @@ impl Display for User { } } -impl AuthUser for User { - fn get_id(&self) -> DbId { +impl AuthUser for User { + fn get_id(&self) -> Julid { self.id } @@ -75,7 +76,7 @@ impl User { { Ok(_) => {} Err(e) => { - let id = self.id.0.to_string(); + let id = self.id.to_string(); tracing::error!("Could not update last_seen for user {id}; got {e:?}"); } } diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs index df1ef11..1fec9a6 100644 --- a/src/watches/handlers.rs +++ b/src/watches/handlers.rs @@ -3,13 +3,14 @@ use axum::{ http::StatusCode, response::{IntoResponse, Redirect, Response}, }; +use julid::Julid; use serde::Deserialize; -use sqlx::{query, query_as, SqlitePool}; +use sqlx::{query, query_as, query_scalar, SqlitePool}; use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage}; use crate::{ util::{empty_string_as_none, year_to_epoch}, - AuthContext, DbId, MyWatchesPage, ShowKind, Watch, WatchQuest, + AuthContext, MyWatchesPage, ShowKind, Watch, WatchQuest, }; //-************************************************************************ @@ -21,7 +22,7 @@ const GET_SAVED_WATCHES_QUERY: &str = const GET_WATCH_QUERY: &str = "select * from watches where id = $1"; -const ADD_WATCH_QUERY: &str = "insert into watches (id, title, kind, release_date, metadata_url, added_by) values ($1, $2, $3, $4, $5, $6)"; +const ADD_WATCH_QUERY: &str = "insert into watches (title, kind, release_date, metadata_url, added_by, length) values ($1, $2, $3, $4, $5, $6) returning id"; const ADD_WATCH_QUEST_QUERY: &str = "insert into watch_quests (user, watch, public, watched) values ($1, $2, $3, $4)"; @@ -109,6 +110,12 @@ pub async fn get_add_new_watch(auth: AuthContext) -> impl IntoResponse { } } +struct QuestQuest { + pub user: Julid, + pub is_public: bool, + pub already_watched: bool, +} + /// Add a Watch to your watchlist (side effects system-add) pub async fn post_add_new_watch( auth: AuthContext, @@ -117,25 +124,22 @@ pub async fn post_add_new_watch( ) -> Result { if let Some(user) = auth.current_user { { - let watch_id = DbId::new(); let release_date = year_to_epoch(form.year.as_deref()); let watch = Watch { - id: watch_id, title: form.title, kind: form.kind, metadata_url: form.metadata_url, - length: None, release_date, added_by: user.id, + ..Default::default() }; - let quest = WatchQuest { + let quest = QuestQuest { user: user.id, - watch: watch_id, is_public: !form.private, already_watched: form.watched_already, }; - add_new_watch_impl(&pool, &watch, Some(quest)).await?; + let watch_id = add_new_watch_impl(&pool, &watch, Some(quest)).await?; let location = format!("/watch/{watch_id}"); Ok(Redirect::to(&location)) @@ -145,23 +149,23 @@ pub async fn post_add_new_watch( } } -pub(crate) async fn add_new_watch_impl( +async fn add_new_watch_impl( db_pool: &SqlitePool, watch: &Watch, - quest: Option, -) -> Result<(), WatchAddError> { + quest: Option, +) -> Result { let mut tx = db_pool .begin() .await .map_err(|_| WatchAddErrorKind::UnknownDBError)?; - query(ADD_WATCH_QUERY) - .bind(watch.id) + let watch_id: Julid = query_scalar(ADD_WATCH_QUERY) .bind(&watch.title) .bind(watch.kind) .bind(watch.release_date) .bind(&watch.metadata_url) .bind(watch.added_by) - .execute(&mut *tx) + .bind(watch.length) + .fetch_one(&mut *tx) .await .map_err(|err| { tracing::error!("Got error: {err}"); @@ -171,7 +175,7 @@ pub(crate) async fn add_new_watch_impl( if let Some(quest) = quest { query(ADD_WATCH_QUEST_QUERY) .bind(quest.user) - .bind(quest.watch) + .bind(watch_id) .bind(quest.is_public) .bind(quest.already_watched) .execute(&mut *tx) @@ -186,7 +190,7 @@ pub(crate) async fn add_new_watch_impl( WatchAddErrorKind::UnknownDBError })?; - Ok(()) + Ok(watch_id) } /// Add a Watch to your watchlist by selecting it with a checkbox @@ -224,7 +228,7 @@ pub async fn get_watch( "".to_string() }; let id = id.trim(); - let id = DbId::from_string(id).unwrap_or_default(); + let id = Julid::from_string(id).unwrap_or_default(); let q = query_as(GET_WATCH_QUERY).bind(id); let watch: Option = q.fetch_one(&pool).await.ok(); diff --git a/src/watches/mod.rs b/src/watches/mod.rs index 8d5ee3b..c3e52d0 100644 --- a/src/watches/mod.rs +++ b/src/watches/mod.rs @@ -1,7 +1,6 @@ +use julid::Julid; use serde::{Deserialize, Serialize}; -use crate::DbId; - pub mod handlers; pub mod templates; @@ -69,13 +68,13 @@ impl From for ShowKind { sqlx::FromRow, )] pub struct Watch { - pub id: DbId, + pub id: Julid, pub title: String, pub kind: ShowKind, pub metadata_url: Option, pub length: Option, pub release_date: Option, - pub added_by: DbId, // this shouldn't be exposed to randos in the application + pub added_by: Julid, // this shouldn't be exposed to randos in the application } impl Watch { @@ -95,14 +94,14 @@ impl Watch { //-************************************************************************ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WatchQuest { - pub user: DbId, - pub watch: DbId, + pub user: Julid, + pub watch: Julid, pub is_public: bool, pub already_watched: bool, } impl WatchQuest { - pub fn id(&self) -> (DbId, DbId) { + pub fn id(&self) -> (Julid, Julid) { (self.user, self.watch) } }