diff options
| author | Daniel Hader <[email protected]> | 2026-05-11 19:28:00 -0500 |
|---|---|---|
| committer | Daniel Hader <[email protected]> | 2026-05-11 19:28:00 -0500 |
| commit | 36fad793c3be58b220ae319a45c8cd8afbae09fa (patch) | |
| tree | 0f072c9f3dd45b4295ded00d8a7f2c318238b688 | |
| parent | a005add5513182abb0b1230cf514c7a45b290e4b (diff) | |
user route and database pooling using r2d2
| -rw-r--r-- | Cargo.lock | 654 | ||||
| -rw-r--r-- | Cargo.toml | 5 | ||||
| -rw-r--r-- | src/auth.rs | 31 | ||||
| -rw-r--r-- | src/database.rs | 6 | ||||
| -rw-r--r-- | src/database/database.rs | 158 | ||||
| -rw-r--r-- | src/database/problem.rs | 3 | ||||
| -rw-r--r-- | src/database/sql/fetch_user_by_email.sql | 1 | ||||
| -rw-r--r-- | src/database/sql/fetch_user_by_username.sql | 1 | ||||
| -rw-r--r-- | src/database/sql/initialize.sql | 2 | ||||
| -rw-r--r-- | src/database/sql/insert_user.sql | 1 | ||||
| -rw-r--r-- | src/database/user.rs | 17 | ||||
| -rw-r--r-- | src/main.rs | 24 | ||||
| -rw-r--r-- | src/routes.rs | 4 | ||||
| -rw-r--r-- | src/routes/errors.rs | 26 | ||||
| -rw-r--r-- | src/routes/problem.rs (renamed from src/routes/problems.rs) | 0 | ||||
| -rw-r--r-- | src/routes/user.rs | 49 |
16 files changed, 935 insertions, 47 deletions
@@ -3,12 +3,36 @@ version = 4 [[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + +[[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] name = "axum" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -61,12 +85,42 @@ dependencies = [ ] [[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -95,17 +149,85 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] name = "code_golf_server" version = "0.1.0" dependencies = [ + "argon2", "axum", - "rusqlite", + "jsonwebtoken", + "r2d2", + "r2d2_sqlite", "serde", "serde_json", "tokio", ] [[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -125,6 +247,12 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" @@ -172,24 +300,80 @@ dependencies = [ ] [[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "foldhash", + "foldhash 0.2.0", ] [[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] name = "hashlink" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "http" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -270,6 +454,24 @@ dependencies = [ ] [[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -282,11 +484,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64", + "getrandom 0.2.17", + "js-sys", + "pem", + "serde", + "serde_json", + "signature", + "simple_asn1", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -303,6 +528,15 @@ dependencies = [ ] [[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -338,12 +572,90 @@ dependencies = [ ] [[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -362,6 +674,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -380,12 +708,75 @@ dependencies = [ ] [[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_sqlite" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a289c0a3bf56505c470efa2366e76010f1d892e2492a2f96b223386d63b7e2" +dependencies = [ + "r2d2", + "rusqlite", + "uuid", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] name = "rsqlite-vfs" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", "thiserror", ] @@ -417,6 +808,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -489,6 +901,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -523,6 +956,12 @@ dependencies = [ ] [[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -560,6 +999,37 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] name = "tokio" version = "1.52.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -633,24 +1103,72 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "rand", + "wasm-bindgen", +] + +[[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] name = "wasm-bindgen" version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -696,6 +1214,40 @@ dependencies = [ ] [[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -711,6 +1263,100 @@ dependencies = [ ] [[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4,8 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] +argon2 = { version = "0.5.3", features = ["std"] } axum = "0.8.9" -rusqlite = "0.39.0" +jsonwebtoken = "10.3.0" +r2d2 = "0.8.10" +r2d2_sqlite = "0.34.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" tokio = { version = "1.52.2", features = ["macros", "rt-multi-thread"] } diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..90ce4a5 --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,31 @@ +use argon2::{Argon2, PasswordHash, PasswordVerifier, password_hash::{ + Error, PasswordHasher, SaltString, rand_core::OsRng +}}; + +pub fn hash_password(password: &str) -> Result<String, Error> { + let argon2 = Argon2::default(); + let salt = SaltString::generate(&mut OsRng); + Ok(argon2.hash_password(password.as_bytes(), &salt)?.to_string()) +} + +pub fn check_password(password: &str, password_hash: &str) -> Result<bool, Error> { + let argon2 = Argon2::default(); + let hash = PasswordHash::new(password_hash)?; + Ok(argon2.verify_password(password.as_bytes(), &hash).is_ok()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_password_hashing() { + + let passwords = vec!["password", "test1", "random"]; + + for password in &passwords { + let hash = hash_password(password).unwrap(); + assert!(check_password(password, &hash).unwrap()); + } + } +} diff --git a/src/database.rs b/src/database.rs index 8853557..3c8afe6 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,2 +1,6 @@ -pub mod database; +mod database; + +pub use database::Database; pub mod problem; +pub mod user; + diff --git a/src/database/database.rs b/src/database/database.rs index 7da66f2..ca0e018 100644 --- a/src/database/database.rs +++ b/src/database/database.rs @@ -1,67 +1,154 @@ use std::path::Path; -use rusqlite::{Connection, Error}; -use crate::database::problem::Problem; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use r2d2_sqlite::rusqlite::OptionalExtension; +use super::problem::Problem; +use super::user::User; + +#[derive(Clone)] pub struct Database { - connection: Connection, + pool: Pool<SqliteConnectionManager> +} + +#[derive(Debug)] +pub enum DatabaseError { + Connection(String), + Query(String), } impl Database { - pub fn new(database_path: impl AsRef<Path>) -> Result<Self, Error> { - let connection = Connection::open(database_path)?; - Ok(Database { - connection, - }) + pub fn new(database_path: impl AsRef<Path>) -> Result<Self, DatabaseError> { + let manager = SqliteConnectionManager::file(database_path); + let pool = Pool::new(manager) + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + Ok(Database { pool }) + } + + pub fn new_in_memory() -> Result<Self, DatabaseError> { + let manager = SqliteConnectionManager::memory(); + let pool = Pool::new(manager) + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + Ok(Database { pool }) + } + + pub fn insert_user(&self, email: &str, username: &str, password_hash: &str) -> Result<User, DatabaseError> { + static QUERY: &str = include_str!("sql/insert_user.sql"); + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + let mut statement = conn.prepare(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; + + Ok(statement + .query_one((email, username, password_hash), |row| { + Ok(User::new( + row.get("id")?, + email.to_owned(), + username.to_owned(), + password_hash.to_owned(), + )) + }) + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) + } + + pub fn fetch_user_by_email(&self, email: &str) -> Result<Option<User>, DatabaseError> { + static QUERY: &str = include_str!("sql/fetch_user_by_email.sql"); + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + let mut statement = conn.prepare(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; + + Ok(statement + .query_one([email], |row| { + Ok(User::new( + row.get("id")?, + row.get("email")?, + row.get("username")?, + row.get("password_hash")?, + )) + }) + .optional() + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) } - pub fn new_in_memory() -> Result<Self, Error> { - let connection = Connection::open_in_memory()?; - Ok(Database { - connection, - }) + pub fn fetch_user_by_username(&self, username: &str) -> Result<Option<User>, DatabaseError> { + static QUERY: &str = include_str!("sql/fetch_user_by_username.sql"); + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + let mut statement = conn.prepare(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; + + Ok(statement + .query_one([username], |row| { + Ok(User::new( + row.get("id")?, + row.get("email")?, + row.get("username")?, + row.get("password_hash")?, + )) + }) + .optional() + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) } - pub fn insert_problem(&self, title: &str, description: &str) -> Result<Problem, Error> { + pub fn insert_problem(&self, title: &str, description: &str) -> Result<Problem, DatabaseError> { static QUERY: &str = include_str!("sql/insert_problem.sql"); - let mut statement = self.connection.prepare(QUERY)?; + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + let mut statement = conn.prepare(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; - let result = statement + Ok(statement .query_one((title, description), |row| { Ok(Problem::new( row.get("id")?, title.to_owned(), description.to_owned(), )) - })?; - - Ok(result) - } - - pub fn delete_problem(&self, id: i64) -> Result<(), Error> { - todo!(); + }) + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) } - pub fn fetch_problems(&self) -> Result<Vec<Problem>, Error> { + pub fn fetch_problems(&self) -> Result<Vec<Problem>, DatabaseError> { static QUERY: &str = include_str!("sql/fetch_problems.sql"); - let mut statement = self.connection.prepare(QUERY)?; + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + let mut statement = conn.prepare(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; - let problems = statement + Ok(statement .query_map([], |row| { Ok(Problem::new( row.get("id")?, row.get("title")?, row.get("description")?, )) - })? - .collect::<Result<Vec<Problem>, _>>()?; - - Ok(problems) + }) + .map_err(|e| DatabaseError::Query(e.to_string()))? + .collect::<Result<Vec<Problem>, _>>() + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) } - - pub fn initialize(&self) -> Result<(), Error> { - static QUERY: &str = include_str!("sql/initialize.sql"); - self.connection.execute_batch(QUERY)?; + + pub fn initialize(&self) -> Result<(), DatabaseError> { + static QUERY: &str = include_str!("sql/initialize.sql"); + let conn = self.pool + .get() + .map_err(|e| DatabaseError::Connection(e.to_string()))?; + + conn.execute_batch(QUERY) + .map_err(|e| DatabaseError::Query(e.to_string()))?; + Ok(()) } } @@ -82,10 +169,11 @@ mod tests { let problem = db.insert_problem(title, description).unwrap(); assert_eq!(problem.title(), title); assert_eq!(problem.description(), description); - + let problems = db.fetch_problems().unwrap(); assert_eq!(problems.len(), 1); assert_eq!(problems[0].title(), title); assert_eq!(problems[0].description(), description); + assert_eq!(problems[0].id(), problem.id()); } } diff --git a/src/database/problem.rs b/src/database/problem.rs index fdbb2b5..c3e04c2 100644 --- a/src/database/problem.rs +++ b/src/database/problem.rs @@ -5,10 +5,11 @@ pub struct Problem { } impl Problem { - pub fn new(id: i64, title: String, description: String) -> Self { + pub(super) fn new(id: i64, title: String, description: String) -> Self { Self { id, title, description } } + pub fn id(&self) -> i64 { self.id } pub fn title(&self) -> &str { &self.title } pub fn description(&self) -> &str { &self.description } } diff --git a/src/database/sql/fetch_user_by_email.sql b/src/database/sql/fetch_user_by_email.sql new file mode 100644 index 0000000..154b5ef --- /dev/null +++ b/src/database/sql/fetch_user_by_email.sql @@ -0,0 +1 @@ +SELECT * FROM user WHERE user.email = ?1; diff --git a/src/database/sql/fetch_user_by_username.sql b/src/database/sql/fetch_user_by_username.sql new file mode 100644 index 0000000..f90d9d1 --- /dev/null +++ b/src/database/sql/fetch_user_by_username.sql @@ -0,0 +1 @@ +SELECT * FROM user WHERE user.username = ?1; diff --git a/src/database/sql/initialize.sql b/src/database/sql/initialize.sql index 3baf4ff..065bac9 100644 --- a/src/database/sql/initialize.sql +++ b/src/database/sql/initialize.sql @@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS problem ( CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL, - username TEXT NOT NULL, + username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL ); diff --git a/src/database/sql/insert_user.sql b/src/database/sql/insert_user.sql new file mode 100644 index 0000000..6b67678 --- /dev/null +++ b/src/database/sql/insert_user.sql @@ -0,0 +1 @@ +INSERT INTO user (email, username, password_hash) VALUES (?1, ?2, ?3) RETURNING id; diff --git a/src/database/user.rs b/src/database/user.rs new file mode 100644 index 0000000..c9aaf51 --- /dev/null +++ b/src/database/user.rs @@ -0,0 +1,17 @@ +pub struct User { + id: i64, + email: String, + username: String, + password_hash: String, +} + +impl User { + pub(super) fn new(id: i64, email: String, username: String, password_hash: String) -> Self { + Self { id, email, username, password_hash } + } + + pub fn id(&self) -> i64 { self.id } + pub fn email(&self) -> &str { &self.email } + pub fn username(&self) -> &str { &self.username } + pub fn password_hash(&self) -> &str { &self.password_hash } +} diff --git a/src/main.rs b/src/main.rs index a143df5..ef762f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,36 @@ mod database; mod routes; +mod auth; use axum::{ - routing::get, + routing::{get, post}, Router, }; -use routes::problems::get_problems; +use routes::problem::get_problems; +use routes::user::create_user; + +use crate::database::Database; + +#[derive(Clone)] +struct AppState { + database: Database, +} #[tokio::main] async fn main() { + + let database = Database::new_in_memory().unwrap(); + database.initialize().unwrap(); + + let state = AppState { + database: database, + }; + let app = Router::new() .route("/", get(|| async {"Hello World!"})) - .route("/problems", get(get_problems)); + .route("/problems", get(get_problems)) + .route("/user", post(create_user).with_state(state)); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); diff --git a/src/routes.rs b/src/routes.rs index c054f87..6d7ac77 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1 +1,3 @@ -pub mod problems; +pub mod problem; +pub mod user; +mod errors; diff --git a/src/routes/errors.rs b/src/routes/errors.rs new file mode 100644 index 0000000..6261d75 --- /dev/null +++ b/src/routes/errors.rs @@ -0,0 +1,26 @@ +use axum::{Json, body::Body, http::{Response, StatusCode}, response::IntoResponse}; +use serde_json::{json}; + +pub enum RouteError { + Internal(String), + UserCreateEmailExists(String), + UserCreateUsernameExists(String), +} + +impl IntoResponse for RouteError { + fn into_response(self) -> Response<Body> { + let (status, message) = match self { + Self::Internal(message) => { + (StatusCode::INTERNAL_SERVER_ERROR, message) + }, + RouteError::UserCreateEmailExists(email) => { + (StatusCode::BAD_REQUEST, format!("user with email \"{}\" already exists", email)) + }, + RouteError::UserCreateUsernameExists(username) => { + (StatusCode::BAD_REQUEST, format!("user with username \"{}\" already exists", username)) + } + }; + + (status, Json(json!({"error": message}))).into_response() + } +} diff --git a/src/routes/problems.rs b/src/routes/problem.rs index caeb808..caeb808 100644 --- a/src/routes/problems.rs +++ b/src/routes/problem.rs diff --git a/src/routes/user.rs b/src/routes/user.rs new file mode 100644 index 0000000..42db46f --- /dev/null +++ b/src/routes/user.rs @@ -0,0 +1,49 @@ +use axum::extract::{Json, State}; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use serde::Deserialize; +use serde_json::json; + +use crate::AppState; +use crate::auth::hash_password; +use super::errors::RouteError; + +#[derive(Deserialize)] +pub struct CreateUserRequest { + email: String, + username: String, + password: String, +} + +pub async fn create_user( + State(state): State<AppState>, + Json(request): Json<CreateUserRequest> +) -> Result<impl IntoResponse, RouteError> { + + match state.database.fetch_user_by_email(&request.email) { + Err(_) => return Err(RouteError::Internal("database action failed".into())), + Ok(Some(_)) => return Err(RouteError::UserCreateEmailExists(request.email)), + Ok(None) => {}, + }; + + match state.database.fetch_user_by_username(&request.username) { + Err(_) => return Err(RouteError::Internal("database action failed B".into())), + Ok(Some(_)) => return Err(RouteError::UserCreateUsernameExists(request.username)), + Ok(None) => {}, + }; + + let Ok(password_hash) = hash_password(&request.password) else { + return Err(RouteError::Internal("failed to hash password".into())) + }; + + let Ok(user) = state.database.insert_user(&request.email, &request.username, &password_hash) else { + return Err(RouteError::Internal("failed to create user".into())); + }; + + return Ok((StatusCode::CREATED, Json(json!({ + "id": user.id(), + "email": user.email(), + "username": user.username(), + })))); +} + |
