split the project in 2
This commit is contained in:
Generated
+770
@@ -0,0 +1,770 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde_core",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[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"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb92f162bf56536459fc83c79b974bb12837acfed43d6bc370a7916d0ae15ecc"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||
dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tssbot-backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[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 = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "tssbot-backend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.8"
|
||||
rusqlite = { version = "0.37", features = ["bundled"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
|
||||
tower-http = { version = "0.6", features = ["cors", "trace"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
urlencoding = "2"
|
||||
@@ -0,0 +1,34 @@
|
||||
# tssbot backend
|
||||
|
||||
Rust backend API service for Toothless' TSS Bot.
|
||||
|
||||
It reads two SQLite databases:
|
||||
|
||||
- `TSS_BATTLES_DB` for `tss_battles.db`
|
||||
- `TSS_TEAMS_DB` for `tss_teams.db`
|
||||
|
||||
Both paths can be absolute or relative to the repo root when run through the root scripts/PM2.
|
||||
|
||||
It currently exposes:
|
||||
|
||||
- `GET /health`
|
||||
- `GET /api/tss/leaderboard/teams?limit=100`
|
||||
- `GET /api/tss/teams/resolve?name=...`
|
||||
- `GET /api/tss/teams/search?q=...&limit=10`
|
||||
- `GET /api/tss/teams/:team`
|
||||
- `GET /api/tss/teams/:team/history`
|
||||
- `GET /api/tss/teams/:team/games`
|
||||
|
||||
## Local development
|
||||
|
||||
```sh
|
||||
npm run dev:backend
|
||||
```
|
||||
|
||||
The backend listens on <http://localhost:6000> by default. Override with `BACKEND_PORT`.
|
||||
|
||||
## Production build
|
||||
|
||||
```sh
|
||||
npm run build:backend
|
||||
```
|
||||
@@ -0,0 +1,958 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::{header, Method, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use rusqlite::{params, Connection, OptionalExtension};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
env, fs,
|
||||
net::SocketAddr,
|
||||
path::{Path as FsPath, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
trace::TraceLayer,
|
||||
};
|
||||
|
||||
const MAX_TEAM_NAME_LENGTH: usize = 80;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
battles_db: PathBuf,
|
||||
teams_db: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ApiError {
|
||||
status: StatusCode,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl ApiError {
|
||||
fn bad_request(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn not_found(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
status: StatusCode::NOT_FOUND,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn internal(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
fn into_response(self) -> Response {
|
||||
(self.status, Json(json!({ "error": self.message }))).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
type ApiResult<T> = Result<Json<T>, ApiError>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LimitQuery {
|
||||
limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ResolveQuery {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SearchQuery {
|
||||
q: Option<String>,
|
||||
name: Option<String>,
|
||||
limit: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HealthResponse {
|
||||
ok: bool,
|
||||
service: &'static str,
|
||||
battles_db: String,
|
||||
teams_db: String,
|
||||
databases: BTreeMap<&'static str, bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LeaderboardResponse {
|
||||
teams: Vec<TeamLeaderboardRow>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchResponse {
|
||||
teams: Vec<TeamSearchRow>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ResolveResponse {
|
||||
team_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TeamSearchRow {
|
||||
team_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
members: i64,
|
||||
clanrating: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TeamLeaderboardRow {
|
||||
team_id: i64,
|
||||
clan_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
short_name: Option<String>,
|
||||
player_count: i64,
|
||||
total_battles: i64,
|
||||
wins: i64,
|
||||
losses: i64,
|
||||
win_rate: f64,
|
||||
total_kills: i64,
|
||||
points: Points,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TeamDetail {
|
||||
team_id: i64,
|
||||
clan_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
short_name: Option<String>,
|
||||
description: Option<String>,
|
||||
region: Option<String>,
|
||||
members: i64,
|
||||
captain_uid: Option<String>,
|
||||
guild_id: Option<String>,
|
||||
created_unix: Option<i64>,
|
||||
updated_unix: Option<i64>,
|
||||
clanrating: Option<i64>,
|
||||
data_set: &'static str,
|
||||
team_summary: TeamSummary,
|
||||
players: Vec<PlayerSummary>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct TeamSummary {
|
||||
player_count: i64,
|
||||
total_battles: i64,
|
||||
wins: i64,
|
||||
losses: i64,
|
||||
win_rate: f64,
|
||||
kdr: f64,
|
||||
total_kills: i64,
|
||||
points: Points,
|
||||
total_points: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Points {
|
||||
total_points: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PlayerSummary {
|
||||
uid: String,
|
||||
nick: Option<String>,
|
||||
role: String,
|
||||
joined_unix: Option<i64>,
|
||||
points: i64,
|
||||
sqb_points: i64,
|
||||
total_battles: i64,
|
||||
wins: i64,
|
||||
losses: i64,
|
||||
win_rate: f64,
|
||||
total_kills: i64,
|
||||
ground_kills: i64,
|
||||
air_kills: i64,
|
||||
assists: i64,
|
||||
deaths: i64,
|
||||
kdr: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct HistoryResponse {
|
||||
team_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
history: Vec<PeriodHistory>,
|
||||
rating_hourly: Vec<RatingPoint>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PeriodHistory {
|
||||
period: String,
|
||||
battles: i64,
|
||||
wins: i64,
|
||||
losses: i64,
|
||||
win_rate: f64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RatingPoint {
|
||||
timestamp: i64,
|
||||
rating: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GamesResponse {
|
||||
team_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
games: Vec<GameRow>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GameRow {
|
||||
session_id: String,
|
||||
timestamp: i64,
|
||||
endtime_unix: i64,
|
||||
map_name: Option<String>,
|
||||
mission_mode: Option<String>,
|
||||
result: String,
|
||||
player_count: i64,
|
||||
winning_team: Option<String>,
|
||||
losing_team: Option<String>,
|
||||
stats: GameStats,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GameStats {
|
||||
ground_kills: i64,
|
||||
air_kills: i64,
|
||||
assists: i64,
|
||||
captures: i64,
|
||||
deaths: i64,
|
||||
score: i64,
|
||||
missile_evades: i64,
|
||||
shell_interceptions: i64,
|
||||
team_kills_stat: i64,
|
||||
}
|
||||
|
||||
struct TeamRecord {
|
||||
team_id: i64,
|
||||
long_name: String,
|
||||
tag_name: Option<String>,
|
||||
description: Option<String>,
|
||||
region: Option<String>,
|
||||
members: i64,
|
||||
captain_uid: Option<String>,
|
||||
guild_id: Option<String>,
|
||||
created_unix: Option<i64>,
|
||||
updated_unix: Option<i64>,
|
||||
clanrating: Option<i64>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
load_root_env();
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
let port = env_u16("BACKEND_PORT")
|
||||
.or_else(|| env_u16("PORT"))
|
||||
.unwrap_or(6000);
|
||||
let state = Arc::new(AppState {
|
||||
battles_db: resolve_db_path("TSS_BATTLES_DB", "tss_battles.db"),
|
||||
teams_db: resolve_db_path("TSS_TEAMS_DB", "tss_teams.db"),
|
||||
});
|
||||
|
||||
let app = Router::new()
|
||||
.route("/health", get(health))
|
||||
.route("/api/tss/leaderboard/teams", get(leaderboard))
|
||||
.route("/api/tss/teams/resolve", get(resolve_team))
|
||||
.route("/api/tss/teams/search", get(search_teams))
|
||||
.route("/api/tss/teams/{team}", get(team_detail))
|
||||
.route("/api/tss/teams/{team}/history", get(team_history))
|
||||
.route("/api/tss/teams/{team}/games", get(team_games))
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
.allow_methods([Method::GET])
|
||||
.allow_origin(Any)
|
||||
.allow_headers([header::ACCEPT, header::CONTENT_TYPE]),
|
||||
)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.with_state(state);
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
tracing::info!("tssbot backend listening on http://{}", addr);
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
let ctrl_c = async {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("failed to install Ctrl+C handler");
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
tokio::signal::unix::signal(tokio::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 => {},
|
||||
}
|
||||
}
|
||||
|
||||
async fn health(State(state): State<Arc<AppState>>) -> Json<HealthResponse> {
|
||||
let mut databases = BTreeMap::new();
|
||||
databases.insert(
|
||||
"battles",
|
||||
Connection::open_with_flags(
|
||||
&state.battles_db,
|
||||
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY,
|
||||
)
|
||||
.is_ok(),
|
||||
);
|
||||
databases.insert(
|
||||
"teams",
|
||||
Connection::open_with_flags(&state.teams_db, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY)
|
||||
.is_ok(),
|
||||
);
|
||||
|
||||
Json(HealthResponse {
|
||||
ok: databases.values().all(|ok| *ok),
|
||||
service: "tssbot-backend",
|
||||
battles_db: state.battles_db.display().to_string(),
|
||||
teams_db: state.teams_db.display().to_string(),
|
||||
databases,
|
||||
})
|
||||
}
|
||||
|
||||
async fn leaderboard(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LimitQuery>,
|
||||
) -> ApiResult<LeaderboardResponse> {
|
||||
let limit = i64::from(query.limit.unwrap_or(100).clamp(1, 100));
|
||||
let teams_conn = open_db(&state.teams_db)?;
|
||||
let battles_conn = open_db(&state.battles_db)?;
|
||||
let mut stmt = teams_conn
|
||||
.prepare(
|
||||
"SELECT team_id, long_name, tag_name, description, region, members, captain_uid, guild_id,
|
||||
created_unix, updated_unix, clanrating
|
||||
FROM teams_data
|
||||
ORDER BY COALESCE(clanrating, 0) DESC, members DESC, long_name COLLATE NOCASE ASC
|
||||
LIMIT ?1",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
let teams = stmt
|
||||
.query_map(params![limit], |row| read_team_record(row))
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)?;
|
||||
|
||||
let mut rows = Vec::with_capacity(teams.len());
|
||||
for team in teams {
|
||||
let summary = team_summary_for(&battles_conn, team.team_id)?;
|
||||
rows.push(TeamLeaderboardRow {
|
||||
team_id: team.team_id,
|
||||
clan_id: team.team_id,
|
||||
long_name: team.long_name.clone(),
|
||||
tag_name: team.tag_name.clone(),
|
||||
short_name: team.tag_name.clone(),
|
||||
player_count: team.members,
|
||||
total_battles: summary.total_battles,
|
||||
wins: summary.wins,
|
||||
losses: summary.losses,
|
||||
win_rate: summary.win_rate,
|
||||
total_kills: summary.total_kills,
|
||||
points: Points {
|
||||
total_points: team.clanrating.unwrap_or(summary.total_points),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Json(LeaderboardResponse { teams: rows }))
|
||||
}
|
||||
|
||||
async fn resolve_team(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<ResolveQuery>,
|
||||
) -> ApiResult<ResolveResponse> {
|
||||
let name = validate_team_name(&query.name)?;
|
||||
let conn = open_db(&state.teams_db)?;
|
||||
let team = find_team(&conn, name)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
|
||||
Ok(Json(ResolveResponse {
|
||||
team_id: team.team_id,
|
||||
long_name: team.long_name.clone(),
|
||||
tag_name: team.tag_name.clone(),
|
||||
name: team.tag_name.clone().unwrap_or(team.long_name),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn search_teams(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<SearchQuery>,
|
||||
) -> ApiResult<SearchResponse> {
|
||||
let raw = query.q.as_deref().or(query.name.as_deref()).unwrap_or("");
|
||||
let name = validate_team_name(raw)?;
|
||||
let limit = i64::from(query.limit.unwrap_or(10).clamp(1, 20));
|
||||
let like = format!("%{}%", escape_like(name));
|
||||
let conn = open_db(&state.teams_db)?;
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT team_id, long_name, tag_name, members, clanrating
|
||||
FROM teams_data
|
||||
WHERE long_name LIKE ?1 ESCAPE '\\' OR tag_name LIKE ?1 ESCAPE '\\'
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN tag_name = ?2 COLLATE NOCASE THEN 0
|
||||
WHEN long_name = ?2 COLLATE NOCASE THEN 1
|
||||
ELSE 2
|
||||
END,
|
||||
COALESCE(clanrating, 0) DESC,
|
||||
long_name COLLATE NOCASE ASC
|
||||
LIMIT ?3",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
let teams = stmt
|
||||
.query_map(params![like, name, limit], |row| {
|
||||
Ok(TeamSearchRow {
|
||||
team_id: row.get(0)?,
|
||||
long_name: row.get(1)?,
|
||||
tag_name: row.get(2)?,
|
||||
members: row.get(3)?,
|
||||
clanrating: row.get(4)?,
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)?;
|
||||
|
||||
Ok(Json(SearchResponse { teams }))
|
||||
}
|
||||
|
||||
async fn team_detail(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(team_name): Path<String>,
|
||||
) -> ApiResult<TeamDetail> {
|
||||
let decoded = decode_path_team(&team_name)?;
|
||||
let teams_conn = open_db(&state.teams_db)?;
|
||||
let battles_conn = open_db(&state.battles_db)?;
|
||||
let team =
|
||||
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
|
||||
let summary = team_summary_for(&battles_conn, team.team_id)?;
|
||||
let players = player_summaries_for(&teams_conn, &battles_conn, team.team_id)?;
|
||||
|
||||
Ok(Json(TeamDetail {
|
||||
team_id: team.team_id,
|
||||
clan_id: team.team_id,
|
||||
long_name: team.long_name,
|
||||
tag_name: team.tag_name.clone(),
|
||||
short_name: team.tag_name,
|
||||
description: team.description,
|
||||
region: team.region,
|
||||
members: team.members,
|
||||
captain_uid: team.captain_uid,
|
||||
guild_id: team.guild_id,
|
||||
created_unix: team.created_unix,
|
||||
updated_unix: team.updated_unix,
|
||||
clanrating: team.clanrating,
|
||||
data_set: "tss",
|
||||
team_summary: summary,
|
||||
players,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn team_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(team_name): Path<String>,
|
||||
) -> ApiResult<HistoryResponse> {
|
||||
let decoded = decode_path_team(&team_name)?;
|
||||
let teams_conn = open_db(&state.teams_db)?;
|
||||
let battles_conn = open_db(&state.battles_db)?;
|
||||
let team =
|
||||
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
|
||||
|
||||
let history = period_history_for(&battles_conn, team.team_id)?;
|
||||
let rating_hourly = rating_history_for(&teams_conn, team.team_id)?;
|
||||
|
||||
Ok(Json(HistoryResponse {
|
||||
team_id: team.team_id,
|
||||
long_name: team.long_name,
|
||||
tag_name: team.tag_name,
|
||||
history,
|
||||
rating_hourly,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn team_games(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(team_name): Path<String>,
|
||||
) -> ApiResult<GamesResponse> {
|
||||
let decoded = decode_path_team(&team_name)?;
|
||||
let teams_conn = open_db(&state.teams_db)?;
|
||||
let battles_conn = open_db(&state.battles_db)?;
|
||||
let team =
|
||||
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
|
||||
let games = games_for(&battles_conn, team.team_id)?;
|
||||
|
||||
Ok(Json(GamesResponse {
|
||||
team_id: team.team_id,
|
||||
long_name: team.long_name,
|
||||
tag_name: team.tag_name,
|
||||
games,
|
||||
}))
|
||||
}
|
||||
|
||||
fn open_db(path: &FsPath) -> Result<Connection, ApiError> {
|
||||
Connection::open_with_flags(path, rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY).map_err(|error| {
|
||||
ApiError::internal(format!("Could not open {}: {}", path.display(), error))
|
||||
})
|
||||
}
|
||||
|
||||
fn db_error(error: rusqlite::Error) -> ApiError {
|
||||
ApiError::internal(format!("Database query failed: {}", error))
|
||||
}
|
||||
|
||||
fn read_team_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<TeamRecord> {
|
||||
Ok(TeamRecord {
|
||||
team_id: row.get(0)?,
|
||||
long_name: row.get(1)?,
|
||||
tag_name: row.get(2)?,
|
||||
description: row.get(3)?,
|
||||
region: row.get(4)?,
|
||||
members: row.get(5)?,
|
||||
captain_uid: row.get(6)?,
|
||||
guild_id: row.get(7)?,
|
||||
created_unix: row.get(8)?,
|
||||
updated_unix: row.get(9)?,
|
||||
clanrating: row.get(10)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn find_team(conn: &Connection, name: &str) -> Result<Option<TeamRecord>, ApiError> {
|
||||
conn.query_row(
|
||||
"SELECT team_id, long_name, tag_name, description, region, members, captain_uid, guild_id,
|
||||
created_unix, updated_unix, clanrating
|
||||
FROM teams_data
|
||||
WHERE team_id = ?1
|
||||
OR long_name = ?2 COLLATE NOCASE
|
||||
OR tag_name = ?2 COLLATE NOCASE
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN tag_name = ?2 COLLATE NOCASE THEN 0
|
||||
WHEN long_name = ?2 COLLATE NOCASE THEN 1
|
||||
ELSE 2
|
||||
END
|
||||
LIMIT 1",
|
||||
params![name.parse::<i64>().ok(), name],
|
||||
read_team_record,
|
||||
)
|
||||
.optional()
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn team_summary_for(conn: &Connection, team_id: i64) -> Result<TeamSummary, ApiError> {
|
||||
conn.query_row(
|
||||
"SELECT
|
||||
COUNT(DISTINCT session_id),
|
||||
SUM(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN victor_bool != 'Win' THEN 1 ELSE 0 END),
|
||||
COALESCE(SUM(ground_kills), 0),
|
||||
COALESCE(SUM(air_kills), 0),
|
||||
COALESCE(SUM(assists), 0),
|
||||
COALESCE(SUM(deaths), 0),
|
||||
COALESCE(SUM(score), 0),
|
||||
COUNT(DISTINCT UID)
|
||||
FROM player_games_hist
|
||||
WHERE team_id = ?1",
|
||||
params![team_id],
|
||||
|row| {
|
||||
let battles: i64 = row.get(0)?;
|
||||
let wins: i64 = row.get(1)?;
|
||||
let losses: i64 = row.get(2)?;
|
||||
let ground: i64 = row.get(3)?;
|
||||
let air: i64 = row.get(4)?;
|
||||
let assists: i64 = row.get(5)?;
|
||||
let deaths: i64 = row.get(6)?;
|
||||
let score: i64 = row.get(7)?;
|
||||
let player_count: i64 = row.get(8)?;
|
||||
let total_kills = ground + air;
|
||||
Ok(TeamSummary {
|
||||
player_count,
|
||||
total_battles: battles,
|
||||
wins,
|
||||
losses,
|
||||
win_rate: percent(wins, battles),
|
||||
kdr: ratio(total_kills, deaths),
|
||||
total_kills,
|
||||
points: Points {
|
||||
total_points: score + assists + total_kills,
|
||||
},
|
||||
total_points: score + assists + total_kills,
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn player_summaries_for(
|
||||
teams_conn: &Connection,
|
||||
battles_conn: &Connection,
|
||||
team_id: i64,
|
||||
) -> Result<Vec<PlayerSummary>, ApiError> {
|
||||
let mut stmt = teams_conn
|
||||
.prepare(
|
||||
"SELECT uid, nick, role, joined_unix, points
|
||||
FROM team_members
|
||||
WHERE team_id = ?1
|
||||
ORDER BY points DESC, nick COLLATE NOCASE ASC",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
let members = stmt
|
||||
.query_map(params![team_id], |row| {
|
||||
Ok((
|
||||
row.get::<_, String>(0)?,
|
||||
row.get::<_, Option<String>>(1)?,
|
||||
row.get::<_, String>(2)?,
|
||||
row.get::<_, Option<i64>>(3)?,
|
||||
row.get::<_, i64>(4)?,
|
||||
))
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)?;
|
||||
|
||||
let mut out = Vec::with_capacity(members.len());
|
||||
let mut stats_stmt = battles_conn
|
||||
.prepare(
|
||||
"SELECT
|
||||
COUNT(DISTINCT session_id),
|
||||
SUM(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN victor_bool != 'Win' THEN 1 ELSE 0 END),
|
||||
COALESCE(SUM(ground_kills), 0),
|
||||
COALESCE(SUM(air_kills), 0),
|
||||
COALESCE(SUM(assists), 0),
|
||||
COALESCE(SUM(deaths), 0),
|
||||
COALESCE(SUM(score), 0)
|
||||
FROM player_games_hist
|
||||
WHERE team_id = ?1 AND UID = ?2",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
for (uid, nick, role, joined_unix, points) in members {
|
||||
let summary = stats_stmt
|
||||
.query_row(params![team_id, uid], |row| {
|
||||
let battles: i64 = row.get(0)?;
|
||||
let wins: i64 = row.get(1)?;
|
||||
let losses: i64 = row.get(2)?;
|
||||
let ground: i64 = row.get(3)?;
|
||||
let air: i64 = row.get(4)?;
|
||||
let assists: i64 = row.get(5)?;
|
||||
let deaths: i64 = row.get(6)?;
|
||||
let score: i64 = row.get(7)?;
|
||||
let total_kills = ground + air;
|
||||
Ok(PlayerSummary {
|
||||
uid: uid.clone(),
|
||||
nick: nick.clone(),
|
||||
role: role.clone(),
|
||||
joined_unix,
|
||||
points,
|
||||
sqb_points: if points == 0 {
|
||||
score + assists + total_kills
|
||||
} else {
|
||||
points
|
||||
},
|
||||
total_battles: battles,
|
||||
wins,
|
||||
losses,
|
||||
win_rate: percent(wins, battles),
|
||||
total_kills,
|
||||
ground_kills: ground,
|
||||
air_kills: air,
|
||||
assists,
|
||||
deaths,
|
||||
kdr: ratio(total_kills, deaths),
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?;
|
||||
out.push(summary);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn period_history_for(conn: &Connection, team_id: i64) -> Result<Vec<PeriodHistory>, ApiError> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT
|
||||
strftime('%Y-%m', endtime_unix, 'unixepoch') AS period,
|
||||
COUNT(DISTINCT session_id),
|
||||
SUM(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END),
|
||||
SUM(CASE WHEN victor_bool != 'Win' THEN 1 ELSE 0 END)
|
||||
FROM player_games_hist
|
||||
WHERE team_id = ?1 AND endtime_unix > 0
|
||||
GROUP BY period
|
||||
ORDER BY period ASC",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
stmt.query_map(params![team_id], |row| {
|
||||
let period: String = row.get(0)?;
|
||||
let battles: i64 = row.get(1)?;
|
||||
let wins: i64 = row.get(2)?;
|
||||
let losses: i64 = row.get(3)?;
|
||||
Ok(PeriodHistory {
|
||||
period,
|
||||
battles,
|
||||
wins,
|
||||
losses,
|
||||
win_rate: percent(wins, battles),
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn rating_history_for(conn: &Connection, team_id: i64) -> Result<Vec<RatingPoint>, ApiError> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT unix_time, COALESCE(total_score, 0)
|
||||
FROM teams_points
|
||||
WHERE team_id = ?1
|
||||
ORDER BY unix_time ASC",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
stmt.query_map(params![team_id], |row| {
|
||||
Ok(RatingPoint {
|
||||
timestamp: row.get(0)?,
|
||||
rating: row.get(1)?,
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn games_for(conn: &Connection, team_id: i64) -> Result<Vec<GameRow>, ApiError> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT
|
||||
p.session_id,
|
||||
COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp,
|
||||
m.mission_mode,
|
||||
CASE
|
||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
|
||||
ELSE 'Loss'
|
||||
END AS result,
|
||||
COUNT(DISTINCT p.UID),
|
||||
COALESCE(SUM(p.ground_kills), 0),
|
||||
COALESCE(SUM(p.air_kills), 0),
|
||||
COALESCE(SUM(p.assists), 0),
|
||||
COALESCE(SUM(p.captures), 0),
|
||||
COALESCE(SUM(p.deaths), 0),
|
||||
COALESCE(SUM(p.score), 0),
|
||||
COALESCE(SUM(p.missile_evades), 0),
|
||||
COALESCE(SUM(p.shell_interceptions), 0),
|
||||
COALESCE(SUM(p.team_kills_stat), 0),
|
||||
m.winning_team,
|
||||
m.losing_team
|
||||
FROM player_games_hist p
|
||||
LEFT JOIN match_summary m ON m.session_id = p.session_id
|
||||
WHERE p.team_id = ?1
|
||||
GROUP BY p.session_id
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 100",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
stmt.query_map(params![team_id], |row| {
|
||||
let session_id: String = row.get(0)?;
|
||||
let timestamp: i64 = row.get(1)?;
|
||||
let mission_mode: Option<String> = row.get(2)?;
|
||||
Ok(GameRow {
|
||||
session_id,
|
||||
timestamp,
|
||||
endtime_unix: timestamp,
|
||||
map_name: mission_mode.clone(),
|
||||
mission_mode,
|
||||
result: row.get(3)?,
|
||||
player_count: row.get(4)?,
|
||||
winning_team: row.get(14)?,
|
||||
losing_team: row.get(15)?,
|
||||
stats: GameStats {
|
||||
ground_kills: row.get(5)?,
|
||||
air_kills: row.get(6)?,
|
||||
assists: row.get(7)?,
|
||||
captures: row.get(8)?,
|
||||
deaths: row.get(9)?,
|
||||
score: row.get(10)?,
|
||||
missile_evades: row.get(11)?,
|
||||
shell_interceptions: row.get(12)?,
|
||||
team_kills_stat: row.get(13)?,
|
||||
},
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn validate_team_name(name: &str) -> Result<&str, ApiError> {
|
||||
let trimmed = name.trim();
|
||||
if trimmed.len() < 2 || trimmed.len() > MAX_TEAM_NAME_LENGTH {
|
||||
return Err(ApiError::bad_request(
|
||||
"Team name must be 2 to 80 characters",
|
||||
));
|
||||
}
|
||||
Ok(trimmed)
|
||||
}
|
||||
|
||||
fn decode_path_team(value: &str) -> Result<String, ApiError> {
|
||||
let decoded = urlencoding::decode(value)
|
||||
.map_err(|_| ApiError::bad_request("Invalid team name"))?
|
||||
.into_owned();
|
||||
validate_team_name(&decoded)?;
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
fn escape_like(value: &str) -> String {
|
||||
value
|
||||
.replace('\\', "\\\\")
|
||||
.replace('%', "\\%")
|
||||
.replace('_', "\\_")
|
||||
}
|
||||
|
||||
fn percent(part: i64, total: i64) -> f64 {
|
||||
if total <= 0 {
|
||||
0.0
|
||||
} else {
|
||||
(part as f64 / total as f64) * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
fn ratio(top: i64, bottom: i64) -> f64 {
|
||||
if bottom <= 0 {
|
||||
top as f64
|
||||
} else {
|
||||
top as f64 / bottom as f64
|
||||
}
|
||||
}
|
||||
|
||||
fn env_u16(key: &str) -> Option<u16> {
|
||||
env::var(key).ok()?.parse().ok()
|
||||
}
|
||||
|
||||
fn resolve_db_path(env_key: &str, default_file: &str) -> PathBuf {
|
||||
let raw = env::var(env_key).unwrap_or_else(|_| default_file.to_string());
|
||||
let expanded = expand_home(&raw);
|
||||
let path = PathBuf::from(expanded);
|
||||
if path.is_absolute() {
|
||||
path
|
||||
} else {
|
||||
env::current_dir()
|
||||
.unwrap_or_else(|_| PathBuf::from("."))
|
||||
.join(path)
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_home(value: &str) -> String {
|
||||
if value == "~" {
|
||||
return home_dir()
|
||||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|| value.to_string());
|
||||
}
|
||||
if let Some(rest) = value
|
||||
.strip_prefix("~/")
|
||||
.or_else(|| value.strip_prefix("~\\"))
|
||||
{
|
||||
if let Some(home) = home_dir() {
|
||||
return home.join(rest).display().to_string();
|
||||
}
|
||||
}
|
||||
value.to_string()
|
||||
}
|
||||
|
||||
fn home_dir() -> Option<PathBuf> {
|
||||
env::var_os("HOME")
|
||||
.or_else(|| env::var_os("USERPROFILE"))
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
fn load_root_env() {
|
||||
let candidates = [
|
||||
env::current_dir().ok().map(|path| path.join(".env")),
|
||||
env::current_dir()
|
||||
.ok()
|
||||
.and_then(|path| path.parent().map(|parent| parent.join(".env"))),
|
||||
Some(
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join(".env"),
|
||||
),
|
||||
];
|
||||
|
||||
for candidate in candidates.into_iter().flatten() {
|
||||
if candidate.exists() {
|
||||
load_env_file(&candidate);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load_env_file(path: &FsPath) {
|
||||
let Ok(contents) = fs::read_to_string(path) else {
|
||||
return;
|
||||
};
|
||||
for line in contents.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
let Some((key, value)) = trimmed.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
let key = key.trim();
|
||||
if key.is_empty() || env::var_os(key).is_some() {
|
||||
continue;
|
||||
}
|
||||
let value = value
|
||||
.trim()
|
||||
.trim_matches('"')
|
||||
.trim_matches('\'')
|
||||
.to_string();
|
||||
env::set_var(key, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":13465357961897998566,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Program Files\\Rust stable MSVC 1.94\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.94.0 (4a4ef493e 2026-03-02)\nbinary: rustc\ncommit-hash: 4a4ef493e3a1488c6e321570238084b38948f6db\ncommit-date: 2026-03-02\nhost: x86_64-pc-windows-msvc\nrelease: 1.94.0\nLLVM version: 21.1.8\n","stderr":""}},"successes":{}}
|
||||
@@ -0,0 +1,3 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
dd6ca575ec4431f0
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"portable-atomic\"]","target":14411119108718288063,"profile":15657897354478470176,"path":6717365521836368625,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\atomic-waker-da9a01f7dc236e82\\dep-lib-atomic_waker","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
78c7cc385d4285de
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"portable-atomic\"]","target":14411119108718288063,"profile":2241668132362809309,"path":6717365521836368625,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\atomic-waker-f1278d2645921f3c\\dep-lib-atomic_waker","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
040c93e36e855d88
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"arbitrary\", \"bytemuck\", \"example_generated\", \"serde\", \"serde_core\", \"std\"]","target":7691312148208718491,"profile":2241668132362809309,"path":8434845941539571929,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\bitflags-a8ec033f306034c3\\dep-lib-bitflags","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
2b1cfe2bdcc2f913
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"extra-platforms\", \"serde\", \"std\"]","target":11402411492164584411,"profile":13827760451848848284,"path":6232424548232413856,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\bytes-a1899f05397b67f9\\dep-lib-bytes","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
72bd6b8bd42622b0
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"extra-platforms\", \"serde\", \"std\"]","target":11402411492164584411,"profile":5585765287293540646,"path":6232424548232413856,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\bytes-cc0a20647e53726a\\dep-lib-bytes","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
645c0c4ba84f291f
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"jobserver\", \"parallel\"]","target":11042037588551934598,"profile":4333757155065362140,"path":5893973166268932723,"deps":[[9159843920629750842,"find_msvc_tools",false,13263039337708535718],[12678166843757613889,"shlex",false,9241398564909348994]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\cc-b8e5d38724e4a1e6\\dep-lib-cc","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
dd5891f43e03cafc
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"core\", \"rustc-dep-of-std\"]","target":13840298032947503755,"profile":2241668132362809309,"path":425904950434454382,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\cfg-if-3dcb42ca3a02dcb5\\dep-lib-cfg_if","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+1
@@ -0,0 +1 @@
|
||||
99703a4650db670d
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\"]","declared_features":"[\"alloc\", \"default\", \"std\"]","target":15245709686714427328,"profile":2241668132362809309,"path":4661389752810606431,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\fallible-iterator-2b1f1fdbcc3af240\\dep-lib-fallible_iterator","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+1
@@ -0,0 +1 @@
|
||||
3ea47666dc70db05
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"std\"]","target":16001337131876932863,"profile":2241668132362809309,"path":6866245534065373636,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\fallible-streaming-iterator-773a55ab8825561a\\dep-lib-fallible_streaming_iterator","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
a63f26ce01c80fb8
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[]","target":10620166500288925791,"profile":4333757155065362140,"path":14910102133086743437,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\find-msvc-tools-526db89c667947b5\\dep-lib-find_msvc_tools","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
af3a210f94c85f68
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"default\", \"std\"]","target":18077926938045032029,"profile":15657897354478470176,"path":12626290509863766455,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\foldhash-860ea31daecb2567\\dep-lib-foldhash","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
e83e84de7a3ab2a5
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[\"default\", \"std\"]","target":18077926938045032029,"profile":2241668132362809309,"path":12626290509863766455,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\foldhash-9dc18cd827dd0134\\dep-lib-foldhash","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
86b80bf85ce58506
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"std\"]","target":6496257856677244489,"profile":15657897354478470176,"path":2780369495299393720,"deps":[[6803352382179706244,"percent_encoding",false,203382068760923564]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\form_urlencoded-f95ea697b2465d1a\\dep-lib-form_urlencoded","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
1fb5ca1a3b1a9b25
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"futures-sink\", \"sink\", \"std\", \"unstable\"]","target":13634065851578929263,"profile":17467636112133979524,"path":16899676779384462205,"deps":[[302948626015856208,"futures_core",false,1643743490218323212]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-channel-d31c1762bab1da24\\dep-lib-futures_channel","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
a6246b8f05ae4be0
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"futures-sink\", \"sink\", \"std\", \"unstable\"]","target":13634065851578929263,"profile":13318305459243126790,"path":16899676779384462205,"deps":[[302948626015856208,"futures_core",false,13301172465390714160]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-channel-eff736dee8ee9cfc\\dep-lib-futures_channel","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
30fdd177e24197b8
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"portable-atomic\", \"std\", \"unstable\"]","target":9453135960607436725,"profile":13318305459243126790,"path":17521873979997666290,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-core-85d3f87fb4b487db\\dep-lib-futures_core","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
0ced52d4febfcf16
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"portable-atomic\", \"std\", \"unstable\"]","target":9453135960607436725,"profile":17467636112133979524,"path":17521873979997666290,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-core-cd18f53292d99fc1\\dep-lib-futures_core","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
b82ec0acee527533
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"std\", \"unstable\"]","target":13518091470260541623,"profile":17467636112133979524,"path":3102762548533649612,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-task-0723fba4205513af\\dep-lib-futures_task","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
ce660c091a8c68d3
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\"]","declared_features":"[\"alloc\", \"cfg-target-has-atomic\", \"default\", \"std\", \"unstable\"]","target":13518091470260541623,"profile":13318305459243126790,"path":3102762548533649612,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-task-12597ee60dfe15d1\\dep-lib-futures_task","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
22b07e2271a86017
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[\"alloc\", \"slab\"]","declared_features":"[\"alloc\", \"async-await\", \"async-await-macro\", \"bilock\", \"cfg-target-has-atomic\", \"channel\", \"compat\", \"default\", \"futures-channel\", \"futures-io\", \"futures-macro\", \"futures-sink\", \"futures_01\", \"io\", \"io-compat\", \"libc\", \"memchr\", \"portable-atomic\", \"sink\", \"slab\", \"spin\", \"std\", \"tokio-io\", \"unstable\", \"write-all-vectored\"]","target":1788798584831431502,"profile":13318305459243126790,"path":9137121785660462178,"deps":[[302948626015856208,"futures_core",false,13301172465390714160],[2251399859588827949,"pin_project_lite",false,4614563224716250225],[12256881686772805731,"futures_task",false,15233579783029548750],[14895711841936801505,"slab",false,10607738326241059162]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\futures-util-c4c26eb39c4b6d81\\dep-lib-futures_util","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{"$message_type":"diagnostic","message":"linker `link.exe` not found","code":null,"level":"error","spans":[],"children":[{"message":"program not found","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m\u001b[97m: linker `link.exe` not found\u001b[0m\n \u001b[1m\u001b[96m|\u001b[0m\n \u001b[1m\u001b[96m= \u001b[0m\u001b[1m\u001b[97mnote\u001b[0m: program not found\n\n"}
|
||||
{"$message_type":"diagnostic","message":"the msvc targets depend on the msvc linker but `link.exe` was not found","code":null,"level":"note","spans":[],"children":[],"rendered":"\u001b[1m\u001b[92mnote\u001b[0m\u001b[1m\u001b[97m: the msvc targets depend on the msvc linker but `link.exe` was not found\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option.","code":null,"level":"note","spans":[],"children":[],"rendered":"\u001b[1m\u001b[92mnote\u001b[0m\u001b[1m\u001b[97m: please ensure that Visual Studio 2017 or later, or Build Tools for Visual Studio were installed with the Visual C++ option.\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"VS Code is a different product, and is not sufficient.","code":null,"level":"note","spans":[],"children":[],"rendered":"\u001b[1m\u001b[92mnote\u001b[0m\u001b[1m\u001b[97m: VS Code is a different product, and is not sufficient.\u001b[0m\n\n"}
|
||||
{"$message_type":"diagnostic","message":"aborting due to 1 previous error","code":null,"level":"error","spans":[],"children":[],"rendered":"\u001b[1m\u001b[91merror\u001b[0m\u001b[1m\u001b[97m: aborting due to 1 previous error\u001b[0m\n\n"}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
41385b921dbbb8d2
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[]","target":12509520342503990962,"profile":2241668132362809309,"path":152072485705541812,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\httpdate-23a892f8f0a14996\\dep-lib-httpdate","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
10fc3430d7934e80
|
||||
@@ -0,0 +1 @@
|
||||
{"rustc":3674164131150989441,"features":"[]","declared_features":"[]","target":12509520342503990962,"profile":15657897354478470176,"path":152072485705541812,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\httpdate-974d134952b1c9dd\\dep-lib-httpdate","checksum":false}}],"rustflags":[],"config":8247474407144887393,"compile_kind":0}
|
||||
Binary file not shown.
@@ -0,0 +1 @@
|
||||
This file has an mtime of when this was started.
|
||||
@@ -0,0 +1 @@
|
||||
519fdf73fbee6519
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user