finish the migration to julids

This commit is contained in:
Joe Ardent 2023-07-28 16:15:27 -07:00
parent 7aefedf993
commit b8481dc106
13 changed files with 236 additions and 414 deletions

289
Cargo.lock generated
View file

@ -234,17 +234,6 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "auto-future" name = "auto-future"
version = "1.0.0" version = "1.0.0"
@ -450,29 +439,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -554,15 +520,6 @@ version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.10"
@ -585,35 +542,10 @@ dependencies = [
"iana-time-zone", "iana-time-zone",
"num-traits", "num-traits",
"serde", "serde",
"time 0.1.45",
"winapi", "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]] [[package]]
name = "clap" name = "clap"
version = "4.3.19" version = "4.3.19"
@ -633,7 +565,7 @@ checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex 0.5.0", "clap_lex",
"strsim", "strsim",
"unicase", "unicase",
"unicode-width", "unicode-width",
@ -651,15 +583,6 @@ dependencies = [
"syn 2.0.27", "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]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.5.0" version = "0.5.0"
@ -696,7 +619,7 @@ dependencies = [
"rand", "rand",
"sha2 0.10.7", "sha2 0.10.7",
"subtle", "subtle",
"time", "time 0.3.23",
"version_check", "version_check",
] ]
@ -832,19 +755,6 @@ dependencies = [
"serde", "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]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -1040,7 +950,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -1049,18 +959,6 @@ version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 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]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.0" version = "0.14.0"
@ -1077,7 +975,7 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f"
dependencies = [ dependencies = [
"hashbrown 0.14.0", "hashbrown",
] ]
[[package]] [[package]]
@ -1114,15 +1012,6 @@ dependencies = [
"unicode-segmentation", "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]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.2" version = "0.3.2"
@ -1221,12 +1110,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.27" version = "0.14.27"
@ -1283,16 +1166,6 @@ dependencies = [
"unicode-normalization", "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]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.0.0" version = "2.0.0"
@ -1300,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.14.0", "hashbrown",
] ]
[[package]] [[package]]
@ -1309,7 +1182,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi 0.3.2", "hermit-abi",
"rustix", "rustix",
"windows-sys", "windows-sys",
] ]
@ -1340,11 +1213,13 @@ dependencies = [
[[package]] [[package]]
name = "julid-rs" name = "julid-rs"
version = "0.1.6" version = "1.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2081aa5e2e85795e4e2b36e95df914655cf281d0e3eafb7e538754d704ac18e3"
dependencies = [ dependencies = [
"chrono",
"rand", "rand",
"serde", "serde",
"sqlite-loadable",
"sqlx", "sqlx",
] ]
@ -1368,28 +1243,12 @@ dependencies = [
"spin 0.5.2", "spin 0.5.2",
] ]
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 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]] [[package]]
name = "libm" name = "libm"
version = "0.2.7" version = "0.2.7"
@ -1497,7 +1356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys",
] ]
@ -1575,7 +1434,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [ dependencies = [
"hermit-abi 0.3.2", "hermit-abi",
"libc", "libc",
] ]
@ -1608,12 +1467,6 @@ dependencies = [
"syn 2.0.27", "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]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -1660,12 +1513,6 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@ -1909,12 +1756,6 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.4" version = "0.38.4"
@ -1997,18 +1838,18 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.176" version = "1.0.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76dc28c9523c5d70816e393136b86d48909cfb27cecaa902d338c19ed47164dc" checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.176" version = "1.0.177"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e7b8c5dc823e3b90651ff1d3808419cd14e5ad76de04feaf37da114e7a306f" checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2101,12 +1942,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -2187,40 +2022,6 @@ dependencies = [
"unicode_categories", "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]] [[package]]
name = "sqlx" name = "sqlx"
version = "0.7.1" version = "0.7.1"
@ -2256,7 +2057,7 @@ dependencies = [
"futures-util", "futures-util",
"hashlink", "hashlink",
"hex", "hex",
"indexmap 2.0.0", "indexmap",
"log", "log",
"memchr", "memchr",
"once_cell", "once_cell",
@ -2481,21 +2282,6 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.44"
@ -2526,6 +2312,17 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "time" name = "time"
version = "0.3.23" version = "0.3.23"
@ -2844,6 +2641,12 @@ dependencies = [
"try-lock", "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]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -2936,7 +2739,7 @@ dependencies = [
"axum-macros", "axum-macros",
"axum-test", "axum-test",
"chrono", "chrono",
"clap 4.3.19", "clap",
"julid-rs", "julid-rs",
"justerror", "justerror",
"optional_optional_user", "optional_optional_user",
@ -2957,17 +2760,6 @@ dependencies = [
"unicode-segmentation", "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]] [[package]]
name = "whoami" name = "whoami"
version = "1.4.1" version = "1.4.1"
@ -2990,15 +2782,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 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]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"

View file

@ -18,7 +18,7 @@ axum-login = { git = "https://github.com/nebkor/axum-login", branch = "sqlx-0.7"
axum-macros = "0.3" axum-macros = "0.3"
chrono = { version = "0.4", default-features = false, features = ["std", "clock"] } chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }
clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] } clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] }
julid-rs = { path = "../julid" } julid-rs = "1"
justerror = "1" justerror = "1"
password-hash = { version = "0.5", features = ["std", "getrandom"] } password-hash = { version = "0.5", features = ["std", "getrandom"] }
rand = "0.8" rand = "0.8"

View file

@ -1,18 +1,29 @@
use std::{ffi::OsString, time::Duration}; use std::{ffi::OsString, time::Duration};
use clap::Parser; 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}; use what2watch::{get_db_pool, import_utils::add_omega_watches};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct Cli { struct Cli {
#[clap(long, short)] #[clap(long, short)]
pub db_path: OsString, 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() { fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
let path = cli.db_path; let path = cli.db_path;
let num = cli.number;
let opts = SqliteConnectOptions::new().filename(path).read_only(true); let opts = SqliteConnectOptions::new().filename(path).read_only(true);
let movie_db = { let movie_db = {
@ -31,26 +42,45 @@ fn main() {
let w2w_db = get_db_pool(); let w2w_db = get_db_pool();
let start = std::time::Instant::now(); let dur = {
let rows = {
let rt = tokio::runtime::Builder::new_multi_thread() let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.build() .build()
.unwrap(); .unwrap();
rt.block_on(async { rt.block_on(async {
add_omega_watches(&w2w_db, &movie_db).await.unwrap(); let dur = add_omega_watches(&w2w_db, &movie_db, num).await.unwrap();
let rows: i32 = sqlx::query_scalar("select count(*) from watches")
.fetch_one(&w2w_db)
.await
.unwrap();
w2w_db.close().await; w2w_db.close().await;
rows dur
}) })
}; };
let end = std::time::Instant::now(); let w2w_db = get_db_pool();
let dur = (end - start).as_secs_f32(); let movies: Vec<(String, f64, i64)> = {
println!("Added {rows} movies in {dur} seconds"); 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()
);
} }

View file

@ -1,9 +1,11 @@
use std::{ffi::OsString, time::Duration}; use std::{ffi::OsString, time::Duration};
use clap::Parser; use clap::Parser;
use julid::Julid;
use rand::{seq::SliceRandom, thread_rng, Rng}; use rand::{seq::SliceRandom, thread_rng, Rng};
use rand_distr::Normal; use rand_distr::Normal;
use sqlx::{ use sqlx::{
query_as,
sqlite::{SqliteConnectOptions, SqlitePoolOptions}, sqlite::{SqliteConnectOptions, SqlitePoolOptions},
SqlitePool, SqlitePool,
}; };
@ -12,7 +14,7 @@ use tokio_retry::Retry;
use what2watch::{ use what2watch::{
get_db_pool, get_db_pool,
import_utils::{add_omega_watches, add_users, add_watch_quests}, import_utils::{add_omega_watches, add_users, add_watch_quests},
DbId, User, WatchQuest, User, Watch, WatchQuest,
}; };
fn main() { fn main() {
@ -29,7 +31,7 @@ fn main() {
let words = std::fs::read_to_string(dict).expect("tried to open {dict:?}"); let words = std::fs::read_to_string(dict).expect("tried to open {dict:?}");
let words: Vec<&str> = words.split('\n').collect(); 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() let rt = runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
@ -47,7 +49,14 @@ fn main() {
let users = &rt.block_on(gen_users(num_users, &words, &w2w_db)); 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<Watch> = rt
.block_on(query_as("select * from watches").fetch_all(&w2w_db))
.unwrap();
let movies: Vec<Julid> = movies.into_iter().map(|m| m.id).collect();
let rng = &mut thread_rng(); let rng = &mut thread_rng();
@ -55,7 +64,7 @@ fn main() {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
rt.block_on(async { rt.block_on(async {
for &user in users { 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 let rows: i32 = rt
@ -70,7 +79,7 @@ fn main() {
//-************************************************************************ //-************************************************************************
// add the users // add the users
//-************************************************************************ //-************************************************************************
async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec<DbId> { async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec<Julid> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let rng = &mut rng; let rng = &mut rng;
let range = 0usize..(words.len()); let range = 0usize..(words.len());
@ -88,9 +97,9 @@ async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec<DbId> {
let username = format!("{n1}_{n2}{nn}"); let username = format!("{n1}_{n2}{nn}");
let displayname = Some(format!("{n1} {n2}")); let displayname = Some(format!("{n1} {n2}"));
let email = Some(format!("{username}@{email_domain}")); let email = Some(format!("{username}@{email_domain}"));
let id = DbId::new();
let user = User { let user = User {
id, id: 0.into(),
username, username,
displayname, displayname,
email, email,
@ -108,8 +117,8 @@ async fn gen_users(num: usize, words: &[&str], pool: &SqlitePool) -> Vec<DbId> {
// batch add quests // batch add quests
//-************************************************************************ //-************************************************************************
async fn add_quests<R: Rng>( async fn add_quests<R: Rng>(
user: DbId, user: Julid,
movies: &[DbId], movies: &[Julid],
w2w_db: &SqlitePool, w2w_db: &SqlitePool,
rng: &mut R, rng: &mut R,
normal: Normal<f32>, normal: Normal<f32>,

View file

@ -5,6 +5,7 @@ use axum_login::{
axum_sessions::{PersistencePolicy, SessionLayer}, axum_sessions::{PersistencePolicy, SessionLayer},
AuthLayer, SqliteStore, SqlxStore, AuthLayer, SqliteStore, SqlxStore,
}; };
use julid::Julid;
use session_store::SqliteSessionStore; use session_store::SqliteSessionStore;
use sqlx::{ use sqlx::{
migrate::Migrator, migrate::Migrator,
@ -12,7 +13,7 @@ use sqlx::{
SqlitePool, SqlitePool,
}; };
use crate::{DbId, User}; use crate::User;
const MAX_CONNS: u32 = 200; const MAX_CONNS: u32 = 200;
const MIN_CONNS: u32 = 5; const MIN_CONNS: u32 = 5;
@ -111,7 +112,7 @@ pub async fn session_layer(pool: SqlitePool, secret: &[u8]) -> SessionLayer<Sqli
pub async fn auth_layer( pub async fn auth_layer(
pool: SqlitePool, pool: SqlitePool,
secret: &[u8], secret: &[u8],
) -> AuthLayer<SqlxStore<SqlitePool, User>, DbId, User> { ) -> AuthLayer<SqlxStore<SqlitePool, User>, Julid, User> {
const QUERY: &str = "select * from users where id = $1"; const QUERY: &str = "select * from users where id = $1";
let store = SqliteStore::<User>::new(pool).with_query(QUERY); let store = SqliteStore::<User>::new(pool).with_query(QUERY);
AuthLayer::new(store, secret) AuthLayer::new(store, secret)
@ -449,14 +450,14 @@ mod session_store {
let mut session = Session::new(); let mut session = Session::new();
session.expire_in(Duration::from_secs(10)); session.expire_in(Duration::from_secs(10));
let original_id = session.id().to_owned(); 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 cookie_value = store.store_session(session).await?.unwrap();
let mut session = store.load_session(cookie_value.clone()).await?.unwrap(); let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
assert_eq!(session.expiry().unwrap(), &original_expires); assert_eq!(session.expiry().unwrap(), &original_expires);
session.expire_in(Duration::from_secs(20)); 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?; store.store_session(session).await?;
let session = store.load_session(cookie_value.clone()).await?.unwrap(); let session = store.load_session(cookie_value.clone()).await?.unwrap();

View file

@ -1,16 +1,18 @@
use std::time::{Duration, Instant};
use julid::Julid;
use sqlx::{query_scalar, SqlitePool}; 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 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 // 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; const BULK_INSERT: usize = 2_000;
#[derive(Debug, sqlx::FromRow, Clone)] #[derive(Debug, sqlx::FromRow, Clone)]
@ -23,13 +25,13 @@ pub struct ImportMovieOmega {
impl From<ImportMovieOmega> for Watch { impl From<ImportMovieOmega> for Watch {
fn from(value: ImportMovieOmega) -> Self { fn from(value: ImportMovieOmega) -> Self {
Watch { Watch {
id: Julid::omega(), // this is ignored by the inserter
title: value.title, title: value.title,
release_date: year_to_epoch(value.year.as_deref()), release_date: year_to_epoch(value.year.as_deref()),
length: value.length.and_then(|v| v.parse::<i64>().ok()), length: value.length.and_then(|v| v.parse::<i64>().ok()),
id: DbId::new(),
kind: ShowKind::Movie, kind: ShowKind::Movie,
metadata_url: None, metadata_url: None,
added_by: OMEGA_ID.into(), added_by: OMEGA_ID,
} }
} }
} }
@ -37,13 +39,13 @@ impl From<ImportMovieOmega> for Watch {
impl From<&ImportMovieOmega> for Watch { impl From<&ImportMovieOmega> for Watch {
fn from(value: &ImportMovieOmega) -> Self { fn from(value: &ImportMovieOmega) -> Self {
Watch { Watch {
id: Julid::omega(),
title: value.title.to_string(), title: value.title.to_string(),
release_date: year_to_epoch(value.year.as_deref()), release_date: year_to_epoch(value.year.as_deref()),
length: value.length.as_ref().and_then(|v| v.parse::<i64>().ok()), length: value.length.as_ref().and_then(|v| v.parse::<i64>().ok()),
id: DbId::new(),
kind: ShowKind::Movie, kind: ShowKind::Movie,
metadata_url: None, 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(()) Ok(())
} }
pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result<(), ()> { pub async fn add_users(db_pool: &SqlitePool, users: &[User]) -> Result<Duration, ()> {
let mut builder = 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| { builder.push_values(users.iter(), |mut b, user| {
b.push_bind(user.id) b.push_bind(&user.username)
.push_bind(&user.username)
.push_bind(&user.displayname) .push_bind(&user.displayname)
.push_bind(&user.email) .push_bind(&user.email)
.push_bind(&user.pwhash); .push_bind(&user.pwhash);
}); });
let q = builder.build(); let q = builder.build();
q.execute(db_pool).await.map_err(|_| ())?; q.execute(db_pool).await.map_err(|_| ())?;
Ok(()) let end = Instant::now();
let dur = end - start;
Ok(dur)
} }
pub async fn add_omega_watches( pub async fn add_omega_watches(
w2w_db: &SqlitePool, w2w_db: &SqlitePool,
movie_db: &SqlitePool, movie_db: &SqlitePool,
) -> Result<Vec<DbId>, ()> { num: u32,
ensure_omega(w2w_db).await; ) -> Result<Duration, ()> {
let omega = ensure_omega(w2w_db).await;
let movies: Vec<ImportMovieOmega> = sqlx::query_as(MOVIE_QUERY) let movies: Vec<ImportMovieOmega> = sqlx::query_as(MOVIE_QUERY)
.bind(num)
.fetch_all(movie_db) .fetch_all(movie_db)
.await .await
.unwrap(); .unwrap();
let mut ids = Vec::with_capacity(10_000); let start = Instant::now();
let omega: DbId = OMEGA_ID.into();
for movies in movies.as_slice().chunks(BULK_INSERT) { for movies in movies.as_slice().chunks(BULK_INSERT) {
let mut builder = sqlx::QueryBuilder::new( 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| { builder.push_values(movies, |mut b, movie| {
let id = DbId::new();
ids.push(id);
let title = &movie.title; let title = &movie.title;
b.push_bind(ShowKind::Movie)
b.push_bind(id)
.push_bind(ShowKind::Movie)
.push_bind(title) .push_bind(title)
.push_bind(movie.length.as_ref().and_then(|l| l.parse::<i64>().ok())) .push_bind(movie.length.as_ref().and_then(|l| l.parse::<i64>().ok()))
.push_bind(year_to_epoch(movie.year.as_deref())) .push_bind(year_to_epoch(movie.year.as_deref()))
.push_bind(omega); .push_bind(omega);
}); });
let q = builder.build(); let q = builder.build();
q.execute(w2w_db).await.map_err(|_| ())?; 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 { if !check_omega_exists(db_pool).await {
let omega = User { sqlx::query("insert into users (id, username, pwhash) values (?, 'the omega user', 'you shall not password')").bind(Julid::omega()).execute(db_pool).await.unwrap();
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();
} }
OMEGA_ID.into() OMEGA_ID
} }
async fn check_omega_exists(db_pool: &SqlitePool) -> bool { 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) let count = query_scalar(USER_EXISTS_QUERY)
.bind(id) .bind(id)
.fetch_one(db_pool) .fetch_one(db_pool)
@ -165,7 +159,8 @@ mod test {
rt.block_on(async { rt.block_on(async {
assert!(!check_omega_exists(&p).await); assert!(!check_omega_exists(&p).await);
ensure_omega(&p).await; ensure_omega(&p).await;
assert!(check_omega_exists(&p).await);
}); });
assert!(rt.block_on(check_omega_exists(&p)));
} }
} }

View file

@ -30,8 +30,8 @@ use optional_optional_user::OptionalOptionalUser;
use templates::*; use templates::*;
use watches::templates::*; use watches::templates::*;
pub type DbId = julid::Julid; type AuthContext =
type AuthContext = axum_login::extractors::AuthContext<DbId, User, axum_login::SqliteStore<User>>; axum_login::extractors::AuthContext<julid::Julid, User, axum_login::SqliteStore<User>>;
/// Returns the router to be used as a service or test object, you do you. /// 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<axum::Router> { pub async fn app(db_pool: sqlx::SqlitePool, secret: &[u8]) -> IntoMakeService<axum::Router> {

View file

@ -114,7 +114,8 @@ mod test {
use crate::{ use crate::{
get_db_pool, get_db_pool,
templates::{LoginPage, LogoutPage, LogoutSuccessPage, MainPage}, 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"; const LOGIN_FORM: &str = "username=test_user&password=a";
@ -257,10 +258,9 @@ mod test {
.await; .await;
assert_eq!(resp.status_code(), 303); assert_eq!(resp.status_code(), 303);
let logged_in = MainPage { let user = User::try_get("test_user", &db).await.unwrap();
user: Some(get_test_user()),
} let logged_in = MainPage { user: Some(user) }.to_string();
.to_string();
let main_page = s.get("/").await; let main_page = s.get("/").await;
let body = std::str::from_utf8(main_page.bytes()).unwrap(); let body = std::str::from_utf8(main_page.bytes()).unwrap();

View file

@ -7,14 +7,15 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use julid::Julid;
use serde::Deserialize; use serde::Deserialize;
use sqlx::{query_as, SqlitePool}; use sqlx::{query_as, SqlitePool};
use unicode_segmentation::UnicodeSegmentation; 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 = 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"; 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 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?; tracing::debug!("created {user:?}");
let now = user.id.timestamp();
tracing::debug!("created {user:?} at {now:?}");
let id = user.id.as_string(); let id = user.id.as_string();
let location = format!("/signup_success/{id}"); let location = format!("/signup_success/{id}");
@ -126,7 +125,7 @@ pub async fn get_signup_success(
State(pool): State<SqlitePool>, State(pool): State<SqlitePool>,
) -> Response { ) -> Response {
let id = id.trim(); 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 = { let user: User = {
query_as(ID_QUERY) query_as(ID_QUERY)
.bind(id) .bind(id)
@ -156,7 +155,6 @@ pub(crate) async fn create_user(
email: &Option<String>, email: &Option<String>,
password: &[u8], password: &[u8],
pool: &SqlitePool, pool: &SqlitePool,
id: DbId,
) -> Result<User, CreateUserError> { ) -> Result<User, CreateUserError> {
// Argon2 with default params (Argon2id v19) // Argon2 with default params (Argon2id v19)
let argon2 = Argon2::default(); let argon2 = Argon2::default();
@ -166,27 +164,16 @@ pub(crate) async fn create_user(
.unwrap() // safe to unwrap, we know the salt is valid .unwrap() // safe to unwrap, we know the salt is valid
.to_string(); .to_string();
let query = sqlx::query(CREATE_QUERY) let res = sqlx::query_as(CREATE_QUERY)
.bind(id)
.bind(username) .bind(username)
.bind(displayname) .bind(displayname)
.bind(email) .bind(email)
.bind(&pwhash); .bind(&pwhash)
.fetch_one(pool)
let res = query.execute(pool).await; .await;
match res { match res {
Ok(_) => { Ok(user) => Ok(user),
let user = User {
id,
username: username.to_string(),
displayname: displayname.to_owned(),
email: email.to_owned(),
last_seen: None,
pwhash,
};
Ok(user)
}
Err(sqlx::Error::Database(db)) => { Err(sqlx::Error::Database(db)) => {
if let Some(exit) = db.code() { if let Some(exit) = db.code() {
let exit = exit.parse().unwrap_or(0u32); let exit = exit.parse().unwrap_or(0u32);
@ -216,11 +203,11 @@ mod test {
use crate::{ use crate::{
db::get_db_pool, db::get_db_pool,
templates::{SignupPage, SignupSuccessPage}, 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, 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] #[test]
fn post_create_user() { fn post_create_user() {
@ -265,10 +252,20 @@ mod test {
let rt = Runtime::new().unwrap(); let rt = Runtime::new().unwrap();
rt.block_on(async { rt.block_on(async {
let server = server_with_pool(&pool).await; let server = server_with_pool(&pool).await;
let body = massage(GOOD_FORM);
let user = get_test_user(); let resp = server
let id = user.id.to_string(); .post("/signup")
dbg!(&id); .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}"); let path = format!("/signup_success/{id}");
@ -288,15 +285,15 @@ mod test {
// various ways to fuck up signup // various ways to fuck up signup
const PASSWORD_MISMATCH_FORM: &str = 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 = const PASSWORD_SHORT_FORM: &str =
"username=bad_user&displayname=Test+User&password=a&pw_verify=a"; "username=bad_user&displayname=Bad+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"; 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 = 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 = const USERNAME_LONG_FORM: &str =
"username=bad_user12345678901234567890&displayname=Test+User&password=aaaa&pw_verify=aaaa"; "username=bad_user12345678901234567890&displayname=Bad+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"; 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] #[test]
fn password_mismatch() { fn password_mismatch() {

View file

@ -1,28 +1,18 @@
use axum::body::Bytes; use axum::body::Bytes;
use axum_test::{TestServer, TestServerConfig}; use axum_test::{TestServer, TestServerConfig};
use julid::Julid;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::{DbId, User}; use crate::User;
pub const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded"; 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 { pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
let secret = [0u8; 64]; let secret = [0u8; 64];
let user = get_test_user(); 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") let r: i32 = sqlx::query_scalar("select count(*) from users")
.fetch_one(pool) .fetch_one(pool)
.await .await
@ -38,8 +28,21 @@ pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
TestServer::new_with_config(app, config).unwrap() TestServer::new_with_config(app, config).unwrap()
} }
pub async fn insert_user(user: &User, pool: &SqlitePool) { fn get_test_user() -> User {
sqlx::query(crate::signup::CREATE_QUERY) 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.id)
.bind(&user.username) .bind(&user.username)
.bind(&user.displayname) .bind(&user.displayname)

View file

@ -5,17 +5,18 @@ use std::{
use axum::{extract::State, http::Request, middleware::Next, response::IntoResponse}; use axum::{extract::State, http::Request, middleware::Next, response::IntoResponse};
use axum_login::{secrecy::SecretVec, AuthUser}; use axum_login::{secrecy::SecretVec, AuthUser};
use julid::Julid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::{AuthContext, DbId}; use crate::AuthContext;
const USERNAME_QUERY: &str = "select * from users where username = $1"; 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"; const LAST_SEEN_QUERY: &str = "update users set last_seen = (select unixepoch()) where id = $1";
#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)] #[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
pub struct User { pub struct User {
pub id: DbId, pub id: Julid,
pub username: String, pub username: String,
pub displayname: Option<String>, pub displayname: Option<String>,
pub email: Option<String>, pub email: Option<String>,
@ -49,8 +50,8 @@ impl Display for User {
} }
} }
impl AuthUser<DbId> for User { impl AuthUser<Julid> for User {
fn get_id(&self) -> DbId { fn get_id(&self) -> Julid {
self.id self.id
} }
@ -75,7 +76,7 @@ impl User {
{ {
Ok(_) => {} Ok(_) => {}
Err(e) => { 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:?}"); tracing::error!("Could not update last_seen for user {id}; got {e:?}");
} }
} }

View file

@ -3,13 +3,14 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Redirect, Response}, response::{IntoResponse, Redirect, Response},
}; };
use julid::Julid;
use serde::Deserialize; use serde::Deserialize;
use sqlx::{query, query_as, SqlitePool}; use sqlx::{query, query_as, query_scalar, SqlitePool};
use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage}; use super::templates::{AddNewWatchPage, GetWatchPage, SearchWatchesPage};
use crate::{ use crate::{
util::{empty_string_as_none, year_to_epoch}, 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 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 = const ADD_WATCH_QUEST_QUERY: &str =
"insert into watch_quests (user, watch, public, watched) values ($1, $2, $3, $4)"; "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) /// Add a Watch to your watchlist (side effects system-add)
pub async fn post_add_new_watch( pub async fn post_add_new_watch(
auth: AuthContext, auth: AuthContext,
@ -117,25 +124,22 @@ pub async fn post_add_new_watch(
) -> Result<impl IntoResponse, WatchAddError> { ) -> Result<impl IntoResponse, WatchAddError> {
if let Some(user) = auth.current_user { if let Some(user) = auth.current_user {
{ {
let watch_id = DbId::new();
let release_date = year_to_epoch(form.year.as_deref()); let release_date = year_to_epoch(form.year.as_deref());
let watch = Watch { let watch = Watch {
id: watch_id,
title: form.title, title: form.title,
kind: form.kind, kind: form.kind,
metadata_url: form.metadata_url, metadata_url: form.metadata_url,
length: None,
release_date, release_date,
added_by: user.id, added_by: user.id,
..Default::default()
}; };
let quest = WatchQuest { let quest = QuestQuest {
user: user.id, user: user.id,
watch: watch_id,
is_public: !form.private, is_public: !form.private,
already_watched: form.watched_already, 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}"); let location = format!("/watch/{watch_id}");
Ok(Redirect::to(&location)) 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, db_pool: &SqlitePool,
watch: &Watch, watch: &Watch,
quest: Option<WatchQuest>, quest: Option<QuestQuest>,
) -> Result<(), WatchAddError> { ) -> Result<Julid, WatchAddError> {
let mut tx = db_pool let mut tx = db_pool
.begin() .begin()
.await .await
.map_err(|_| WatchAddErrorKind::UnknownDBError)?; .map_err(|_| WatchAddErrorKind::UnknownDBError)?;
query(ADD_WATCH_QUERY) let watch_id: Julid = query_scalar(ADD_WATCH_QUERY)
.bind(watch.id)
.bind(&watch.title) .bind(&watch.title)
.bind(watch.kind) .bind(watch.kind)
.bind(watch.release_date) .bind(watch.release_date)
.bind(&watch.metadata_url) .bind(&watch.metadata_url)
.bind(watch.added_by) .bind(watch.added_by)
.execute(&mut *tx) .bind(watch.length)
.fetch_one(&mut *tx)
.await .await
.map_err(|err| { .map_err(|err| {
tracing::error!("Got error: {err}"); tracing::error!("Got error: {err}");
@ -171,7 +175,7 @@ pub(crate) async fn add_new_watch_impl(
if let Some(quest) = quest { if let Some(quest) = quest {
query(ADD_WATCH_QUEST_QUERY) query(ADD_WATCH_QUEST_QUERY)
.bind(quest.user) .bind(quest.user)
.bind(quest.watch) .bind(watch_id)
.bind(quest.is_public) .bind(quest.is_public)
.bind(quest.already_watched) .bind(quest.already_watched)
.execute(&mut *tx) .execute(&mut *tx)
@ -186,7 +190,7 @@ pub(crate) async fn add_new_watch_impl(
WatchAddErrorKind::UnknownDBError WatchAddErrorKind::UnknownDBError
})?; })?;
Ok(()) Ok(watch_id)
} }
/// Add a Watch to your watchlist by selecting it with a checkbox /// Add a Watch to your watchlist by selecting it with a checkbox
@ -224,7 +228,7 @@ pub async fn get_watch(
"".to_string() "".to_string()
}; };
let id = id.trim(); 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 q = query_as(GET_WATCH_QUERY).bind(id);
let watch: Option<Watch> = q.fetch_one(&pool).await.ok(); let watch: Option<Watch> = q.fetch_one(&pool).await.ok();

View file

@ -1,7 +1,6 @@
use julid::Julid;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::DbId;
pub mod handlers; pub mod handlers;
pub mod templates; pub mod templates;
@ -69,13 +68,13 @@ impl From<i64> for ShowKind {
sqlx::FromRow, sqlx::FromRow,
)] )]
pub struct Watch { pub struct Watch {
pub id: DbId, pub id: Julid,
pub title: String, pub title: String,
pub kind: ShowKind, pub kind: ShowKind,
pub metadata_url: Option<String>, pub metadata_url: Option<String>,
pub length: Option<i64>, pub length: Option<i64>,
pub release_date: Option<i64>, pub release_date: Option<i64>,
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 { impl Watch {
@ -95,14 +94,14 @@ impl Watch {
//-************************************************************************ //-************************************************************************
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WatchQuest { pub struct WatchQuest {
pub user: DbId, pub user: Julid,
pub watch: DbId, pub watch: Julid,
pub is_public: bool, pub is_public: bool,
pub already_watched: bool, pub already_watched: bool,
} }
impl WatchQuest { impl WatchQuest {
pub fn id(&self) -> (DbId, DbId) { pub fn id(&self) -> (Julid, Julid) {
(self.user, self.watch) (self.user, self.watch)
} }
} }