aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock557
-rw-r--r--Cargo.toml3
-rw-r--r--lib/auth-service/Cargo.toml1
-rw-r--r--lib/auth-service/src/service/mod.rs31
-rw-r--r--lib/shared-svc/Cargo.toml6
-rw-r--r--lib/shared-svc/src/cache/key.rs27
-rw-r--r--lib/shared-svc/src/cache/mod.rs3
-rw-r--r--lib/shared-svc/src/database/mod.rs16
-rw-r--r--lib/shared-svc/src/lib.rs9
-rw-r--r--sellershut/Cargo.toml18
-rw-r--r--sellershut/sellershut.toml2
-rw-r--r--sellershut/src/main.rs42
-rw-r--r--sellershut/src/server/mod.rs75
-rw-r--r--sellershut/src/server/routes/mod.rs65
-rw-r--r--sellershut/src/server/shutdown.rs30
-rw-r--r--sellershut/src/state/mod.rs19
16 files changed, 852 insertions, 52 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 17ebca4..253825c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,12 @@
version = 4
[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -83,6 +89,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
name = "arc-swap"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -173,6 +188,7 @@ dependencies = [
"async-trait",
"oauth2",
"secrecy",
+ "serde_json",
"shared-svc",
"sqlx",
"thiserror 2.0.18",
@@ -261,6 +277,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
name = "bb8"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -296,6 +318,9 @@ name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+dependencies = [
+ "serde_core",
+]
[[package]]
name = "blake3"
@@ -460,6 +485,12 @@ dependencies = [
]
[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -502,6 +533,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if 1.0.4",
+]
+
+[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -556,6 +596,17 @@ dependencies = [
]
[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -565,6 +616,17 @@ dependencies = [
]
[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -580,6 +642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer 0.10.4",
+ "const-oid",
"crypto-common",
"subtle",
]
@@ -667,6 +730,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
+name = "flate2"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
+dependencies = [
+ "miniz_oxide",
+ "zlib-rs",
+]
+
+[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
+[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -698,6 +782,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1095,6 +1190,8 @@ checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
+ "serde",
+ "serde_core",
]
[[package]]
@@ -1140,6 +1237,9 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
[[package]]
name = "libc"
@@ -1148,6 +1248,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
name = "libredox"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1159,6 +1265,16 @@ dependencies = [
]
[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1223,6 +1339,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1253,6 +1389,22 @@ dependencies = [
]
[[package]]
+name = "num-bigint-dig"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
+dependencies = [
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1268,12 +1420,24 @@ dependencies = [
]
[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
@@ -1344,6 +1508,21 @@ dependencies = [
]
[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1362,6 +1541,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1578,6 +1784,18 @@ dependencies = [
]
[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1647,6 +1865,60 @@ dependencies = [
]
[[package]]
+name = "rsa"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+dependencies = [
+ "const-oid",
+ "digest 0.10.7",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rust-embed"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "syn",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
+dependencies = [
+ "sha2 0.10.9",
+ "walkdir",
+]
+
+[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1700,6 +1972,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1723,15 +2004,25 @@ dependencies = [
"auth-service",
"axum",
"clap",
+ "http-body-util",
"secrecy",
"serde",
+ "serde_json",
"shared-svc",
+ "sqlx",
"tokio",
"toml",
+ "tower",
"tracing",
"tracing-appender",
"tracing-subscriber",
"url",
+ "utoipa",
+ "utoipa-axum",
+ "utoipa-rapidoc",
+ "utoipa-redoc",
+ "utoipa-scalar",
+ "utoipa-swagger-ui",
]
[[package]]
@@ -1810,6 +2101,17 @@ dependencies = [
]
[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if 1.0.4",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1856,6 +2158,7 @@ dependencies = [
"log",
"redis",
"secrecy",
+ "sqlx",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -1879,6 +2182,22 @@ dependencies = [
]
[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest 0.10.7",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+
+[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1904,6 +2223,25 @@ dependencies = [
]
[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
name = "sqlx"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1911,7 +2249,9 @@ checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
dependencies = [
"sqlx-core",
"sqlx-macros",
+ "sqlx-mysql",
"sqlx-postgres",
+ "sqlx-sqlite",
]
[[package]]
@@ -1987,6 +2327,47 @@ dependencies = [
]
[[package]]
+name = "sqlx-mysql"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bitflags",
+ "byteorder",
+ "bytes",
+ "crc",
+ "digest 0.10.7",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac 0.12.1",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand 0.8.5",
+ "rsa",
+ "sha1",
+ "sha2 0.10.9",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.18",
+ "tracing",
+ "whoami",
+]
+
+[[package]]
name = "sqlx-postgres"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2024,6 +2405,29 @@ dependencies = [
]
[[package]]
+name = "sqlx-sqlite"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
+dependencies = [
+ "atoi",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde_urlencoded",
+ "sqlx-core",
+ "thiserror 2.0.18",
+ "tracing",
+ "url",
+]
+
+[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2422,6 +2826,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+
+[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2480,18 +2890,124 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "utoipa"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_json",
+ "utoipa-gen",
+]
+
+[[package]]
+name = "utoipa-axum"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c25bae5bccc842449ec0c5ddc5cbb6a3a1eaeac4503895dc105a1138f8234a0"
+dependencies = [
+ "axum",
+ "paste",
+ "tower-layer",
+ "tower-service",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-gen"
+version = "5.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "utoipa-rapidoc"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5f8f5abd341cce16bb4f09a8bafc087d4884a004f25fb980e538d51d6501dab"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-redoc"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6427547f6db7ec006cbbef95f7565952a16f362e298b416d2d497d9706fef72d"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-scalar"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
+dependencies = [
+ "axum",
+ "serde",
+ "serde_json",
+ "utoipa",
+]
+
+[[package]]
+name = "utoipa-swagger-ui"
+version = "9.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
+dependencies = [
+ "axum",
+ "base64 0.22.1",
+ "mime_guess",
+ "regex",
+ "rust-embed",
+ "serde",
+ "serde_json",
+ "url",
+ "utoipa",
+ "zip",
+]
+
+[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2629,6 +3145,15 @@ dependencies = [
]
[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3037,7 +3562,39 @@ dependencies = [
]
[[package]]
+name = "zip"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
+dependencies = [
+ "arbitrary",
+ "crc32fast",
+ "flate2",
+ "indexmap",
+ "memchr",
+ "zopfli",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c"
+
+[[package]]
name = "zmij"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445"
+
+[[package]]
+name = "zopfli"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 1dab827..0a75b41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ documentation = "https://books.kanjala.com/sellershut"
async-trait = "0.1.89"
secrecy = "0.10.3"
serde = "1.0.228"
+serde_json = "1.0.149"
shared-svc = { path = "lib/shared-svc" }
thiserror = "2.0.18"
time = "0.3.47"
@@ -21,7 +22,7 @@ url = "2.5.8"
[workspace.dependencies.sqlx]
version = "0.8.6"
default-features = false
-features = ["macros", "postgres", "runtime-tokio-rustls"]
+features = ["macros", "migrate", "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 e972312..d35d17e 100644
--- a/lib/auth-service/Cargo.toml
+++ b/lib/auth-service/Cargo.toml
@@ -11,6 +11,7 @@ async-session = "3.0.0"
async-trait.workspace = true
oauth2 = "5.0.0"
secrecy = "0.10.3"
+serde_json = "1.0.149"
shared-svc = { workspace = true, features = ["cache"] }
sqlx.workspace = true
thiserror.workspace = true
diff --git a/lib/auth-service/src/service/mod.rs b/lib/auth-service/src/service/mod.rs
index 3d45523..5150221 100644
--- a/lib/auth-service/src/service/mod.rs
+++ b/lib/auth-service/src/service/mod.rs
@@ -1,17 +1,20 @@
use async_session::{Result, Session, SessionStore};
use async_trait::async_trait;
-use shared_svc::cache::RedisManager;
-use tracing::instrument;
+use shared_svc::cache::{CacheKey, RedisManager, redis::AsyncCommands};
+use sqlx::PgPool;
+use tracing::{debug, instrument};
#[derive(Debug, Clone)]
pub struct AuthService {
cache: RedisManager,
+ database: PgPool,
}
impl AuthService {
- pub fn new(cache: &RedisManager) -> Self {
+ pub fn new(cache: &RedisManager, database: &PgPool) -> Self {
Self {
cache: cache.clone(),
+ database: database.clone(),
}
}
}
@@ -32,20 +35,32 @@ impl SessionStore for AuthService {
#[doc = ""]
#[doc = " The return value is the value of the cookie to store for the"]
#[doc = " user that represents this session"]
- #[instrument(skip(self))]
+ #[instrument(err(Debug), skip(self, session), fields(id = session.id()))]
async fn store_session(&self, session: Session) -> Result<Option<String>> {
- todo!()
+ debug!("storing session");
+ let mut client = self.cache.get().await?;
+ let bytes = serde_json::to_vec(&session)?;
+ client
+ .set_ex::<_, _, ()>(CacheKey::Session(session.id()), bytes, 3600)
+ .await?;
+ Ok(session.into_cookie_value())
}
#[doc = " Remove a session from the session store"]
- #[instrument(skip(self))]
+ #[instrument(err(Debug), skip(self, session), fields(id = session.id()))]
async fn destroy_session(&self, session: Session) -> Result {
- todo!()
+ debug!("destroying session");
+ let mut client = self.cache.get().await?;
+ client.del::<_, ()>(CacheKey::Session(session.id())).await?;
+ Ok(())
}
#[doc = " Empties the entire store, destroying all sessions"]
#[instrument(skip(self))]
async fn clear_store(&self) -> Result {
- todo!()
+ debug!("clearing store");
+ let mut client = self.cache.get().await?;
+ client.del::<_, ()>(CacheKey::Session("").key()).await?;
+ Ok(())
}
}
diff --git a/lib/shared-svc/Cargo.toml b/lib/shared-svc/Cargo.toml
index d95d3ef..85a1c82 100644
--- a/lib/shared-svc/Cargo.toml
+++ b/lib/shared-svc/Cargo.toml
@@ -11,18 +11,20 @@ bb8-redis = { version = "0.26.0", optional = true }
log = "0.4.29"
redis = { version = "1.0.3", optional = true }
secrecy.workspace = true
+sqlx = { workspace = true, optional = true }
thiserror.workspace = true
tokio = { workspace = true, optional = true }
tracing.workspace = true
url.workspace = true
[features]
-default = []
+default = ["cache", "database"]
cache = [
- "bb8-redis",
+ "dep:bb8-redis",
"redis/cluster-async",
"redis/connection-manager",
"redis/sentinel",
"redis/tokio-comp",
"tokio/sync"
]
+database = ["dep:sqlx"]
diff --git a/lib/shared-svc/src/cache/key.rs b/lib/shared-svc/src/cache/key.rs
new file mode 100644
index 0000000..756b7d0
--- /dev/null
+++ b/lib/shared-svc/src/cache/key.rs
@@ -0,0 +1,27 @@
+use redis::{ToRedisArgs, ToSingleRedisArg};
+
+#[derive(Clone, Copy)]
+pub enum CacheKey<'a> {
+ Session(&'a str),
+}
+
+impl CacheKey<'_> {
+ pub fn key(&self) -> &str {
+ match self {
+ CacheKey::Session(_) => "session:*",
+ }
+ }
+}
+
+impl ToRedisArgs for CacheKey<'_> {
+ fn write_redis_args<W>(&self, out: &mut W)
+ where
+ W: ?Sized + redis::RedisWrite,
+ {
+ out.write_arg_fmt(match self {
+ CacheKey::Session(id) => format!("session:{id}"),
+ })
+ }
+}
+
+impl ToSingleRedisArg for CacheKey<'_> {}
diff --git a/lib/shared-svc/src/cache/mod.rs b/lib/shared-svc/src/cache/mod.rs
index cd15463..90cab04 100644
--- a/lib/shared-svc/src/cache/mod.rs
+++ b/lib/shared-svc/src/cache/mod.rs
@@ -1,6 +1,9 @@
mod cluster;
mod config;
+mod key;
mod sentinel;
+pub use key::*;
+pub use redis;
pub use sentinel::SentinelConfig;
pub use config::*;
diff --git a/lib/shared-svc/src/database/mod.rs b/lib/shared-svc/src/database/mod.rs
new file mode 100644
index 0000000..bbc1ba3
--- /dev/null
+++ b/lib/shared-svc/src/database/mod.rs
@@ -0,0 +1,16 @@
+use sqlx::PgPool;
+use tracing::{debug, error};
+use url::Url;
+
+use crate::ServiceError;
+
+pub async fn connect(url: &Url, pool_size: u32) -> Result<PgPool, ServiceError> {
+ let host = url.host_str();
+ debug!(host = host, "connecting to database");
+
+ Ok(sqlx::postgres::PgPoolOptions::new()
+ .max_connections(pool_size)
+ .connect(url.as_str())
+ .await
+ .inspect_err(|e| error!("{e}"))?)
+}
diff --git a/lib/shared-svc/src/lib.rs b/lib/shared-svc/src/lib.rs
index 92d9c32..d4852a5 100644
--- a/lib/shared-svc/src/lib.rs
+++ b/lib/shared-svc/src/lib.rs
@@ -1,12 +1,19 @@
#[cfg(feature = "cache")]
pub mod cache;
+#[cfg(feature = "database")]
+pub mod database;
+
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ServiceError {
- #[error("data store disconnected")]
+ #[error("cache error")]
+ #[cfg(feature = "cache")]
Cache(#[from] redis::RedisError),
+ #[error("database error")]
+ #[cfg(feature = "database")]
+ Database(#[from] sqlx::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
diff --git a/sellershut/Cargo.toml b/sellershut/Cargo.toml
index 9adb37f..8ff0e79 100644
--- a/sellershut/Cargo.toml
+++ b/sellershut/Cargo.toml
@@ -13,13 +13,31 @@ axum = "0.8.8"
clap = { version = "4.5.57", features = ["derive", "env"] }
secrecy = { workspace = true, features = ["serde"] }
serde = { workspace = true, features = ["derive"] }
+serde_json.workspace = true
shared-svc.workspace = true
+sqlx.workspace = true
toml = "0.9.11"
tracing.workspace = true
tracing-appender = "0.2.4"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
url = { workspace = true, features = ["serde"] }
+utoipa = "5.4.0"
+utoipa-axum = "0.2.0"
+utoipa-rapidoc = { version = "6.0.0", optional = true }
+utoipa-redoc = { version = "6.0.0", optional = true }
+utoipa-scalar = { version = "0.3.0", optional = true }
+utoipa-swagger-ui = { version = "9.0.2", optional = true }
[dependencies.tokio]
workspace = true
features = ["macros", "rt", "rt-multi-thread", "signal"]
+
+[features]
+rapidoc = ["dep:utoipa-rapidoc", "utoipa-rapidoc/axum"]
+redoc = ["dep:utoipa-redoc", "utoipa-redoc/axum"]
+scalar = ["dep:utoipa-scalar", "utoipa-scalar/axum"]
+swagger = ["dep:utoipa-swagger-ui", "utoipa-swagger-ui/axum"]
+
+[dev-dependencies]
+http-body-util = "0.1.3"
+tower = { version = "0.5.3", features = ["util"] }
diff --git a/sellershut/sellershut.toml b/sellershut/sellershut.toml
index 3cdc7ab..ce2f4ff 100644
--- a/sellershut/sellershut.toml
+++ b/sellershut/sellershut.toml
@@ -4,7 +4,7 @@ environment = "dev"
log-level = "trace"
[database]
-url = "http://localhost:5432"
+url = "postgres://postgres:password@localhost:5432/sellershut"
pool-size = 10
[cache]
diff --git a/sellershut/src/main.rs b/sellershut/src/main.rs
index f267071..9484f14 100644
--- a/sellershut/src/main.rs
+++ b/sellershut/src/main.rs
@@ -1,20 +1,20 @@
mod config;
mod logging;
+mod server;
mod state;
use std::net::{Ipv6Addr, SocketAddr};
-use std::time::Duration;
+use std::sync::Arc;
use anyhow::Context;
-use axum::{Router, routing::get};
use clap::Parser;
-use tokio::time::sleep;
-use tokio::{net::TcpListener, signal};
+use tokio::net::TcpListener;
use tracing::info;
use crate::config::Configuration;
use crate::config::cli::Cli;
use crate::logging::initialise_logging;
+use crate::server::shutdown::shutdown_signal;
use crate::state::AppState;
#[tokio::main]
@@ -31,45 +31,17 @@ async fn main() -> anyhow::Result<()> {
let config = Configuration::merge(&cli, &config)?;
initialise_logging(&config);
- let state = AppState::new(&config).await?;
+ let state = Arc::new(AppState::new(&config).await?);
- // Create a regular axum app.
- let app = Router::new()
- .route("/slow", get(|| sleep(Duration::from_secs(5))))
- .route("/forever", get(std::future::pending::<()>));
+ let app = server::router(Arc::clone(&state)).await;
let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.server.port));
info!(port = addr.port(), "starting server");
let listener = TcpListener::bind(addr).await?;
- // Run the server with graceful shutdown
axum::serve(listener, app)
- .with_graceful_shutdown(shutdown_signal())
+ .with_graceful_shutdown(shutdown_signal(state))
.await?;
Ok(())
}
-
-async fn shutdown_signal() {
- let ctrl_c = async {
- signal::ctrl_c()
- .await
- .expect("failed to install Ctrl+C handler");
- };
-
- #[cfg(unix)]
- let terminate = async {
- signal::unix::signal(signal::unix::SignalKind::terminate())
- .expect("failed to install signal handler")
- .recv()
- .await;
- };
-
- #[cfg(not(unix))]
- let terminate = std::future::pending::<()>();
-
- tokio::select! {
- _ = ctrl_c => {},
- _ = terminate => {},
- }
-}
diff --git a/sellershut/src/server/mod.rs b/sellershut/src/server/mod.rs
new file mode 100644
index 0000000..9ec4bf4
--- /dev/null
+++ b/sellershut/src/server/mod.rs
@@ -0,0 +1,75 @@
+mod routes;
+pub mod shutdown;
+
+use std::sync::Arc;
+
+use axum::Router;
+use utoipa::OpenApi;
+use utoipa_axum::router::OpenApiRouter;
+
+use crate::{server::routes::ApiDoc, state::AppState};
+
+pub async fn router(state: Arc<AppState>) -> Router<()> {
+ let doc = ApiDoc::openapi();
+
+ // doc.merge(other_doc);
+
+ let stubs = OpenApiRouter::with_openapi(doc).routes(utoipa_axum::routes!(routes::health));
+
+ let (router, _api) = stubs.split_for_parts();
+
+ #[cfg(feature = "swagger")]
+ let router = router.merge(
+ utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
+ .url("/api-docs/swaggerdoc.json", _api.clone()),
+ );
+
+ #[cfg(feature = "redoc")]
+ let router = {
+ use utoipa_redoc::Servable as _;
+ router.merge(utoipa_redoc::Redoc::with_url("/redoc", _api.clone()))
+ };
+
+ #[cfg(feature = "scalar")]
+ let router = {
+ use utoipa_scalar::Servable as _;
+ router.merge(utoipa_scalar::Scalar::with_url("/scalar", _api.clone()))
+ };
+
+ #[cfg(feature = "rapidoc")]
+ let router = router.merge(
+ utoipa_rapidoc::RapiDoc::with_openapi("/api-docs/rapidoc.json", _api).path("/rapidoc"),
+ );
+
+ router.with_state(state)
+}
+
+#[cfg(test)]
+pub mod tests {
+ use std::str::FromStr;
+
+ use auth_service::client::{ClientConfig, OauthClient};
+ use secrecy::SecretString;
+ use shared_svc::cache::{CacheConfig, RedisManager, RedisVariant};
+ use url::Url;
+
+ pub async fn get_redis() -> RedisManager {
+ let config = CacheConfig {
+ redis_dsn: Url::parse("redis://localhost:6379/15").unwrap(),
+ pooled: false,
+ kind: RedisVariant::NonClustered,
+ max_connections: 10,
+ };
+ RedisManager::new(&config).await.unwrap()
+ }
+
+ pub fn get_oauth_client() -> OauthClient {
+ let oauth_client = ClientConfig::new(
+ String::default(),
+ SecretString::default(),
+ Url::from_str("http://localhost").unwrap(),
+ Url::from_str("http://localhost").unwrap(),
+ );
+ OauthClient::try_from(&oauth_client).unwrap()
+ }
+}
diff --git a/sellershut/src/server/routes/mod.rs b/sellershut/src/server/routes/mod.rs
new file mode 100644
index 0000000..287293a
--- /dev/null
+++ b/sellershut/src/server/routes/mod.rs
@@ -0,0 +1,65 @@
+use axum::response::IntoResponse;
+use utoipa::OpenApi;
+
+const MAIN: &str = "sellershut";
+#[derive(OpenApi)]
+#[openapi(
+ tags(
+ (name = MAIN, description = "API health check"),
+ )
+)]
+pub(super) struct ApiDoc;
+
+/// Get health of the API.
+#[utoipa::path(
+ method(get, head),
+ path = "/api/health",
+ responses(
+ (status = OK, description = "Success", body = str, content_type = "text/plain")
+ )
+)]
+pub async fn health() -> impl IntoResponse {
+ let name = env!("CARGO_PKG_NAME");
+ let ver = env!("CARGO_PKG_VERSION");
+ format!("{name} v{ver} is live")
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Arc;
+
+ use auth_service::AuthService;
+ use axum::{
+ body::Body,
+ http::{Request, StatusCode},
+ };
+ use sqlx::PgPool;
+
+ use crate::{
+ server::{
+ router,
+ tests::{get_oauth_client, get_redis},
+ },
+ state::AppState,
+ };
+ use tower::ServiceExt;
+
+ #[sqlx::test]
+ async fn health(database: PgPool) {
+ let cache = get_redis().await;
+ let state = Arc::new(AppState {
+ discord_client: get_oauth_client(),
+ auth_service: AuthService::new(&cache, &database),
+ cache,
+ database,
+ });
+ let app = router(state).await;
+
+ let response = app
+ .oneshot(Request::builder().uri("/api/health").body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+
+ assert_eq!(response.status(), StatusCode::OK);
+ }
+}
diff --git a/sellershut/src/server/shutdown.rs b/sellershut/src/server/shutdown.rs
new file mode 100644
index 0000000..075b3d0
--- /dev/null
+++ b/sellershut/src/server/shutdown.rs
@@ -0,0 +1,30 @@
+use std::sync::Arc;
+use tokio::signal;
+
+use crate::state::AppState;
+
+pub async fn shutdown_signal(state: Arc<AppState>) {
+ let ctrl_c = async {
+ signal::ctrl_c()
+ .await
+ .expect("failed to install Ctrl+C handler");
+ };
+
+ #[cfg(unix)]
+ let terminate = async {
+ signal::unix::signal(signal::unix::SignalKind::terminate())
+ .expect("failed to install signal handler")
+ .recv()
+ .await;
+ };
+
+ #[cfg(not(unix))]
+ let terminate = std::future::pending::<()>();
+
+ state.database.close().await;
+
+ tokio::select! {
+ _ = ctrl_c => {},
+ _ = terminate => {},
+ }
+}
diff --git a/sellershut/src/state/mod.rs b/sellershut/src/state/mod.rs
index 84358a2..be20e13 100644
--- a/sellershut/src/state/mod.rs
+++ b/sellershut/src/state/mod.rs
@@ -1,14 +1,16 @@
use auth_service::{AuthService, client::OauthClient};
use shared_svc::cache::RedisManager;
+use sqlx::PgPool;
use tracing::{debug, error};
use crate::config::Configuration;
#[derive(Debug, Clone)]
pub struct AppState {
- discord_client: OauthClient,
- cache: RedisManager,
- auth_service: AuthService,
+ pub discord_client: OauthClient,
+ pub cache: RedisManager,
+ pub auth_service: AuthService,
+ pub database: PgPool,
}
impl AppState {
@@ -26,12 +28,21 @@ impl AppState {
c
};
- let auth_service = AuthService::new(&cache);
+ let database = {
+ let host = config.database.url.host_str();
+ let c = shared_svc::database::connect(&config.database.url, config.database.pool_size)
+ .await?;
+ debug!(host = host, "database connection ok");
+ c
+ };
+
+ let auth_service = AuthService::new(&cache, &database);
Ok(Self {
discord_client,
cache,
auth_service,
+ database,
})
}
}