summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock654
-rw-r--r--Cargo.toml5
-rw-r--r--src/auth.rs31
-rw-r--r--src/database.rs6
-rw-r--r--src/database/database.rs158
-rw-r--r--src/database/problem.rs3
-rw-r--r--src/database/sql/fetch_user_by_email.sql1
-rw-r--r--src/database/sql/fetch_user_by_username.sql1
-rw-r--r--src/database/sql/initialize.sql2
-rw-r--r--src/database/sql/insert_user.sql1
-rw-r--r--src/database/user.rs17
-rw-r--r--src/main.rs24
-rw-r--r--src/routes.rs4
-rw-r--r--src/routes/errors.rs26
-rw-r--r--src/routes/problem.rs (renamed from src/routes/problems.rs)0
-rw-r--r--src/routes/user.rs49
16 files changed, 935 insertions, 47 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a9eba09..4e29078 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index d00ab80..a9e44d5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(),
+ }))));
+}
+