From a9630ecdc459068ca51ee2d7be3837d609840842 Mon Sep 17 00:00:00 2001 From: rtkay123 Date: Mon, 9 Feb 2026 17:54:46 +0200 Subject: feat: connect to database --- Cargo.lock | 557 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +- lib/auth-service/Cargo.toml | 1 + lib/auth-service/src/service/mod.rs | 31 +- lib/shared-svc/Cargo.toml | 6 +- lib/shared-svc/src/cache/key.rs | 27 ++ lib/shared-svc/src/cache/mod.rs | 3 + lib/shared-svc/src/database/mod.rs | 16 ++ lib/shared-svc/src/lib.rs | 9 +- sellershut/Cargo.toml | 18 ++ sellershut/sellershut.toml | 2 +- sellershut/src/main.rs | 42 +-- sellershut/src/server/mod.rs | 75 +++++ sellershut/src/server/routes/mod.rs | 65 +++++ sellershut/src/server/shutdown.rs | 30 ++ sellershut/src/state/mod.rs | 19 +- 16 files changed, 852 insertions(+), 52 deletions(-) create mode 100644 lib/shared-svc/src/cache/key.rs create mode 100644 lib/shared-svc/src/database/mod.rs create mode 100644 sellershut/src/server/mod.rs create mode 100644 sellershut/src/server/routes/mod.rs create mode 100644 sellershut/src/server/shutdown.rs diff --git a/Cargo.lock b/Cargo.lock index 17ebca4..253825c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. 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" @@ -82,6 +88,15 @@ version = "1.0.101" 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" @@ -173,6 +188,7 @@ dependencies = [ "async-trait", "oauth2", "secrecy", + "serde_json", "shared-svc", "sqlx", "thiserror 2.0.18", @@ -260,6 +276,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bb8" version = "0.9.1" @@ -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" @@ -459,6 +484,12 @@ dependencies = [ "crossbeam-utils", ] +[[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" @@ -501,6 +532,15 @@ version = "0.4.0" 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" @@ -555,6 +595,17 @@ dependencies = [ "subtle", ] +[[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" @@ -564,6 +615,17 @@ dependencies = [ "powerfmt", ] +[[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" @@ -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", ] @@ -666,6 +729,27 @@ version = "0.1.9" 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" @@ -697,6 +781,17 @@ version = "0.3.31" 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" @@ -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" @@ -1147,6 +1247,12 @@ version = "0.2.180" 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" @@ -1158,6 +1264,16 @@ dependencies = [ "redox_syscall 0.7.0", ] +[[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" @@ -1222,6 +1338,26 @@ version = "0.3.17" 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" @@ -1252,6 +1388,22 @@ dependencies = [ "num-traits", ] +[[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" @@ -1267,6 +1419,17 @@ dependencies = [ "num-traits", ] +[[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" @@ -1274,6 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1343,6 +1507,21 @@ dependencies = [ "windows-link", ] +[[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" @@ -1361,6 +1540,33 @@ version = "0.1.0" 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" @@ -1577,6 +1783,18 @@ dependencies = [ "bitflags", ] +[[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" @@ -1646,6 +1864,60 @@ dependencies = [ "windows-sys 0.52.0", ] +[[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" @@ -1699,6 +1971,15 @@ version = "1.0.22" 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" @@ -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]] @@ -1809,6 +2100,17 @@ dependencies = [ "serde", ] +[[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" @@ -1856,6 +2158,7 @@ dependencies = [ "log", "redis", "secrecy", + "sqlx", "thiserror 2.0.18", "tokio", "tracing", @@ -1878,6 +2181,22 @@ dependencies = [ "libc", ] +[[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" @@ -1903,6 +2222,25 @@ dependencies = [ "windows-sys 0.60.2", ] +[[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" @@ -1911,7 +2249,9 @@ checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", "sqlx-postgres", + "sqlx-sqlite", ] [[package]] @@ -1986,6 +2326,47 @@ dependencies = [ "url", ] +[[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" @@ -2023,6 +2404,29 @@ dependencies = [ "whoami", ] +[[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" @@ -2421,6 +2825,12 @@ version = "1.19.0" 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" @@ -2479,18 +2889,124 @@ version = "0.2.2" 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" @@ -2628,6 +3144,15 @@ dependencies = [ "wasite", ] +[[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" @@ -3036,8 +3561,40 @@ dependencies = [ "syn", ] +[[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> { - 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(&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 { + 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) -> 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) { + 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, }) } } -- cgit v1.2.3