aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2026-02-09 13:25:17 +0200
committerrtkay123 <dev@kanjala.com>2026-02-09 13:25:17 +0200
commitbac76a98bf4e90610d0a7105d2ebe6872dc147d4 (patch)
tree341a1a31bd20741f781d48e01f7ded253bf34caf
parent253c5631ae09fd5ad9fd6b3eff104e6099d4676c (diff)
downloadsellershut-bac76a98bf4e90610d0a7105d2ebe6872dc147d4.tar.bz2
sellershut-bac76a98bf4e90610d0a7105d2ebe6872dc147d4.zip
feat: svc crate
-rw-r--r--Cargo.lock891
-rw-r--r--Cargo.toml11
-rw-r--r--lib/auth-service/Cargo.toml7
-rw-r--r--lib/auth-service/src/client/mod.rs5
-rw-r--r--lib/auth-service/src/lib.rs2
-rw-r--r--lib/auth-service/src/service/mod.rs51
-rw-r--r--lib/shared-svc/Cargo.toml28
-rw-r--r--lib/shared-svc/src/cache/cluster.rs58
-rw-r--r--lib/shared-svc/src/cache/config.rs18
-rw-r--r--lib/shared-svc/src/cache/mod.rs250
-rw-r--r--lib/shared-svc/src/cache/sentinel.rs67
-rw-r--r--lib/shared-svc/src/lib.rs16
-rw-r--r--sellershut/Cargo.toml1
-rw-r--r--sellershut/sellershut.toml3
-rw-r--r--sellershut/src/config/cli/cache.rs118
-rw-r--r--sellershut/src/config/cli/mod.rs4
-rw-r--r--sellershut/src/config/mod.rs84
-rw-r--r--sellershut/src/logging/mod.rs2
-rw-r--r--sellershut/src/main.rs3
-rw-r--r--sellershut/src/state/mod.rs32
20 files changed, 1629 insertions, 22 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6d62eee..17ebca4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,6 +12,12 @@ dependencies = [
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -77,6 +83,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
+name = "arc-swap"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "arcstr"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d"
+
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-session"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da4ce523b4e2ebaaf330746761df23a465b951a83d84bbce4233dabedae630"
+dependencies = [
+ "anyhow",
+ "async-lock",
+ "async-trait",
+ "base64 0.13.1",
+ "bincode",
+ "blake3",
+ "chrono",
+ "hmac 0.11.0",
+ "log",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha2 0.9.9",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -86,9 +169,14 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
name = "auth-service"
version = "0.1.0"
dependencies = [
+ "async-session",
+ "async-trait",
"oauth2",
"secrecy",
+ "shared-svc",
+ "sqlx",
"thiserror 2.0.18",
+ "time",
"tracing",
"url",
]
@@ -152,18 +240,88 @@ dependencies = [
]
[[package]]
+name = "backon"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
+dependencies = [
+ "fastrand",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "bb8"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "457d7ed3f888dfd2c7af56d4975cade43c622f74bdcddfed6d4352f57acc6310"
+dependencies = [
+ "futures-util",
+ "parking_lot",
+ "portable-atomic",
+ "tokio",
+]
+
+[[package]]
+name = "bb8-redis"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1063effc7f6cf848bcbcc6e31b5962be75215835587d3109607c643d616f66"
+dependencies = [
+ "bb8",
+ "redis",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
+name = "blake3"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if 0.1.10",
+ "constant_time_eq",
+ "crypto-mac 0.8.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -179,6 +337,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -196,6 +360,12 @@ dependencies = [
[[package]]
name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
@@ -267,6 +437,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
+
+[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -282,6 +481,27 @@ dependencies = [
]
[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
+[[package]]
+name = "crc16"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
+
+[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -291,6 +511,15 @@ dependencies = [
]
[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -307,6 +536,26 @@ dependencies = [
]
[[package]]
+name = "crypto-mac"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -317,12 +566,22 @@ dependencies = [
[[package]]
name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
+ "block-buffer 0.10.4",
"crypto-common",
+ "subtle",
]
[[package]]
@@ -337,6 +596,21 @@ dependencies = [
]
[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -353,12 +627,52 @@ dependencies = [
]
[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if 1.0.4",
+ "home",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -374,6 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
+ "futures-sink",
]
[[package]]
@@ -383,6 +698,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -395,7 +733,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
+ "futures-io",
+ "futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"pin-utils",
"slab",
@@ -417,7 +758,7 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
"js-sys",
"libc",
"wasi",
@@ -430,7 +771,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
"js-sys",
"libc",
"r-efi",
@@ -440,17 +781,80 @@ dependencies = [
[[package]]
name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
+[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac 0.12.1",
+]
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac 0.11.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "home"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -531,7 +935,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
- "webpki-roots",
+ "webpki-roots 1.0.6",
]
[[package]]
@@ -540,7 +944,7 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-channel",
"futures-util",
@@ -690,7 +1094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.16.1",
]
[[package]]
@@ -744,12 +1148,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
+name = "libredox"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+dependencies = [
+ "bitflags",
+ "libc",
+ "redox_syscall 0.7.0",
+]
+
+[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[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"
@@ -777,6 +1201,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if 1.0.4",
+ "digest 0.10.7",
+]
+
+[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -809,12 +1243,31 @@ 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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[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"
@@ -829,7 +1282,7 @@ version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
dependencies = [
- "base64",
+ "base64 0.22.1",
"chrono",
"getrandom 0.2.17",
"http",
@@ -838,7 +1291,7 @@ dependencies = [
"serde",
"serde_json",
"serde_path_to_error",
- "sha2",
+ "sha2 0.10.9",
"thiserror 1.0.69",
"url",
]
@@ -856,6 +1309,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[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 1.0.4",
+ "libc",
+ "redox_syscall 0.5.18",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -874,6 +1362,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "portable-atomic"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
+
+[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1036,6 +1530,54 @@ dependencies = [
]
[[package]]
+name = "redis"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e969d1d702793536d5fda739a82b88ad7cbe7d04f8386ee8cd16ad3eff4854a5"
+dependencies = [
+ "arc-swap",
+ "arcstr",
+ "backon",
+ "bytes",
+ "cfg-if 1.0.4",
+ "combine",
+ "crc16",
+ "futures-channel",
+ "futures-util",
+ "itoa",
+ "log",
+ "num-bigint",
+ "percent-encoding",
+ "pin-project-lite",
+ "rand 0.9.2",
+ "ryu",
+ "sha1_smol",
+ "socket2",
+ "tokio",
+ "tokio-util",
+ "url",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1058,7 +1600,7 @@ version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"futures-core",
"http",
@@ -1087,7 +1629,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "webpki-roots",
+ "webpki-roots 1.0.6",
]
[[package]]
@@ -1097,7 +1639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
- "cfg-if",
+ "cfg-if 1.0.4",
"getrandom 0.2.17",
"libc",
"untrusted",
@@ -1158,6 +1700,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1177,6 +1725,7 @@ dependencies = [
"clap",
"secrecy",
"serde",
+ "shared-svc",
"tokio",
"toml",
"tracing",
@@ -1261,14 +1810,33 @@ dependencies = [
]
[[package]]
+name = "sha1_smol"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if 1.0.4",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
"cpufeatures",
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -1281,6 +1849,20 @@ dependencies = [
]
[[package]]
+name = "shared-svc"
+version = "0.1.0"
+dependencies = [
+ "bb8-redis",
+ "log",
+ "redis",
+ "secrecy",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "url",
+]
+
+[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1307,6 +1889,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
[[package]]
name = "socket2"
@@ -1319,12 +1904,143 @@ dependencies = [
]
[[package]]
+name = "sqlx"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-postgres",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener 5.4.1",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.15.5",
+ "hashlink",
+ "indexmap",
+ "log",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rustls",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "smallvec",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "sqlx-core",
+ "sqlx-postgres",
+ "syn",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bitflags",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac 0.12.1",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "rand 0.8.5",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.18",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
+[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1413,7 +2129,7 @@ version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
]
[[package]]
@@ -1481,6 +2197,7 @@ dependencies = [
"bytes",
"libc",
"mio",
+ "parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@@ -1510,6 +2227,30 @@ dependencies = [
]
[[package]]
+name = "tokio-stream"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1681,12 +2422,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
+name = "unicode-normalization"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1754,12 +2516,18 @@ dependencies = [
]
[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
+[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
@@ -1772,7 +2540,7 @@ version = "0.4.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
dependencies = [
- "cfg-if",
+ "cfg-if 1.0.4",
"futures-util",
"js-sys",
"once_cell",
@@ -1834,6 +2602,15 @@ dependencies = [
[[package]]
name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
@@ -1842,6 +2619,16 @@ dependencies = [
]
[[package]]
+name = "whoami"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
+dependencies = [
+ "libredox",
+ "wasite",
+]
+
+[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1902,6 +2689,15 @@ dependencies = [
[[package]]
name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
@@ -1929,6 +2725,21 @@ dependencies = [
[[package]]
name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
@@ -1962,6 +2773,12 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
@@ -1974,6 +2791,12 @@ checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
@@ -1986,6 +2809,12 @@ checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
@@ -2010,6 +2839,12 @@ checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
@@ -2022,6 +2857,12 @@ checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
@@ -2034,6 +2875,12 @@ checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
@@ -2046,6 +2893,12 @@ checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
@@ -2075,6 +2928,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
+name = "xxhash-rust"
+version = "0.8.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
+
+[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 315004b..1dab827 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,9 +8,20 @@ readme = "README.md"
documentation = "https://books.kanjala.com/sellershut"
[workspace.dependencies]
+async-trait = "0.1.89"
secrecy = "0.10.3"
serde = "1.0.228"
+shared-svc = { path = "lib/shared-svc" }
thiserror = "2.0.18"
+time = "0.3.47"
tokio = "1.49.0"
tracing = "0.1.44"
url = "2.5.8"
+
+[workspace.dependencies.sqlx]
+version = "0.8.6"
+default-features = false
+features = ["macros", "postgres", "runtime-tokio-rustls"]
+
+[profile.dev.package.sqlx-macros]
+opt-level = 3
diff --git a/lib/auth-service/Cargo.toml b/lib/auth-service/Cargo.toml
index 8efdc57..e972312 100644
--- a/lib/auth-service/Cargo.toml
+++ b/lib/auth-service/Cargo.toml
@@ -7,8 +7,13 @@ readme.workspace = true
documentation.workspace = true
[dependencies]
-secrecy = "0.10.3"
+async-session = "3.0.0"
+async-trait.workspace = true
oauth2 = "5.0.0"
+secrecy = "0.10.3"
+shared-svc = { workspace = true, features = ["cache"] }
+sqlx.workspace = true
thiserror.workspace = true
+time.workspace = true
tracing.workspace = true
url = { workspace = true, features = ["serde"] }
diff --git a/lib/auth-service/src/client/mod.rs b/lib/auth-service/src/client/mod.rs
index af581b0..45e7e4d 100644
--- a/lib/auth-service/src/client/mod.rs
+++ b/lib/auth-service/src/client/mod.rs
@@ -5,6 +5,7 @@ use url::Url;
use crate::AuthServiceError;
+#[derive(Debug, Clone)]
pub struct OauthClient(
oauth2::basic::BasicClient<
EndpointSet,
@@ -38,10 +39,10 @@ impl ClientConfig {
}
}
-impl TryFrom<ClientConfig> for OauthClient {
+impl TryFrom<&ClientConfig> for OauthClient {
type Error = AuthServiceError;
- fn try_from(value: ClientConfig) -> Result<Self, Self::Error> {
+ fn try_from(value: &ClientConfig) -> Result<Self, Self::Error> {
debug!("creating oauth client");
Ok(Self(
oauth2::basic::BasicClient::new(ClientId::new(value.client_id.to_string()))
diff --git a/lib/auth-service/src/lib.rs b/lib/auth-service/src/lib.rs
index f7b9e80..308ce0f 100644
--- a/lib/auth-service/src/lib.rs
+++ b/lib/auth-service/src/lib.rs
@@ -1,4 +1,6 @@
pub mod client;
+mod service;
+pub use service::*;
use thiserror::Error;
diff --git a/lib/auth-service/src/service/mod.rs b/lib/auth-service/src/service/mod.rs
new file mode 100644
index 0000000..3d45523
--- /dev/null
+++ b/lib/auth-service/src/service/mod.rs
@@ -0,0 +1,51 @@
+use async_session::{Result, Session, SessionStore};
+use async_trait::async_trait;
+use shared_svc::cache::RedisManager;
+use tracing::instrument;
+
+#[derive(Debug, Clone)]
+pub struct AuthService {
+ cache: RedisManager,
+}
+
+impl AuthService {
+ pub fn new(cache: &RedisManager) -> Self {
+ Self {
+ cache: cache.clone(),
+ }
+ }
+}
+
+#[async_trait]
+impl SessionStore for AuthService {
+ #[doc = " Get a session from the storage backend."]
+ #[doc = ""]
+ #[doc = " The input is expected to be the value of an identifying"]
+ #[doc = " cookie. This will then be parsed by the session middleware"]
+ #[doc = " into a session if possible"]
+ #[instrument(skip(self))]
+ async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
+ todo!()
+ }
+
+ #[doc = " Store a session on the storage backend."]
+ #[doc = ""]
+ #[doc = " The return value is the value of the cookie to store for the"]
+ #[doc = " user that represents this session"]
+ #[instrument(skip(self))]
+ async fn store_session(&self, session: Session) -> Result<Option<String>> {
+ todo!()
+ }
+
+ #[doc = " Remove a session from the session store"]
+ #[instrument(skip(self))]
+ async fn destroy_session(&self, session: Session) -> Result {
+ todo!()
+ }
+
+ #[doc = " Empties the entire store, destroying all sessions"]
+ #[instrument(skip(self))]
+ async fn clear_store(&self) -> Result {
+ todo!()
+ }
+}
diff --git a/lib/shared-svc/Cargo.toml b/lib/shared-svc/Cargo.toml
new file mode 100644
index 0000000..d95d3ef
--- /dev/null
+++ b/lib/shared-svc/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "shared-svc"
+version = "0.1.0"
+edition = "2024"
+license.workspace = true
+readme.workspace = true
+documentation.workspace = true
+
+[dependencies]
+bb8-redis = { version = "0.26.0", optional = true }
+log = "0.4.29"
+redis = { version = "1.0.3", optional = true }
+secrecy.workspace = true
+thiserror.workspace = true
+tokio = { workspace = true, optional = true }
+tracing.workspace = true
+url.workspace = true
+
+[features]
+default = []
+cache = [
+ "bb8-redis",
+ "redis/cluster-async",
+ "redis/connection-manager",
+ "redis/sentinel",
+ "redis/tokio-comp",
+ "tokio/sync"
+]
diff --git a/lib/shared-svc/src/cache/cluster.rs b/lib/shared-svc/src/cache/cluster.rs
new file mode 100644
index 0000000..ea71954
--- /dev/null
+++ b/lib/shared-svc/src/cache/cluster.rs
@@ -0,0 +1,58 @@
+use bb8_redis::bb8;
+use redis::{
+ ErrorKind, FromRedisValue, IntoConnectionInfo, RedisError,
+ cluster::{ClusterClient, ClusterClientBuilder},
+ cluster_routing::{MultipleNodeRoutingInfo, ResponsePolicy, RoutingInfo},
+};
+
+/// ConnectionManager that implements `bb8::ManageConnection` and supports
+/// asynchronous clustered connections via `redis_cluster_async::Connection`
+#[derive(Clone)]
+pub struct RedisClusterConnectionManager {
+ client: ClusterClient,
+}
+
+impl RedisClusterConnectionManager {
+ pub fn new<T: IntoConnectionInfo>(
+ info: T,
+ ) -> Result<RedisClusterConnectionManager, RedisError> {
+ Ok(RedisClusterConnectionManager {
+ client: ClusterClientBuilder::new(vec![info]).retries(0).build()?,
+ })
+ }
+}
+
+impl bb8::ManageConnection for RedisClusterConnectionManager {
+ type Connection = redis::cluster_async::ClusterConnection;
+ type Error = RedisError;
+
+ async fn connect(&self) -> Result<Self::Connection, Self::Error> {
+ self.client.get_async_connection().await
+ }
+
+ async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
+ let cmd = redis::cmd("PING");
+ let pong = conn
+ .route_command(
+ cmd,
+ RoutingInfo::MultiNode((
+ MultipleNodeRoutingInfo::AllMasters,
+ Some(ResponsePolicy::OneSucceeded),
+ )),
+ )
+ .await
+ .and_then(|v| Ok(String::from_redis_value(v)?))?;
+ match pong.as_str() {
+ "PONG" => Ok(()),
+ _ => Err((
+ ErrorKind::Server(redis::ServerErrorKind::ResponseError),
+ "ping request",
+ )
+ .into()),
+ }
+ }
+
+ fn has_broken(&self, _: &mut Self::Connection) -> bool {
+ false
+ }
+}
diff --git a/lib/shared-svc/src/cache/config.rs b/lib/shared-svc/src/cache/config.rs
new file mode 100644
index 0000000..79a80d7
--- /dev/null
+++ b/lib/shared-svc/src/cache/config.rs
@@ -0,0 +1,18 @@
+use url::Url;
+
+use crate::cache::sentinel::SentinelConfig;
+
+#[derive(Debug, Clone)]
+pub struct CacheConfig {
+ pub redis_dsn: Url,
+ pub pooled: bool,
+ pub kind: RedisVariant,
+ pub max_connections: u16,
+}
+
+#[derive(Debug, Clone)]
+pub enum RedisVariant {
+ Clustered,
+ NonClustered,
+ Sentinel(SentinelConfig),
+}
diff --git a/lib/shared-svc/src/cache/mod.rs b/lib/shared-svc/src/cache/mod.rs
new file mode 100644
index 0000000..cd15463
--- /dev/null
+++ b/lib/shared-svc/src/cache/mod.rs
@@ -0,0 +1,250 @@
+mod cluster;
+mod config;
+mod sentinel;
+pub use sentinel::SentinelConfig;
+
+pub use config::*;
+
+use redis::{
+ AsyncConnectionConfig, ProtocolVersion, RedisConnectionInfo, RedisError, TlsMode,
+ aio::ConnectionManagerConfig, sentinel::SentinelNodeConnectionInfo,
+};
+use secrecy::ExposeSecret;
+use std::{fmt::Debug, sync::Arc};
+use tracing::debug;
+
+use bb8_redis::{
+ RedisConnectionManager,
+ bb8::{self, Pool, RunError},
+};
+use tokio::sync::Mutex;
+
+use crate::{
+ ServiceError,
+ cache::{cluster::RedisClusterConnectionManager, sentinel::RedisSentinelConnectionManager},
+};
+
+const REDIS_CONN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
+
+#[derive(Clone)]
+pub enum RedisManager {
+ Clustered(Pool<RedisClusterConnectionManager>),
+ NonClustered(Pool<RedisConnectionManager>),
+ Sentinel(Pool<RedisSentinelConnectionManager>),
+ ClusteredUnpooled(redis::cluster_async::ClusterConnection),
+ NonClusteredUnpooled(redis::aio::ConnectionManager),
+ SentinelUnpooled(Arc<Mutex<redis::sentinel::SentinelClient>>),
+}
+
+impl Debug for RedisManager {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Clustered(arg0) => f.debug_tuple("Clustered").field(arg0).finish(),
+ Self::NonClustered(arg0) => f.debug_tuple("NonClustered").field(arg0).finish(),
+ Self::Sentinel(arg0) => f.debug_tuple("Sentinel").field(arg0).finish(),
+ Self::ClusteredUnpooled(_arg0) => f.debug_tuple("ClusteredUnpooled").finish(),
+ Self::NonClusteredUnpooled(arg0) => {
+ f.debug_tuple("NonClusteredUnpooled").field(arg0).finish()
+ }
+ Self::SentinelUnpooled(_arg0) => f.debug_tuple("SentinelUnpooled").finish(),
+ }
+ }
+}
+
+pub enum RedisConnection<'a> {
+ Clustered(bb8::PooledConnection<'a, RedisClusterConnectionManager>),
+ NonClustered(bb8::PooledConnection<'a, RedisConnectionManager>),
+ SentinelPooled(bb8::PooledConnection<'a, RedisSentinelConnectionManager>),
+ ClusteredUnpooled(redis::cluster_async::ClusterConnection),
+ NonClusteredUnpooled(redis::aio::ConnectionManager),
+ SentinelUnpooled(redis::aio::MultiplexedConnection),
+}
+
+impl redis::aio::ConnectionLike for RedisConnection<'_> {
+ fn req_packed_command<'a>(
+ &'a mut self,
+ cmd: &'a redis::Cmd,
+ ) -> redis::RedisFuture<'a, redis::Value> {
+ match self {
+ RedisConnection::Clustered(conn) => conn.req_packed_command(cmd),
+ RedisConnection::NonClustered(conn) => conn.req_packed_command(cmd),
+ RedisConnection::ClusteredUnpooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::NonClusteredUnpooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::SentinelPooled(conn) => conn.req_packed_command(cmd),
+ RedisConnection::SentinelUnpooled(conn) => conn.req_packed_command(cmd),
+ }
+ }
+
+ fn req_packed_commands<'a>(
+ &'a mut self,
+ cmd: &'a redis::Pipeline,
+ offset: usize,
+ count: usize,
+ ) -> redis::RedisFuture<'a, Vec<redis::Value>> {
+ match self {
+ RedisConnection::Clustered(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::NonClustered(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::ClusteredUnpooled(conn) => {
+ conn.req_packed_commands(cmd, offset, count)
+ }
+ RedisConnection::NonClusteredUnpooled(conn) => {
+ conn.req_packed_commands(cmd, offset, count)
+ }
+ RedisConnection::SentinelPooled(conn) => conn.req_packed_commands(cmd, offset, count),
+ RedisConnection::SentinelUnpooled(conn) => conn.req_packed_commands(cmd, offset, count),
+ }
+ }
+
+ fn get_db(&self) -> i64 {
+ match self {
+ RedisConnection::Clustered(conn) => conn.get_db(),
+ RedisConnection::NonClustered(conn) => conn.get_db(),
+ RedisConnection::ClusteredUnpooled(conn) => conn.get_db(),
+ RedisConnection::NonClusteredUnpooled(conn) => conn.get_db(),
+ RedisConnection::SentinelPooled(conn) => conn.get_db(),
+ RedisConnection::SentinelUnpooled(conn) => conn.get_db(),
+ }
+ }
+}
+
+impl RedisManager {
+ pub async fn new(config: &CacheConfig) -> Result<Self, ServiceError> {
+ let u = config.redis_dsn.host_str();
+ debug!(url = u, "connecting to cache");
+ if config.pooled {
+ Self::new_pooled(
+ config.redis_dsn.as_ref(),
+ &config.kind,
+ config.max_connections,
+ )
+ .await
+ } else {
+ Self::new_unpooled(config.redis_dsn.as_ref(), &config.kind).await
+ }
+ }
+ async fn new_pooled(
+ dsn: &str,
+ variant: &RedisVariant,
+ max_conns: u16,
+ ) -> Result<Self, ServiceError> {
+ debug!(variant = ?variant, "creating pooled cache connection");
+ match variant {
+ RedisVariant::Clustered => {
+ let mgr = RedisClusterConnectionManager::new(dsn)?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::Clustered(pool))
+ }
+ RedisVariant::NonClustered => {
+ let mgr = RedisConnectionManager::new(dsn)?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::NonClustered(pool))
+ }
+ RedisVariant::Sentinel(cfg) => {
+ let mgr = RedisSentinelConnectionManager::new(
+ vec![dsn],
+ cfg.service_name.clone(),
+ Some(create_config(cfg)),
+ )?;
+ let pool = bb8::Pool::builder()
+ .max_size(max_conns.into())
+ .build(mgr)
+ .await?;
+ Ok(RedisManager::Sentinel(pool))
+ }
+ }
+ }
+
+ async fn new_unpooled(dsn: &str, variant: &RedisVariant) -> Result<Self, ServiceError> {
+ match variant {
+ RedisVariant::Clustered => {
+ let cli = redis::cluster::ClusterClient::builder(vec![dsn])
+ .retries(1)
+ .connection_timeout(REDIS_CONN_TIMEOUT)
+ .build()?;
+ let con = cli.get_async_connection().await?;
+ Ok(RedisManager::ClusteredUnpooled(con))
+ }
+ RedisVariant::NonClustered => {
+ let cli = redis::Client::open(dsn)?;
+ let con = redis::aio::ConnectionManager::new_with_config(
+ cli,
+ ConnectionManagerConfig::new()
+ .set_number_of_retries(1)
+ .set_connection_timeout(Some(REDIS_CONN_TIMEOUT)),
+ )
+ .await?;
+ Ok(RedisManager::NonClusteredUnpooled(con))
+ }
+ RedisVariant::Sentinel(cfg) => {
+ let cli = redis::sentinel::SentinelClient::build(
+ vec![dsn],
+ cfg.service_name.clone(),
+ Some(create_config(cfg)),
+ redis::sentinel::SentinelServerType::Master,
+ )?;
+
+ Ok(RedisManager::SentinelUnpooled(Arc::new(Mutex::new(cli))))
+ }
+ }
+ }
+
+ pub async fn get(&self) -> Result<RedisConnection<'_>, RunError<RedisError>> {
+ match self {
+ Self::Clustered(pool) => Ok(RedisConnection::Clustered(pool.get().await?)),
+ Self::NonClustered(pool) => Ok(RedisConnection::NonClustered(pool.get().await?)),
+ Self::Sentinel(pool) => Ok(RedisConnection::SentinelPooled(pool.get().await?)),
+ Self::ClusteredUnpooled(conn) => Ok(RedisConnection::ClusteredUnpooled(conn.clone())),
+ Self::NonClusteredUnpooled(conn) => {
+ Ok(RedisConnection::NonClusteredUnpooled(conn.clone()))
+ }
+ Self::SentinelUnpooled(conn) => {
+ let mut conn = conn.lock().await;
+ let con = conn
+ .get_async_connection_with_config(
+ &AsyncConnectionConfig::new()
+ .set_response_timeout(Some(REDIS_CONN_TIMEOUT)),
+ )
+ .await?;
+ Ok(RedisConnection::SentinelUnpooled(con))
+ }
+ }
+ }
+}
+
+fn create_config(cfg: &SentinelConfig) -> SentinelNodeConnectionInfo {
+ let tls_mode = cfg.redis_tls_mode_secure.then_some(TlsMode::Secure);
+ let protocol = if cfg.redis_use_resp3 {
+ ProtocolVersion::RESP3
+ } else {
+ ProtocolVersion::default()
+ };
+ let info = RedisConnectionInfo::default();
+ let info = if let Some(pass) = &cfg.redis_password {
+ info.set_password(pass.expose_secret())
+ } else {
+ info
+ };
+
+ let info = if let Some(user) = &cfg.redis_username {
+ info.set_username(user.clone())
+ } else {
+ info
+ }
+ .set_protocol(protocol)
+ .set_db(cfg.redis_db.unwrap_or(0));
+
+ let sent_info = SentinelNodeConnectionInfo::default();
+
+ if let Some(tls) = tls_mode {
+ sent_info.set_tls_mode(tls)
+ } else {
+ sent_info
+ }
+ .set_redis_connection_info(info)
+}
diff --git a/lib/shared-svc/src/cache/sentinel.rs b/lib/shared-svc/src/cache/sentinel.rs
new file mode 100644
index 0000000..9e76423
--- /dev/null
+++ b/lib/shared-svc/src/cache/sentinel.rs
@@ -0,0 +1,67 @@
+use bb8_redis::bb8;
+use redis::{
+ ErrorKind, IntoConnectionInfo, RedisError,
+ sentinel::{SentinelClient, SentinelNodeConnectionInfo, SentinelServerType},
+};
+use secrecy::SecretString;
+use tokio::sync::Mutex;
+
+struct LockedSentinelClient(pub(crate) Mutex<SentinelClient>);
+
+/// ConnectionManager that implements `bb8::ManageConnection` and supports
+/// asynchronous Sentinel connections via `redis::sentinel::SentinelClient`
+pub struct RedisSentinelConnectionManager {
+ client: LockedSentinelClient,
+}
+
+impl RedisSentinelConnectionManager {
+ pub fn new<T: IntoConnectionInfo>(
+ info: Vec<T>,
+ service_name: String,
+ node_connection_info: Option<SentinelNodeConnectionInfo>,
+ ) -> Result<RedisSentinelConnectionManager, RedisError> {
+ Ok(RedisSentinelConnectionManager {
+ client: LockedSentinelClient(Mutex::new(SentinelClient::build(
+ info,
+ service_name,
+ node_connection_info,
+ SentinelServerType::Master,
+ )?)),
+ })
+ }
+}
+
+impl bb8::ManageConnection for RedisSentinelConnectionManager {
+ type Connection = redis::aio::MultiplexedConnection;
+ type Error = RedisError;
+
+ async fn connect(&self) -> Result<Self::Connection, Self::Error> {
+ self.client.0.lock().await.get_async_connection().await
+ }
+
+ async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
+ let pong: String = redis::cmd("PING").query_async(conn).await?;
+ match pong.as_str() {
+ "PONG" => Ok(()),
+ _ => Err((
+ ErrorKind::Server(redis::ServerErrorKind::ResponseError),
+ "ping request",
+ )
+ .into()),
+ }
+ }
+
+ fn has_broken(&self, _: &mut Self::Connection) -> bool {
+ false
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct SentinelConfig {
+ pub service_name: String,
+ pub redis_tls_mode_secure: bool,
+ pub redis_db: Option<i64>,
+ pub redis_username: Option<String>,
+ pub redis_password: Option<SecretString>,
+ pub redis_use_resp3: bool,
+}
diff --git a/lib/shared-svc/src/lib.rs b/lib/shared-svc/src/lib.rs
new file mode 100644
index 0000000..92d9c32
--- /dev/null
+++ b/lib/shared-svc/src/lib.rs
@@ -0,0 +1,16 @@
+#[cfg(feature = "cache")]
+pub mod cache;
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum ServiceError {
+ #[error("data store disconnected")]
+ Cache(#[from] redis::RedisError),
+ #[error("the data for key `{0}` is not available")]
+ Redaction(String),
+ #[error("invalid header (expected {expected:?}, found {found:?})")]
+ InvalidHeader { expected: String, found: String },
+ #[error("unknown data store error")]
+ Unknown,
+}
diff --git a/sellershut/Cargo.toml b/sellershut/Cargo.toml
index 5ad7438..9adb37f 100644
--- a/sellershut/Cargo.toml
+++ b/sellershut/Cargo.toml
@@ -13,6 +13,7 @@ axum = "0.8.8"
clap = { version = "4.5.57", features = ["derive", "env"] }
secrecy = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
+shared-svc.workspace = true
toml = "0.9.11"
tracing.workspace = true
tracing-appender = "0.2.4"
diff --git a/sellershut/sellershut.toml b/sellershut/sellershut.toml
index 32add09..3cdc7ab 100644
--- a/sellershut/sellershut.toml
+++ b/sellershut/sellershut.toml
@@ -7,6 +7,9 @@ log-level = "trace"
url = "http://localhost:5432"
pool-size = 10
+[cache]
+url = "redis://localhost:6379"
+
[oauth]
redirect-url = "http://localhost:2210"
diff --git a/sellershut/src/config/cli/cache.rs b/sellershut/src/config/cli/cache.rs
new file mode 100644
index 0000000..f20b7f4
--- /dev/null
+++ b/sellershut/src/config/cli/cache.rs
@@ -0,0 +1,118 @@
+use clap::{Parser, ValueEnum};
+use secrecy::SecretString;
+use serde::Deserialize;
+use url::Url;
+
+#[derive(Parser, Deserialize, Debug)]
+#[serde(rename_all = "kebab-case")]
+/// Distributed cache options
+pub struct Cache {
+ /// Cache url
+ #[arg(long, value_name = "CACHE_URL", env = "CACHE_URL")]
+ #[serde(rename = "url")]
+ pub cache_url: Option<Url>,
+ /// Pooled distributed cache
+ #[arg(long, value_name = "CACHE_POOLED", env = "CACHE_POOLED")]
+ pub cache_pooled: Option<bool>,
+ /// Distributed cache type
+ #[serde(rename = "type")]
+ #[arg(long, value_name = "CACHE_TYPE", env = "CACHE_TYPE")]
+ pub cache_kind: Option<RedisVariant>,
+ /// Database pool size
+ #[arg(
+ long,
+ value_name = "CACHE_MAX_CONNECTIONS",
+ env = "CACHE_MAX_CONNECTIONS"
+ )]
+ pub cache_max_connections: Option<u16>,
+ #[command(flatten)]
+ pub sentinel: Option<SentinelConfig>,
+}
+
+#[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Deserialize, Clone, Copy, Default, ValueEnum)]
+#[serde(rename_all = "kebab-case")]
+pub enum RedisVariant {
+ Clustered,
+ #[default]
+ NonClustered,
+ Sentinel,
+}
+
+pub fn create_cache_variant(
+ variant: RedisVariant,
+ sent: Option<SentinelConfig>,
+) -> shared_svc::cache::RedisVariant {
+ match (variant, sent) {
+ (RedisVariant::Clustered, None) | (RedisVariant::Clustered, Some(_)) => {
+ shared_svc::cache::RedisVariant::Clustered
+ }
+ (RedisVariant::NonClustered, None) | (RedisVariant::NonClustered, Some(_)) => {
+ shared_svc::cache::RedisVariant::NonClustered
+ }
+ (RedisVariant::Sentinel, None) => {
+ unreachable!("should have bailed here")
+ }
+ (RedisVariant::Sentinel, Some(v)) => shared_svc::cache::RedisVariant::Sentinel(v.into()),
+ }
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Parser)]
+pub struct SentinelConfig {
+ /// Cache sentinel service name
+ #[arg(
+ long,
+ value_name = "CACHE_SENTINEL_SERVICE",
+ env = "CACHE_SENTINEL_SERVICE"
+ )]
+ #[serde(rename = "service-name")]
+ pub cache_sentinel_service: Option<String>,
+ #[serde(default)]
+ /// Cache sentinel TLS mode secure
+ #[arg(
+ long,
+ value_name = "CACHE_SENTINEL_TLS_SECURE",
+ env = "CACHE_SENTINEL_TLS_SECURE"
+ )]
+ #[serde(rename = "tls-mode-secure")]
+ pub cache_sentinel_tls_secure: bool,
+ /// Cache sentinel database number
+ #[arg(long, value_name = "CACHE_SENTINEL_DB", env = "CACHE_SENTINEL_DB")]
+ pub cache_sentinel_db: Option<i64>,
+ /// Cache sentinel username
+ #[arg(
+ long,
+ value_name = "CACHE_SENTINEL_USERNAME",
+ env = "CACHE_SENTINEL_USERNAME"
+ )]
+ pub cache_sentinel_username: Option<String>,
+ /// Cache sentinel password
+ #[arg(
+ long,
+ value_name = "CACHE_SENTINEL_PASSWORD",
+ env = "CACHE_SENTINEL_PASSWORD"
+ )]
+ pub cache_sentinel_password: Option<String>,
+ /// Cache sentinel use resp3
+ #[arg(
+ long,
+ value_name = "CACHE_SENTINEL_RESP3",
+ env = "CACHE_SENTINEL_RESP3"
+ )]
+ #[serde(default)]
+ pub cache_sentinel_use_resp3: Option<bool>,
+}
+
+impl From<SentinelConfig> for shared_svc::cache::SentinelConfig {
+ fn from(value: SentinelConfig) -> Self {
+ Self {
+ service_name: value.cache_sentinel_service.unwrap(),
+ redis_tls_mode_secure: value.cache_sentinel_tls_secure,
+ redis_db: value.cache_sentinel_db,
+ redis_username: value.cache_sentinel_username,
+ redis_password: value
+ .cache_sentinel_password
+ .map(|v| SecretString::new(v.into())),
+ redis_use_resp3: value.cache_sentinel_use_resp3.unwrap(),
+ }
+ }
+}
diff --git a/sellershut/src/config/cli/mod.rs b/sellershut/src/config/cli/mod.rs
index 8227407..784114e 100644
--- a/sellershut/src/config/cli/mod.rs
+++ b/sellershut/src/config/cli/mod.rs
@@ -1,3 +1,4 @@
+pub mod cache;
pub mod database;
pub mod oauth;
use std::path::PathBuf;
@@ -6,6 +7,7 @@ use clap::Parser;
use clap::ValueEnum;
use serde::Deserialize;
+use crate::config::cli::cache::Cache;
use crate::config::cli::{database::Database, oauth::Oauth};
use crate::config::log_level::LogLevel;
@@ -24,6 +26,8 @@ pub struct Cli {
pub oauth: Option<Oauth>,
#[command(flatten)]
pub database: Option<Database>,
+ #[command(flatten)]
+ pub cache: Option<Cache>,
}
#[derive(Parser, Deserialize, Default, Debug)]
diff --git a/sellershut/src/config/mod.rs b/sellershut/src/config/mod.rs
index b013765..42d4d5d 100644
--- a/sellershut/src/config/mod.rs
+++ b/sellershut/src/config/mod.rs
@@ -4,10 +4,14 @@ pub mod log_level;
use auth_service::client::ClientConfig;
use clap::ValueEnum;
use serde::Deserialize;
+use shared_svc::cache::CacheConfig;
use tracing::Level;
use url::Url;
-use crate::config::cli::{Cli, CliEnvironment};
+use crate::config::cli::{
+ Cli, CliEnvironment,
+ cache::{RedisVariant, SentinelConfig, create_cache_variant},
+};
#[derive(Deserialize, ValueEnum, Clone, Copy)]
#[serde(rename_all = "lowercase")]
@@ -29,6 +33,7 @@ pub struct Configuration {
pub server: Server,
pub oauth: Oauth,
pub database: Database,
+ pub cache: CacheConfig,
}
pub struct Oauth {
@@ -134,6 +139,77 @@ impl Configuration {
.or(file_db.and_then(|v| v.database_pool_size))
.unwrap_or(10); // sensible default
+ let cli_cache = cli.cache.as_ref();
+ let file_cache = file.cache.as_ref();
+
+ let cache_url = cli_cache
+ .and_then(|v| v.cache_url.clone())
+ .or(file_cache.and_then(|v| v.cache_url.clone()));
+
+ if cache_url.is_none() {
+ missing.push("cache.url");
+ }
+ let cache_pooled = cli_cache
+ .and_then(|v| v.cache_pooled)
+ .or(file_cache.and_then(|v| v.cache_pooled))
+ .unwrap_or(true);
+
+ let cache_kind = cli_cache
+ .and_then(|v| v.cache_kind)
+ .or(file_cache.and_then(|v| v.cache_kind))
+ .unwrap_or_default();
+
+ let cache_max_connections = cli_cache
+ .and_then(|v| v.cache_max_connections)
+ .or(file_cache.and_then(|v| v.cache_max_connections))
+ .unwrap_or(10);
+
+ // --- sentinel (only if kind == Sentinel) ---
+ let cli_sentinel = cli_cache.and_then(|v| v.sentinel.as_ref());
+ let file_sentinel = file_cache.and_then(|v| v.sentinel.as_ref());
+
+ let sentinel = if cache_kind == RedisVariant::Sentinel {
+ let service_name = cli_sentinel
+ .map(|v| v.cache_sentinel_service.clone())
+ .or(file_sentinel.map(|v| v.cache_sentinel_service.clone()));
+
+ if service_name.is_none() {
+ missing.push("cache.sentinel.service-name");
+ }
+
+ let redis_tls_mode_secure = cli_sentinel
+ .map(|v| v.cache_sentinel_tls_secure)
+ .or(file_sentinel.map(|v| v.cache_sentinel_tls_secure))
+ .unwrap_or(true);
+
+ let redis_db = cli_sentinel
+ .and_then(|v| v.cache_sentinel_db)
+ .or(file_sentinel.and_then(|v| v.cache_sentinel_db));
+
+ let redis_username = cli_sentinel
+ .and_then(|v| v.cache_sentinel_username.clone())
+ .or(file_sentinel.and_then(|v| v.cache_sentinel_username.clone()));
+
+ let redis_password = cli_sentinel
+ .and_then(|v| v.cache_sentinel_password.clone())
+ .or(file_sentinel.and_then(|v| v.cache_sentinel_password.clone()));
+
+ let redis_use_resp3 = cli_sentinel
+ .and_then(|v| v.cache_sentinel_use_resp3)
+ .or(file_sentinel.and_then(|v| v.cache_sentinel_use_resp3));
+
+ Some(SentinelConfig {
+ cache_sentinel_service: service_name.unwrap(),
+ cache_sentinel_tls_secure: redis_tls_mode_secure,
+ cache_sentinel_db: redis_db,
+ cache_sentinel_username: redis_username,
+ cache_sentinel_password: redis_password,
+ cache_sentinel_use_resp3: redis_use_resp3,
+ })
+ } else {
+ None
+ };
+
if !missing.is_empty() {
anyhow::bail!(
"Missing required configuration values:\n{}",
@@ -166,6 +242,12 @@ impl Configuration {
url: database_url.unwrap(),
pool_size,
},
+ cache: CacheConfig {
+ redis_dsn: cache_url.unwrap(),
+ pooled: cache_pooled,
+ kind: create_cache_variant(cache_kind, sentinel),
+ max_connections: cache_max_connections,
+ },
})
}
}
diff --git a/sellershut/src/logging/mod.rs b/sellershut/src/logging/mod.rs
index c4b00c9..29c0ca1 100644
--- a/sellershut/src/logging/mod.rs
+++ b/sellershut/src/logging/mod.rs
@@ -7,7 +7,7 @@ pub fn initialise_logging(config: &Configuration) {
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
format!(
- "{}={},tower_http=debug,axum=trace",
+ "{}={},tower_http=debug,axum=trace,info",
env!("CARGO_CRATE_NAME"),
config.server.log_level
)
diff --git a/sellershut/src/main.rs b/sellershut/src/main.rs
index 86db34d..f267071 100644
--- a/sellershut/src/main.rs
+++ b/sellershut/src/main.rs
@@ -15,6 +15,7 @@ use tracing::info;
use crate::config::Configuration;
use crate::config::cli::Cli;
use crate::logging::initialise_logging;
+use crate::state::AppState;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
@@ -30,6 +31,8 @@ async fn main() -> anyhow::Result<()> {
let config = Configuration::merge(&cli, &config)?;
initialise_logging(&config);
+ let state = AppState::new(&config).await?;
+
// Create a regular axum app.
let app = Router::new()
.route("/slow", get(|| sleep(Duration::from_secs(5))))
diff --git a/sellershut/src/state/mod.rs b/sellershut/src/state/mod.rs
index cf659c5..1a28381 100644
--- a/sellershut/src/state/mod.rs
+++ b/sellershut/src/state/mod.rs
@@ -1,5 +1,35 @@
-use auth_service::client::OauthClient;
+use auth_service::{AuthService, client::OauthClient};
+use shared_svc::cache::RedisManager;
+use tracing::{debug, error};
+use crate::config::Configuration;
+
+#[derive(Debug, Clone)]
pub struct AppState {
discord_client: OauthClient,
+ cache: RedisManager,
+ auth_service: AuthService,
+}
+
+impl AppState {
+ pub async fn new(config: &Configuration) -> anyhow::Result<Self> {
+ let discord_client = OauthClient::try_from(&config.oauth.discord)?
+ .with_redirect_url(&config.oauth.redirect_url);
+
+ let cache = {
+ let c = RedisManager::new(&config.cache).await?;
+ debug!("testing cache connection");
+ // test connection
+ c.get().await.inspect_err(|e| error!("{e}"))?;
+ c
+ };
+
+ let auth_service = AuthService::new(&cache);
+
+ Ok(Self {
+ discord_client,
+ cache,
+ auth_service,
+ })
+ }
}