add invitation support to the db
This commit is contained in:
parent
e05d74d143
commit
a8efdac3dd
18 changed files with 623 additions and 195 deletions
292
Cargo.lock
generated
292
Cargo.lock
generated
|
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
|||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
|
@ -110,9 +110,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.76"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
|
@ -147,7 +147,7 @@ checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163"
|
|||
dependencies = [
|
||||
"askama",
|
||||
"axum-core",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -163,7 +163,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -183,13 +183,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.75"
|
||||
version = "0.1.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -225,16 +225,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "202651474fe73c62d9e0a56c6133f7a0ff1dc1c8cf7a5b03381af2a26553ac9d"
|
||||
checksum = "d09dbe0e490df5da9d69b36dca48a76635288a82f92eca90024883a56202026d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
|
@ -255,18 +255,19 @@ dependencies = [
|
|||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77cb22c689c44d4c07b0ab44ebc25d69d8ae601a2f28fb8d672d344178fa17aa"
|
||||
checksum = "e87c8503f93e6d144ee5690907ba22db7ba79ab001a932ab99034f0fe836b3df"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
|
@ -275,13 +276,14 @@ dependencies = [
|
|||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-login"
|
||||
version = "0.11.2"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f10f9f96befdaed5ba6668b1d428824ef2ddde2a0d8e3f640b8100c486679fa"
|
||||
checksum = "d18e5b44cbb5815db20bc18b2d178cd4fe5e942a5f1faad026f1cd5e53833f4d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
|
@ -306,14 +308,14 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-test"
|
||||
version = "14.0.0"
|
||||
version = "14.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56ac99db40006a1a3fffeb381f2a78cb341dbc99d07b561e8bd119e22a2b1b0f"
|
||||
checksum = "e2d15e9969313df61a64e25ce39cc8e586d42432696a0c8e0cfac1d377013d9c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -321,12 +323,14 @@ dependencies = [
|
|||
"axum",
|
||||
"bytes",
|
||||
"cookie",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"mime",
|
||||
"pretty_assertions",
|
||||
"reserve-port",
|
||||
"rust-multipart-rfc7578_2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
|
@ -353,9 +357,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.5"
|
||||
version = "0.21.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||
checksum = "c79fed4cdb43e993fcdadc7e58a09fd0e3e649c4436fa11da71c9f1f3ee7feb9"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
|
@ -365,9 +369,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
|||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778"
|
||||
checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -453,9 +457,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.11"
|
||||
version = "4.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
|
||||
checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -463,9 +467,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.11"
|
||||
version = "4.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
|
||||
checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
@ -484,7 +488,7 @@ dependencies = [
|
|||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -524,9 +528,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -548,22 +552,18 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adc6598521bb5a83d491e8c1fe51db7296019d2ca3cb93cc6c2a20369a4d78a2"
|
||||
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.18"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
|
@ -588,9 +588,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.10"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
|
@ -766,7 +766,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -810,9 +810,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
||||
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -829,16 +829,16 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a"
|
||||
checksum = "991910e35c615d8cab86b5ab04be67e6ad24d2bf5f4f11fdbbed26da999bbeab"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
@ -913,6 +913,17 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.0.0"
|
||||
|
@ -931,7 +942,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -942,7 +953,7 @@ checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
@ -984,7 +995,7 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
|
@ -1003,7 +1014,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
|
@ -1016,9 +1027,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.58"
|
||||
version = "0.1.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
|
||||
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
|
@ -1083,9 +1094,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "julid-rs"
|
||||
version = "1.6.1803"
|
||||
version = "1.6.180339"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f72d13254374b783994e8f3ad449a1b671b500746a49ff554354b8f216cfcbba"
|
||||
checksum = "35d2c64cb630d89e4e193437d73b203fcefe40ad97e1b9c5faf247e92471e553"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
|
@ -1116,9 +1127,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
|
@ -1187,9 +1198,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
|
@ -1264,6 +1275,31 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
|
@ -1281,6 +1317,16 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
|
@ -1302,6 +1348,18 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
|
@ -1342,7 +1400,7 @@ name = "optional_optional_user"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1374,6 +1432,17 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse_duration"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"num",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-auth"
|
||||
version = "1.0.0"
|
||||
|
@ -1435,7 +1504,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1501,18 +1570,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.71"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -1602,9 +1671,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
|||
|
||||
[[package]]
|
||||
name = "reserve-port"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3969e7fe15c6c1532ba1a761628298e870bbd18c252fd41a58445f6091c372a0"
|
||||
checksum = "9838134a2bfaa8e1f40738fcc972ac799de6e0e06b5157acb95fc2b05a0ea283"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"thiserror",
|
||||
|
@ -1666,6 +1735,22 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-multipart-rfc7578_2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03b748410c0afdef2ebbe3685a6a862e2ee937127cdaae623336a459451c8d57"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 0.2.11",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"rand",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
@ -1705,29 +1790,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.108"
|
||||
version = "1.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1736,9 +1821,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
|
||||
checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
|
@ -1780,9 +1865,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha256"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386"
|
||||
checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
@ -2103,9 +2188,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
|
@ -2120,9 +2205,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.42"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2137,35 +2222,35 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.1"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.51"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.51"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2261,7 +2346,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2315,7 +2400,7 @@ dependencies = [
|
|||
"axum-core",
|
||||
"cookie",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
|
@ -2331,7 +2416,7 @@ dependencies = [
|
|||
"bitflags 2.4.1",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
|
@ -2379,7 +2464,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"axum-core",
|
||||
"futures",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -2439,7 +2524,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2640,7 +2725,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
@ -2662,7 +2747,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -2687,10 +2772,11 @@ dependencies = [
|
|||
"axum-test",
|
||||
"chrono",
|
||||
"clap",
|
||||
"http",
|
||||
"http 1.0.0",
|
||||
"julid-rs",
|
||||
"justerror",
|
||||
"optional_optional_user",
|
||||
"parse_duration",
|
||||
"password-auth",
|
||||
"password-hash",
|
||||
"rand",
|
||||
|
@ -2737,11 +2823,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.51.1"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2899,7 +2985,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -35,6 +35,7 @@ tower-sessions = { version = "0.8", default-features = false, features = ["sqlit
|
|||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
unicode-segmentation = "1"
|
||||
parse_duration = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "14"
|
||||
|
|
2
julid
2
julid
|
@ -1 +1 @@
|
|||
Subproject commit f2ade6d85eddfbcaa54f106564deb6252bfc81df
|
||||
Subproject commit 705afc19e953133aadf811a0a51597e169f7aa62
|
|
@ -11,9 +11,20 @@ create table if not exists users (
|
|||
email text,
|
||||
last_seen int,
|
||||
pwhash blob not null,
|
||||
invited_by blob not null,
|
||||
last_updated int not null default (unixepoch())
|
||||
);
|
||||
|
||||
-- invitations
|
||||
create table if not exists invites (
|
||||
id blob not null primary key default (julid_new()),
|
||||
owner blob not null,
|
||||
expires_at int,
|
||||
remaining int not null default 1,
|
||||
last_updated int not null default (unixepoch()),
|
||||
foreign key (owner) references users (id) on delete cascade on update no action
|
||||
);
|
||||
|
||||
-- table of things to watch
|
||||
create table if not exists watches (
|
||||
id blob not null primary key default (julid_new()),
|
||||
|
|
|
@ -5,6 +5,13 @@ BEGIN
|
|||
update users set last_updated = (select unixepoch()) where id=NEW.id;
|
||||
END;
|
||||
|
||||
create trigger if not exists update_last_updated_invites
|
||||
after update on invites
|
||||
when OLD.last_updated = NEW.last_updated or OLD.last_updated is null
|
||||
BEGIN
|
||||
update invites set last_updated = (select unixepoch()) where id=NEW.id;
|
||||
END;
|
||||
|
||||
create trigger if not exists insert_user_follows
|
||||
after insert on users
|
||||
BEGIN
|
||||
|
|
80
src/bin/mkinvites.rs
Normal file
80
src/bin/mkinvites.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use julid::Julid;
|
||||
use parse_duration::parse;
|
||||
use sqlx::SqlitePool;
|
||||
use what2watch::{get_db_pool, Invitation, User};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Cli {
|
||||
#[clap(long, short, help = "Expire after period (eg, '5h', '1y', etc.)")]
|
||||
pub expires_in: Option<String>,
|
||||
#[clap(long, short, help = "Number of times the invitation can be used")]
|
||||
pub uses: Option<u8>,
|
||||
#[clap(long, short, help = "ID of the user creating the invite", default_value_t = Julid::omega().as_string())]
|
||||
pub owner: String,
|
||||
#[clap(long, short, help = "Number of invites to create", default_value_t = 1)]
|
||||
pub number: u8,
|
||||
}
|
||||
|
||||
struct Iq {
|
||||
owner: Julid,
|
||||
expires: Option<Duration>,
|
||||
uses: Option<u8>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let num = cli.number;
|
||||
let owner = Julid::from_string(&cli.owner).expect("Malformed ID given for owner");
|
||||
let expires = cli
|
||||
.expires_in
|
||||
.map(|e| parse(&e).expect("Could not parse {e} as a duration"));
|
||||
let uses = cli.uses;
|
||||
let quest = Iq {
|
||||
owner,
|
||||
expires,
|
||||
uses,
|
||||
};
|
||||
|
||||
let pool = get_db_pool();
|
||||
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let invites = rt.block_on(async {
|
||||
ensure_omega(&pool).await;
|
||||
generate_invites(quest, num, &pool).await
|
||||
});
|
||||
for invite in invites {
|
||||
println!("{invite}");
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_omega(pool: &SqlitePool) {
|
||||
User::omega()
|
||||
.try_insert(pool)
|
||||
.await
|
||||
.expect("Could not ensure Omega");
|
||||
}
|
||||
|
||||
async fn generate_invites(quest: Iq, number: u8, pool: &SqlitePool) -> Vec<Julid> {
|
||||
let mut invites = Vec::with_capacity(number as usize);
|
||||
for _ in 0..number {
|
||||
let mut invite = Invitation::new(quest.owner);
|
||||
if let Some(uses) = quest.uses {
|
||||
invite = invite.with_uses(uses);
|
||||
}
|
||||
if let Some(expires) = quest.expires {
|
||||
invite = invite.with_expires_in(expires);
|
||||
}
|
||||
let invite = Invitation::commit(&invite, pool)
|
||||
.await
|
||||
.expect("Error inserting invite into DB");
|
||||
invites.push(invite);
|
||||
}
|
||||
invites
|
||||
}
|
|
@ -127,7 +127,7 @@ pub async fn add_omega_watches(
|
|||
|
||||
pub async fn ensure_omega(db_pool: &SqlitePool) -> Julid {
|
||||
if !check_omega_exists(db_pool).await {
|
||||
sqlx::query("insert into users (id, username, pwhash) values (?, 'the omega user', 'you shall not password')").bind(OMEGA_ID).execute(db_pool).await.unwrap();
|
||||
User::omega().try_insert(db_pool).await.unwrap();
|
||||
}
|
||||
OMEGA_ID
|
||||
}
|
||||
|
@ -156,10 +156,12 @@ mod test {
|
|||
let p = crate::db::get_db_pool();
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
dbg!("checking omega");
|
||||
assert!(!check_omega_exists(&p).await);
|
||||
dbg!("no omega");
|
||||
ensure_omega(&p).await;
|
||||
});
|
||||
|
||||
dbg!("maybe omega");
|
||||
assert!(rt.block_on(check_omega_exists(&p)));
|
||||
}
|
||||
}
|
||||
|
|
24
src/lib.rs
24
src/lib.rs
|
@ -1,4 +1,9 @@
|
|||
use axum::{error_handling::HandleErrorLayer, routing::IntoMakeService, BoxError};
|
||||
use axum::{
|
||||
error_handling::HandleErrorLayer,
|
||||
middleware,
|
||||
routing::{get, post, IntoMakeService},
|
||||
BoxError,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
#[macro_use]
|
||||
extern crate justerror;
|
||||
|
@ -8,6 +13,7 @@ extern crate justerror;
|
|||
pub use db::get_db_pool;
|
||||
pub mod import_utils;
|
||||
|
||||
pub use signup::Invitation;
|
||||
pub use users::User;
|
||||
pub use watches::{ShowKind, Watch, WatchQuest};
|
||||
|
||||
|
@ -34,10 +40,9 @@ use watches::templates::*;
|
|||
pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
||||
// don't bother bringing handlers into the whole crate namespace
|
||||
use auth::*;
|
||||
use axum::{middleware, routing::get};
|
||||
use generic_handlers::{handle_slash, handle_slash_redir};
|
||||
use login::{get_login, get_logout, post_login, post_logout};
|
||||
use signup::{get_create_user, get_signup_success, post_create_user};
|
||||
use signup::handlers::{get_create_user, get_signup_success, post_create_user};
|
||||
use tower_http::services::ServeDir;
|
||||
use watches::handlers::{
|
||||
get_add_new_watch, get_search_watch, get_watch, get_watch_status, get_watches,
|
||||
|
@ -57,17 +62,20 @@ pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
|||
let assets_dir = std::env::current_dir().unwrap().join("assets");
|
||||
let assets_svc = ServeDir::new(assets_dir.as_path());
|
||||
|
||||
tracing_subscriber::fmt().with_target(false).pretty().init();
|
||||
|
||||
axum::Router::new()
|
||||
.route("/", get(handle_slash).post(handle_slash))
|
||||
.nest_service("/assets", assets_svc)
|
||||
.route("/signup", get(get_create_user).post(post_create_user))
|
||||
.route("/signup_success/:id", get(get_signup_success))
|
||||
.route("/signup/:invitation", get(get_create_user))
|
||||
.route("/signup/", post(post_create_user))
|
||||
.route("/signup_success/:user", get(get_signup_success))
|
||||
.route("/login", get(get_login).post(post_login))
|
||||
.route("/logout", get(get_logout).post(post_logout))
|
||||
.route("/watches", get(get_watches))
|
||||
.route("/watch", get(get_watch))
|
||||
.route("/watch/:id", get(get_watch))
|
||||
.route("/watch/status/:id", get(get_watch_status))
|
||||
.route("/watch/:watch", get(get_watch))
|
||||
.route("/watch/status/:watch", get(get_watch_status))
|
||||
.route("/search", get(get_search_watch))
|
||||
.route("/add", get(get_add_new_watch).post(post_add_new_watch))
|
||||
.route(
|
||||
|
@ -92,7 +100,7 @@ pub mod test_utils;
|
|||
//-************************************************************************
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{MainPage, OptionalOptionalUser, SignupSuccessPage, User};
|
||||
use super::{signup::templates::SignupSuccessPage, MainPage, OptionalOptionalUser, User};
|
||||
|
||||
#[test]
|
||||
fn main_page_has_optional_user() {
|
||||
|
|
|
@ -7,11 +7,13 @@ fn main() {
|
|||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "what2watch=debug,axum::routing=info".into()),
|
||||
.unwrap_or_else(|_| "what2watch=debug,axum::routing=debug".into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
//tracing_subscriber::fmt().with_target(false).pretty().init();
|
||||
|
||||
let pool = get_db_pool();
|
||||
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
|
|
|
@ -9,13 +9,14 @@ use axum::{
|
|||
};
|
||||
use julid::Julid;
|
||||
use serde::Deserialize;
|
||||
use sqlx::{query_as, SqlitePool};
|
||||
use sqlx::{query_as, Sqlite, SqlitePool};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::{util::empty_string_as_none, SignupPage, SignupSuccessPage, User};
|
||||
use super::{templates::*, Invitation};
|
||||
use crate::{util::empty_string_as_none, User};
|
||||
|
||||
pub(crate) const CREATE_QUERY: &str =
|
||||
"insert into users (username, displayname, email, pwhash) values ($1, $2, $3, $4) returning *";
|
||||
"insert into users (username, displayname, email, pwhash, invited_by) values ($1, $2, $3, $4, $5) returning *";
|
||||
const ID_QUERY: &str = "select * from users where id = $1";
|
||||
|
||||
//-************************************************************************
|
||||
|
@ -32,6 +33,11 @@ impl IntoResponse for CreateUserError {
|
|||
CreateUserErrorKind::UnknownDBError => {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||
}
|
||||
CreateUserErrorKind::BadInvitation => (
|
||||
StatusCode::OK,
|
||||
SignupErrorPage("Sorry, that invitation isn't valid.".to_string()),
|
||||
)
|
||||
.into_response(),
|
||||
_ => (StatusCode::OK, format!("{self}")).into_response(),
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +46,7 @@ impl IntoResponse for CreateUserError {
|
|||
#[Error]
|
||||
#[non_exhaustive]
|
||||
pub enum CreateUserErrorKind {
|
||||
BadInvitation,
|
||||
AlreadyExists,
|
||||
#[error(desc = "Usernames must be between 1 and 20 characters long")]
|
||||
BadUsername,
|
||||
|
@ -61,6 +68,7 @@ pub struct SignupForm {
|
|||
pub email: Option<String>,
|
||||
pub password: String,
|
||||
pub pw_verify: String,
|
||||
pub invitation: String,
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
|
@ -68,8 +76,19 @@ pub struct SignupForm {
|
|||
//-************************************************************************
|
||||
|
||||
/// Get Handler: displays the form to create a user
|
||||
pub async fn get_create_user() -> SignupPage {
|
||||
SignupPage::default()
|
||||
#[axum::debug_handler]
|
||||
pub async fn get_create_user(
|
||||
State(_pool): State<SqlitePool>,
|
||||
invitation: Option<Path<String>>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
let invitation = invitation.ok_or(CreateUserErrorKind::BadInvitation)?;
|
||||
let invitation =
|
||||
Julid::from_string(&invitation.0).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
||||
|
||||
Ok(SignupPage {
|
||||
invitation,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Post Handler: validates form values and calls the actual, private user
|
||||
|
@ -79,6 +98,8 @@ pub async fn post_create_user(
|
|||
State(pool): State<SqlitePool>,
|
||||
Form(signup): Form<SignupForm>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
dbg!(&signup);
|
||||
|
||||
use crate::util::validate_optional_length;
|
||||
let username = signup.username.trim();
|
||||
let password = signup.password.trim();
|
||||
|
@ -108,7 +129,15 @@ pub async fn post_create_user(
|
|||
|
||||
let email = validate_optional_length(&signup.email, 5..30, CreateUserErrorKind::BadEmail)?;
|
||||
|
||||
let user = create_user(username, &displayname, &email, password, &pool).await?;
|
||||
let user = create_user(
|
||||
username,
|
||||
&displayname,
|
||||
&email,
|
||||
password,
|
||||
&pool,
|
||||
&signup.invitation,
|
||||
)
|
||||
.await?;
|
||||
let when = user.id.created_at();
|
||||
tracing::debug!("created {user:?} at {when}");
|
||||
|
||||
|
@ -122,6 +151,7 @@ pub async fn get_signup_success(
|
|||
Path(id): Path<String>,
|
||||
State(pool): State<SqlitePool>,
|
||||
) -> Response {
|
||||
dbg!(&id);
|
||||
let id = id.trim();
|
||||
let id = Julid::from_string(id).unwrap_or_default();
|
||||
let user: User = {
|
||||
|
@ -153,6 +183,7 @@ pub(crate) async fn create_user(
|
|||
email: &Option<String>,
|
||||
password: &[u8],
|
||||
pool: &SqlitePool,
|
||||
invitation: &str,
|
||||
) -> Result<User, CreateUserError> {
|
||||
// Argon2 with default params (Argon2id v19)
|
||||
let argon2 = Argon2::default();
|
||||
|
@ -162,28 +193,83 @@ pub(crate) async fn create_user(
|
|||
.unwrap() // safe to unwrap, we know the salt is valid
|
||||
.to_string();
|
||||
|
||||
let res = sqlx::query_as(CREATE_QUERY)
|
||||
let mut tx = pool.begin().await.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
})?;
|
||||
|
||||
let invitation =
|
||||
Julid::from_string(invitation).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
||||
|
||||
let invited_by = validate_invitation(invitation, &mut tx).await?;
|
||||
|
||||
let user = sqlx::query_as(CREATE_QUERY)
|
||||
.bind(username)
|
||||
.bind(displayname)
|
||||
.bind(email)
|
||||
.bind(&pwhash)
|
||||
.bind(invited_by)
|
||||
.fetch_one(pool)
|
||||
.await;
|
||||
|
||||
Ok(res.map_err(|e| {
|
||||
match e {
|
||||
sqlx::Error::Database(db) => {
|
||||
let exit = db.code().unwrap_or_default().parse().unwrap_or(0);
|
||||
// https://www.sqlite.org/rescode.html codes for unique constraint violations:
|
||||
if exit == 2067u32 || exit == 1555 {
|
||||
CreateUserErrorKind::AlreadyExists
|
||||
} else {
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
.await
|
||||
.map_err(|e| {
|
||||
match e {
|
||||
sqlx::Error::Database(db) => {
|
||||
let exit = db.code().unwrap_or_default().parse().unwrap_or(0);
|
||||
// https://www.sqlite.org/rescode.html codes for unique constraint violations:
|
||||
if exit == 2067u32 || exit == 1555 {
|
||||
CreateUserErrorKind::AlreadyExists
|
||||
} else {
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
}
|
||||
}
|
||||
_ => CreateUserErrorKind::UnknownDBError,
|
||||
}
|
||||
_ => CreateUserErrorKind::UnknownDBError,
|
||||
})?;
|
||||
|
||||
tx.commit().await.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
})?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
async fn validate_invitation(
|
||||
invitation: Julid,
|
||||
tx: &mut sqlx::Transaction<'_, Sqlite>,
|
||||
) -> Result<Julid, CreateUserErrorKind> {
|
||||
let invitation: Invitation = sqlx::query_as("select * from invites where id = ?")
|
||||
.bind(invitation)
|
||||
.fetch_optional(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
})?
|
||||
.ok_or(CreateUserErrorKind::BadInvitation)?;
|
||||
|
||||
let remaining = invitation.remaining;
|
||||
if remaining < 1 {
|
||||
return Err(CreateUserErrorKind::BadInvitation);
|
||||
}
|
||||
let _ = sqlx::query("update invites set remaining = ? where id = ?")
|
||||
.bind(remaining - 1)
|
||||
.bind(invitation.id)
|
||||
.execute(&mut **tx)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
})?;
|
||||
|
||||
if let Some(ts) = invitation.expires_at {
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
if ts < now {
|
||||
return Err(CreateUserErrorKind::BadInvitation);
|
||||
}
|
||||
})?)
|
||||
}
|
||||
|
||||
Ok(invitation.owner)
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
|
@ -193,24 +279,25 @@ pub(crate) async fn create_user(
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use axum::http::StatusCode;
|
||||
use julid::Julid;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{
|
||||
db::get_db_pool,
|
||||
templates::{SignupPage, SignupSuccessPage},
|
||||
test_utils::{massage, server_with_pool, FORM_CONTENT_TYPE},
|
||||
signup::templates::{SignupPage, SignupSuccessPage},
|
||||
test_utils::{massage, server_with_pool, FORM_CONTENT_TYPE, INVITE_ID_INT},
|
||||
User,
|
||||
};
|
||||
|
||||
const GOOD_FORM: &str = "username=good_user&displayname=Good+User&password=aaaa&pw_verify=aaaa";
|
||||
|
||||
#[test]
|
||||
fn post_create_user() {
|
||||
let pool = get_db_pool();
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let server = server_with_pool(&pool).await;
|
||||
let body = massage(GOOD_FORM);
|
||||
let id: Julid = INVITE_ID_INT.into();
|
||||
let form = format!("username=good_user&displayname=Good+User&password=aaaa&pw_verify=aaaa&invitation={}", id);
|
||||
let body = massage(&form);
|
||||
|
||||
let resp = server
|
||||
.post("/signup")
|
||||
|
@ -219,11 +306,14 @@ mod test {
|
|||
.content_type(FORM_CONTENT_TYPE)
|
||||
.await;
|
||||
|
||||
dbg!(&resp.text());
|
||||
|
||||
assert_eq!(StatusCode::SEE_OTHER, resp.status_code());
|
||||
|
||||
// get the new user from the db
|
||||
let user = User::try_get("good_user", &pool).await;
|
||||
assert!(user.is_ok());
|
||||
assert!(user.unwrap().is_some());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -233,8 +323,9 @@ mod test {
|
|||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let server = server_with_pool(&pool).await;
|
||||
|
||||
let resp = server.get("/signup").await;
|
||||
let invitation: Julid = INVITE_ID_INT.into();
|
||||
let path = format!("/signup/{invitation}");
|
||||
let resp = server.get(&path).await;
|
||||
let body = std::str::from_utf8(resp.as_bytes()).unwrap();
|
||||
let expected = SignupPage::default().to_string();
|
||||
assert_eq!(&expected, body);
|
||||
|
@ -243,11 +334,15 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn handle_signup_success() {
|
||||
dbg!("getting the pool");
|
||||
let pool = get_db_pool();
|
||||
dbg!("got the pool");
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let server = server_with_pool(&pool).await;
|
||||
let body = massage(GOOD_FORM);
|
||||
let id: Julid = INVITE_ID_INT.into();
|
||||
let form = format!("username=good_user&displayname=Good+User&password=aaaa&pw_verify=aaaa&invitation={}", id);
|
||||
let body = massage(&form);
|
||||
|
||||
let resp = server
|
||||
.post("/signup")
|
||||
|
@ -259,9 +354,12 @@ mod test {
|
|||
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().unwrap();
|
||||
let user = User::try_get("good_user", &pool).await.unwrap();
|
||||
dbg!(&user);
|
||||
let user = user.unwrap();
|
||||
let id = user.id;
|
||||
|
||||
|
||||
let path = format!("/signup_success/{id}");
|
||||
|
||||
let resp = server.get(&path).expect_success().await;
|
||||
|
@ -276,19 +374,19 @@ mod test {
|
|||
//-************************************************************************
|
||||
mod failure {
|
||||
use super::*;
|
||||
use crate::signup::{CreateUserError, CreateUserErrorKind};
|
||||
use crate::signup::handlers::{CreateUserError, CreateUserErrorKind};
|
||||
|
||||
// various ways to fuck up signup
|
||||
const PASSWORD_MISMATCH_FORM: &str =
|
||||
"username=bad_user&displayname=Bad+User&password=aaaa&pw_verify=bbbb";
|
||||
"username=bad_user&displayname=Bad+User&password=aaaa&pw_verify=bbbb&invitation=0000000000000000000000001A";
|
||||
const PASSWORD_SHORT_FORM: &str =
|
||||
"username=bad_user&displayname=Bad+User&password=a&pw_verify=a";
|
||||
const PASSWORD_LONG_FORM: &str = "username=bad_user&displayname=Bad+User&password=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda&pw_verify=sphinx+of+black+qwartz+judge+my+vow+etc+etc+yadd+yadda";
|
||||
"username=bad_user&displayname=Bad+User&password=a&pw_verify=a&invitation=0000000000000000000000001A";
|
||||
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&invitation=0000000000000000000000001A";
|
||||
const USERNAME_SHORT_FORM: &str =
|
||||
"username=&displayname=Bad+User&password=aaaa&pw_verify=aaaa";
|
||||
"username=&displayname=Bad+User&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
||||
const USERNAME_LONG_FORM: &str =
|
||||
"username=bad_user12345678901234567890&displayname=Bad+User&password=aaaa&pw_verify=aaaa";
|
||||
const DISPLAYNAME_LONG_FORM: &str = "username=bad_user&displayname=Since+time+immemorial%2C+display+names+have+been+subject+to+a+number+of+conventions%2C+restrictions%2C+usages%2C+and+even+incentives.+Have+we+finally+gone+too+far%3F+In+this+essay%2C+&password=aaaa&pw_verify=aaaa";
|
||||
"username=bad_user12345678901234567890&displayname=Bad+User&password=aaaa&pw_verify=aaaa&invitation=0000000000000000000000001A";
|
||||
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&invitation=0000000000000000000000001A";
|
||||
|
||||
#[test]
|
||||
fn password_mismatch() {
|
||||
|
@ -301,7 +399,7 @@ mod test {
|
|||
let resp = server
|
||||
.post("/signup")
|
||||
// failure to sign up is not failure to submit the request
|
||||
.expect_success()
|
||||
//.expect_success()
|
||||
.bytes(body)
|
||||
.content_type(FORM_CONTENT_TYPE)
|
||||
.await;
|
||||
|
@ -381,7 +479,7 @@ mod test {
|
|||
rt.block_on(async {
|
||||
let server = server_with_pool(&pool).await;
|
||||
let form =
|
||||
format!("username=bad_user&displayname=Test+User&password={pw}&pw_verify={pw}");
|
||||
format!("username=bad_user&displayname=Test+User&password={pw}&pw_verify={pw}&invitation=0");
|
||||
let body = massage(&form);
|
||||
|
||||
let resp = server
|
||||
|
@ -460,7 +558,10 @@ mod test {
|
|||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(async {
|
||||
let server = server_with_pool(&pool).await;
|
||||
let body = massage(GOOD_FORM);
|
||||
let id: Julid = INVITE_ID_INT.into();
|
||||
let form = format!("username=good_user&displayname=Good+User&password=aaaa&pw_verify=aaaa&invitation={}", id);
|
||||
let body = massage(&form);
|
||||
//let body = massage(GOOD_FORM);
|
||||
|
||||
let _resp = server
|
||||
.post("/signup")
|
72
src/signup/mod.rs
Normal file
72
src/signup/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use julid::Julid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
pub mod handlers;
|
||||
pub mod templates;
|
||||
|
||||
#[Error(desc = "Could not create user.")]
|
||||
#[non_exhaustive]
|
||||
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
||||
|
||||
#[Error]
|
||||
#[non_exhaustive]
|
||||
pub enum CreateInviteErrorKind {
|
||||
DBError,
|
||||
TooManyUses,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct Invitation {
|
||||
pub id: Julid,
|
||||
pub owner: Julid,
|
||||
pub expires_at: Option<i64>,
|
||||
remaining: i16,
|
||||
}
|
||||
|
||||
impl Invitation {
|
||||
pub async fn commit(&self, db: &SqlitePool) -> Result<Julid, CreateInviteError> {
|
||||
sqlx::query_scalar(
|
||||
"insert into invites (owner, expires_at, remaining) values (?, ?, ?) returning id",
|
||||
)
|
||||
.bind(self.owner)
|
||||
.bind(self.expires_at)
|
||||
.bind(self.remaining)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::debug!("Got error creating invite: {e}");
|
||||
CreateInviteErrorKind::DBError
|
||||
})?
|
||||
.ok_or(CreateInviteErrorKind::DBError.into())
|
||||
}
|
||||
|
||||
pub fn new(owner: Julid) -> Self {
|
||||
Self {
|
||||
id: Julid::alpha(), // stand-in value, will let the db fill it in
|
||||
owner,
|
||||
expires_at: None,
|
||||
remaining: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_uses(&self, uses: u8) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
owner: self.owner,
|
||||
expires_at: self.expires_at,
|
||||
remaining: uses as i16,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_expires_in(&self, expires_in: Duration) -> Self {
|
||||
Self {
|
||||
id: self.id,
|
||||
owner: self.owner,
|
||||
expires_at: Some((chrono::Utc::now() + expires_in).timestamp()),
|
||||
remaining: self.remaining,
|
||||
}
|
||||
}
|
||||
}
|
28
src/signup/templates.rs
Normal file
28
src/signup/templates.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use askama::Template;
|
||||
use julid::Julid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{OptionalOptionalUser, User};
|
||||
|
||||
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||
#[template(path = "signup.html")]
|
||||
pub struct SignupPage {
|
||||
pub username: String,
|
||||
pub displayname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub password: String,
|
||||
pub pw_verify: String,
|
||||
pub invitation: Julid,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser,
|
||||
)]
|
||||
#[template(path = "signup_success.html")]
|
||||
pub struct SignupSuccessPage(pub User);
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser,
|
||||
)]
|
||||
#[template(path = "signup_error.html")]
|
||||
pub struct SignupErrorPage(pub String);
|
|
@ -3,22 +3,6 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{OptionalOptionalUser, User};
|
||||
|
||||
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||
#[template(path = "signup.html")]
|
||||
pub struct SignupPage {
|
||||
pub username: String,
|
||||
pub displayname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub password: String,
|
||||
pub pw_verify: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Template, Default, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser,
|
||||
)]
|
||||
#[template(path = "signup_success.html")]
|
||||
pub struct SignupSuccessPage(pub User);
|
||||
|
||||
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||
#[template(path = "login_page.html")]
|
||||
pub struct LoginPage {
|
||||
|
|
|
@ -7,10 +7,16 @@ use crate::User;
|
|||
|
||||
pub const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded";
|
||||
|
||||
pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
|
||||
let user = get_test_user();
|
||||
pub const INVITE_ID_INT: u128 = 42;
|
||||
|
||||
pub async fn server_with_pool(pool: &SqlitePool) -> TestServer {
|
||||
//User::omega().try_insert(pool).await.unwrap();
|
||||
|
||||
let user = get_test_user();
|
||||
user.try_insert(pool).await.unwrap();
|
||||
|
||||
add_test_invite(pool).await;
|
||||
|
||||
insert_user_with_id(&user, pool).await;
|
||||
let r: i32 = sqlx::query_scalar("select count(*) from users")
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
|
@ -33,22 +39,20 @@ fn get_test_user() -> User {
|
|||
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()),
|
||||
invited_by: Julid::omega(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert_user_with_id(user: &User, pool: &SqlitePool) {
|
||||
sqlx::query(
|
||||
"insert into users (id, username, displayname, email, pwhash) values ($1, $2, $3, $4, $5)",
|
||||
)
|
||||
.bind(user.id)
|
||||
.bind(&user.username)
|
||||
.bind(&user.displayname)
|
||||
.bind(&user.email)
|
||||
.bind(&user.pwhash)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
async fn add_test_invite(pool: &SqlitePool) {
|
||||
let id: Julid = INVITE_ID_INT.into();
|
||||
sqlx::query("insert into invites (id, owner, remaining) values (?, ?, ?)")
|
||||
.bind(id)
|
||||
.bind(Julid::omega())
|
||||
.bind(2)
|
||||
.execute(pool)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// https://www.youtube.com/watch?v=29MJySO7PGg
|
||||
|
|
28
src/users.rs
28
src/users.rs
|
@ -13,6 +13,8 @@ use crate::AuthSession;
|
|||
|
||||
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 INSERT_QUERY: &str =
|
||||
"insert into users (id, username, displayname, email, pwhash, invited_by) values ($1, $2, $3, $4, $5, $6)";
|
||||
|
||||
#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
|
@ -22,6 +24,7 @@ pub struct User {
|
|||
pub email: Option<String>,
|
||||
pub last_seen: Option<i64>,
|
||||
pub pwhash: String,
|
||||
pub invited_by: Julid,
|
||||
pub digest: String,
|
||||
}
|
||||
|
||||
|
@ -35,6 +38,7 @@ impl sqlx::FromRow<'_, SqliteRow> for User {
|
|||
displayname: row.try_get("displayname")?,
|
||||
email: row.try_get("email")?,
|
||||
last_seen: row.try_get("last_seen")?,
|
||||
invited_by: row.try_get("invited_by")?,
|
||||
pwhash,
|
||||
digest,
|
||||
})
|
||||
|
@ -50,6 +54,7 @@ impl Debug for User {
|
|||
.field("email", &self.email)
|
||||
.field("last_seen", &self.last_seen)
|
||||
.field("digest", &self.digest)
|
||||
.field("invited_by", &self.invited_by.as_string())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +80,19 @@ impl User {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn try_insert(&self, db: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||
sqlx::query(INSERT_QUERY)
|
||||
.bind(self.id)
|
||||
.bind(&self.username)
|
||||
.bind(&self.displayname)
|
||||
.bind(&self.email)
|
||||
.bind(&self.pwhash)
|
||||
.bind(self.invited_by)
|
||||
.execute(db)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub async fn update_last_seen(&self, pool: &SqlitePool) {
|
||||
match sqlx::query(LAST_SEEN_QUERY)
|
||||
.bind(self.id)
|
||||
|
@ -88,6 +106,16 @@ impl User {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn omega() -> Self {
|
||||
User {
|
||||
id: Julid::omega(),
|
||||
username: "omega".into(),
|
||||
displayname: Some("The One Who Is".into()),
|
||||
invited_by: Julid::omega(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
|
|
|
@ -249,16 +249,14 @@ pub async fn get_search_watch(
|
|||
|
||||
let query = if search_query == SearchQuery::default() {
|
||||
query_as(DEFAULT_WATCHES_QUERY)
|
||||
} else if let Some(title) = search_query.title {
|
||||
let q = format!("%{title}%");
|
||||
query_as("select * from watches where title like ?").bind(q)
|
||||
} else if let Some(search) = search_query.search {
|
||||
let q = format!("%{search}");
|
||||
query_as("select * from watches where title like ?").bind(q)
|
||||
} else {
|
||||
if let Some(title) = search_query.title {
|
||||
let q = format!("%{title}%");
|
||||
query_as("select * from watches where title like ?").bind(q)
|
||||
} else if let Some(search) = search_query.search {
|
||||
let q = format!("%{search}");
|
||||
query_as("select * from watches where title like ?").bind(q)
|
||||
} else {
|
||||
query_as(DEFAULT_WATCHES_QUERY)
|
||||
}
|
||||
query_as(DEFAULT_WATCHES_QUERY)
|
||||
};
|
||||
|
||||
// until tantivy search
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<div class="watchlist">
|
||||
<ul>
|
||||
{% for watch in watches %}
|
||||
<li><span class="watchtitle">{{watch.title}}</span> -- {% call m::get_or_default(watch.year(), "when??") %}:
|
||||
<li><span class="watchtitle">{{watch.title}}</span> -- {% call m::get_or_default(watch.year(), "when??") %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
16
templates/signup_error.html
Normal file
16
templates/signup_error.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Dang, Bish{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
<h1>Oh dang!</h1>
|
||||
|
||||
<div id="signup_success">
|
||||
<p>
|
||||
Sorry, something went wrong: {{self.0}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in a new issue