feat: add metadata-agregator gRPC client integration
- Add tonic/prost for gRPC client generation - Add proto definitions from metadata-agregator service - Add MetadataClient and MetadataService for gRPC communication - Add REST controller exposing metadata endpoints (search, get, sync) - Update config with metadata.endpoint setting - Update flake.nix with protobuf for proto compilation
This commit is contained in:
Generated
+462
-20
@@ -17,6 +17,28 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@@ -34,13 +56,46 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core 0.4.5",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"itoa",
|
||||
"matchit 0.7.3",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"sync_wrapper",
|
||||
"tower 0.5.3",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-core 0.5.6",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
@@ -50,7 +105,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"matchit 0.8.4",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
@@ -61,12 +116,32 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
@@ -190,18 +265,52 @@ dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
@@ -232,6 +341,12 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
@@ -290,6 +405,31 @@ dependencies = [
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.14.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
@@ -366,6 +506,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@@ -393,6 +534,19 @@ dependencies = [
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-timeout"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
|
||||
dependencies = [
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.20"
|
||||
@@ -410,7 +564,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -525,6 +679,16 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
@@ -553,6 +717,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
@@ -589,6 +762,12 @@ version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
@@ -622,6 +801,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
@@ -661,13 +846,20 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||
|
||||
[[package]]
|
||||
name = "music-agregator"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum 0.8.9",
|
||||
"base64",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"roxmltree",
|
||||
"serde",
|
||||
@@ -675,6 +867,8 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-build",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -709,6 +903,36 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
@@ -758,6 +982,58 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"itertools",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
"petgraph",
|
||||
"prettyplease",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"regex",
|
||||
"syn",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.13.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psl-types"
|
||||
version = "2.0.11"
|
||||
@@ -787,7 +1063,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"socket2 0.6.3",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -803,7 +1079,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand",
|
||||
"rand 0.9.4",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
@@ -824,7 +1100,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.6.3",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -850,14 +1126,35 @@ version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -867,7 +1164,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -879,6 +1185,18 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -928,7 +1246,7 @@ dependencies = [
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
@@ -964,6 +1282,19 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.40"
|
||||
@@ -1089,7 +1420,7 @@ version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -1123,6 +1454,16 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.3"
|
||||
@@ -1176,6 +1517,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
@@ -1271,7 +1625,7 @@ dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.3",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -1297,6 +1651,94 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"axum 0.7.9",
|
||||
"base64",
|
||||
"bytes",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-timeout",
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-build"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
|
||||
dependencies = [
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"prost-build",
|
||||
"prost-types",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.6",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
@@ -1326,7 +1768,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1589,7 +2031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
@@ -1602,7 +2044,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"semver",
|
||||
]
|
||||
|
||||
@@ -1757,7 +2199,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
@@ -1788,7 +2230,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -1807,7 +2249,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"indexmap 2.14.0",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
|
||||
@@ -23,6 +23,11 @@ thiserror = "2"
|
||||
url = "2"
|
||||
roxmltree = "0.20"
|
||||
base64 = "0.22"
|
||||
tonic = "0.12"
|
||||
prost = "0.13"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.12"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure()
|
||||
.build_server(false)
|
||||
.build_client(true)
|
||||
.compile_protos(&["proto/metadata/v1/metadata.proto"], &["proto"])?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
database:
|
||||
url: "postgresql://music:music@localhost:5433/music_aggregator"
|
||||
|
||||
metadata:
|
||||
endpoint: "http://localhost:50051"
|
||||
|
||||
indexers:
|
||||
- name: "Jackett"
|
||||
url: "http://localhost:9117"
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
version = "0.1.0";
|
||||
src = ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
nativeBuildInputs = [ pkgs.protobuf ];
|
||||
};
|
||||
in
|
||||
{
|
||||
@@ -63,6 +64,7 @@
|
||||
pre-commit
|
||||
gitleaks
|
||||
plantuml
|
||||
protobuf
|
||||
|
||||
rustc
|
||||
cargo
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package metadata.v1;
|
||||
|
||||
option go_package = "github.com/metadata-agregator/pkg/gen/metadata/v1;metadatav1";
|
||||
|
||||
enum Provider {
|
||||
PROVIDER_UNSPECIFIED = 0;
|
||||
PROVIDER_MUSICBRAINZ = 1;
|
||||
}
|
||||
|
||||
// MetadataService provides music metadata aggregation.
|
||||
service MetadataService {
|
||||
// GetArtist retrieves an artist by ID or external source ID.
|
||||
rpc GetArtist(GetArtistRequest) returns (Artist);
|
||||
|
||||
// SearchArtists searches for artists by name.
|
||||
rpc SearchArtists(SearchArtistsRequest) returns (SearchArtistsResponse);
|
||||
|
||||
// GetAlbum retrieves an album by ID.
|
||||
rpc GetAlbum(GetAlbumRequest) returns (Album);
|
||||
|
||||
// GetArtistAlbums retrieves all albums by an artist.
|
||||
rpc GetArtistAlbums(GetArtistAlbumsRequest) returns (GetArtistAlbumsResponse);
|
||||
|
||||
// GetTrack retrieves a track by ID.
|
||||
rpc GetTrack(GetTrackRequest) returns (Track);
|
||||
|
||||
// GetAlbumTracks retrieves all tracks on an album.
|
||||
rpc GetAlbumTracks(GetAlbumTracksRequest) returns (GetAlbumTracksResponse);
|
||||
|
||||
// SyncArtist triggers ingestion of an artist from external sources.
|
||||
rpc SyncArtist(SyncArtistRequest) returns (SyncArtistResponse);
|
||||
}
|
||||
|
||||
// Requests
|
||||
|
||||
message GetArtistRequest {
|
||||
oneof identifier {
|
||||
string id = 1; // Internal UUID
|
||||
ExternalID external = 2; // External source ID (e.g., musicbrainz MBID)
|
||||
}
|
||||
Provider provider = 3; // UNSPECIFIED = query all providers
|
||||
}
|
||||
|
||||
message SearchArtistsRequest {
|
||||
string query = 1;
|
||||
int32 limit = 2;
|
||||
int32 offset = 3;
|
||||
Provider provider = 4;
|
||||
}
|
||||
|
||||
message GetAlbumRequest {
|
||||
oneof identifier {
|
||||
string id = 1;
|
||||
ExternalID external = 2;
|
||||
}
|
||||
Provider provider = 3;
|
||||
}
|
||||
|
||||
message GetArtistAlbumsRequest {
|
||||
string artist_id = 1;
|
||||
int32 limit = 2;
|
||||
int32 offset = 3;
|
||||
Provider provider = 4;
|
||||
}
|
||||
|
||||
message GetTrackRequest {
|
||||
oneof identifier {
|
||||
string id = 1;
|
||||
ExternalID external = 2;
|
||||
string isrc = 3;
|
||||
}
|
||||
Provider provider = 4;
|
||||
}
|
||||
|
||||
message GetAlbumTracksRequest {
|
||||
string album_id = 1;
|
||||
Provider provider = 2;
|
||||
}
|
||||
|
||||
message SyncArtistRequest {
|
||||
oneof target {
|
||||
string name = 1;
|
||||
ExternalID external = 2;
|
||||
}
|
||||
Provider provider = 3;
|
||||
}
|
||||
|
||||
// Responses
|
||||
|
||||
message SearchArtistsResponse {
|
||||
repeated Artist artists = 1;
|
||||
int32 total = 2;
|
||||
}
|
||||
|
||||
message GetArtistAlbumsResponse {
|
||||
repeated Album albums = 1;
|
||||
int32 total = 2;
|
||||
}
|
||||
|
||||
message GetAlbumTracksResponse {
|
||||
repeated Track tracks = 1;
|
||||
}
|
||||
|
||||
message SyncArtistResponse {
|
||||
Artist artist = 1;
|
||||
int32 albums_synced = 2;
|
||||
int32 tracks_synced = 3;
|
||||
}
|
||||
|
||||
// Core Entities
|
||||
|
||||
message Artist {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string sort_name = 3;
|
||||
string artist_type = 4; // person, group, orchestra, etc.
|
||||
string country = 5;
|
||||
string formed_date = 6;
|
||||
string disbanded_date = 7;
|
||||
string description = 8;
|
||||
string image_url = 9;
|
||||
repeated Genre genres = 10;
|
||||
repeated ExternalID external_ids = 11;
|
||||
}
|
||||
|
||||
message Album {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
string album_type = 3; // album, ep, single, compilation
|
||||
string release_date = 4;
|
||||
string upc = 5;
|
||||
int32 total_tracks = 6;
|
||||
int32 total_discs = 7;
|
||||
string cover_url = 8;
|
||||
repeated ArtistCredit artists = 9;
|
||||
Label label = 10;
|
||||
repeated Genre genres = 11;
|
||||
repeated ExternalID external_ids = 12;
|
||||
}
|
||||
|
||||
message Track {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
int32 duration_ms = 3;
|
||||
string isrc = 4;
|
||||
bool explicit = 5;
|
||||
int32 disc_number = 6;
|
||||
int32 track_number = 7;
|
||||
repeated ArtistCredit artists = 8;
|
||||
Work work = 9;
|
||||
repeated ExternalID external_ids = 10;
|
||||
}
|
||||
|
||||
message Work {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
string work_type = 3;
|
||||
string language = 4;
|
||||
repeated ArtistCredit composers = 5;
|
||||
}
|
||||
|
||||
message Label {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
string country = 3;
|
||||
}
|
||||
|
||||
message Genre {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message ArtistCredit {
|
||||
Artist artist = 1;
|
||||
string role = 2; // primary, featured, remixer, producer
|
||||
int32 position = 3;
|
||||
string join_phrase = 4; // " & ", " feat. ", etc.
|
||||
}
|
||||
|
||||
message ExternalID {
|
||||
string source = 1; // musicbrainz, spotify, discogs, etc.
|
||||
string source_id = 2;
|
||||
string url = 3;
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::metadata::proto::{Album, Artist, ArtistCredit, ExternalId, Genre, Label, Track, Work};
|
||||
use crate::AppState;
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/artists/search", get(search_artists))
|
||||
.route("/artists/:id", get(get_artist))
|
||||
.route("/artists/:id/albums", get(get_artist_albums))
|
||||
.route("/artists/sync", post(sync_artist))
|
||||
.route("/albums/:id", get(get_album))
|
||||
.route("/albums/:id/tracks", get(get_album_tracks))
|
||||
.route("/status", get(connection_status))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchQuery {
|
||||
pub q: String,
|
||||
pub limit: Option<i32>,
|
||||
pub offset: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ArtistResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub sort_name: String,
|
||||
pub artist_type: String,
|
||||
pub country: String,
|
||||
pub formed_date: String,
|
||||
pub disbanded_date: String,
|
||||
pub description: String,
|
||||
pub image_url: String,
|
||||
pub genres: Vec<GenreResponse>,
|
||||
pub external_ids: Vec<ExternalIdResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct GenreResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ExternalIdResponse {
|
||||
pub source: String,
|
||||
pub source_id: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AlbumResponse {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub album_type: String,
|
||||
pub release_date: String,
|
||||
pub upc: String,
|
||||
pub total_tracks: i32,
|
||||
pub total_discs: i32,
|
||||
pub cover_url: String,
|
||||
pub artists: Vec<ArtistCreditResponse>,
|
||||
pub label: Option<LabelResponse>,
|
||||
pub genres: Vec<GenreResponse>,
|
||||
pub external_ids: Vec<ExternalIdResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ArtistCreditResponse {
|
||||
pub artist: Option<ArtistResponse>,
|
||||
pub role: String,
|
||||
pub position: i32,
|
||||
pub join_phrase: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct LabelResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TrackResponse {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub duration_ms: i32,
|
||||
pub isrc: String,
|
||||
pub explicit: bool,
|
||||
pub disc_number: i32,
|
||||
pub track_number: i32,
|
||||
pub artists: Vec<ArtistCreditResponse>,
|
||||
pub work: Option<WorkResponse>,
|
||||
pub external_ids: Vec<ExternalIdResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct WorkResponse {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub work_type: String,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SearchArtistsResponse {
|
||||
pub artists: Vec<ArtistResponse>,
|
||||
pub total: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ArtistAlbumsResponse {
|
||||
pub albums: Vec<AlbumResponse>,
|
||||
pub total: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AlbumTracksResponse {
|
||||
pub tracks: Vec<TrackResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SyncRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct SyncResponse {
|
||||
pub artist: Option<ArtistResponse>,
|
||||
pub albums_synced: i32,
|
||||
pub tracks_synced: i32,
|
||||
}
|
||||
|
||||
fn map_genre(g: &Genre) -> GenreResponse {
|
||||
GenreResponse {
|
||||
id: g.id.clone(),
|
||||
name: g.name.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_external_id(e: &ExternalId) -> ExternalIdResponse {
|
||||
ExternalIdResponse {
|
||||
source: e.source.clone(),
|
||||
source_id: e.source_id.clone(),
|
||||
url: e.url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_artist(a: &Artist) -> ArtistResponse {
|
||||
ArtistResponse {
|
||||
id: a.id.clone(),
|
||||
name: a.name.clone(),
|
||||
sort_name: a.sort_name.clone(),
|
||||
artist_type: a.artist_type.clone(),
|
||||
country: a.country.clone(),
|
||||
formed_date: a.formed_date.clone(),
|
||||
disbanded_date: a.disbanded_date.clone(),
|
||||
description: a.description.clone(),
|
||||
image_url: a.image_url.clone(),
|
||||
genres: a.genres.iter().map(map_genre).collect(),
|
||||
external_ids: a.external_ids.iter().map(map_external_id).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_label(l: &Label) -> LabelResponse {
|
||||
LabelResponse {
|
||||
id: l.id.clone(),
|
||||
name: l.name.clone(),
|
||||
country: l.country.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_artist_credit(c: &ArtistCredit) -> ArtistCreditResponse {
|
||||
ArtistCreditResponse {
|
||||
artist: c.artist.as_ref().map(map_artist),
|
||||
role: c.role.clone(),
|
||||
position: c.position,
|
||||
join_phrase: c.join_phrase.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_album(a: &Album) -> AlbumResponse {
|
||||
AlbumResponse {
|
||||
id: a.id.clone(),
|
||||
title: a.title.clone(),
|
||||
album_type: a.album_type.clone(),
|
||||
release_date: a.release_date.clone(),
|
||||
upc: a.upc.clone(),
|
||||
total_tracks: a.total_tracks,
|
||||
total_discs: a.total_discs,
|
||||
cover_url: a.cover_url.clone(),
|
||||
artists: a.artists.iter().map(map_artist_credit).collect(),
|
||||
label: a.label.as_ref().map(map_label),
|
||||
genres: a.genres.iter().map(map_genre).collect(),
|
||||
external_ids: a.external_ids.iter().map(map_external_id).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_work(w: &Work) -> WorkResponse {
|
||||
WorkResponse {
|
||||
id: w.id.clone(),
|
||||
title: w.title.clone(),
|
||||
work_type: w.work_type.clone(),
|
||||
language: w.language.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_track(t: &Track) -> TrackResponse {
|
||||
TrackResponse {
|
||||
id: t.id.clone(),
|
||||
title: t.title.clone(),
|
||||
duration_ms: t.duration_ms,
|
||||
isrc: t.isrc.clone(),
|
||||
explicit: t.explicit,
|
||||
disc_number: t.disc_number,
|
||||
track_number: t.track_number,
|
||||
artists: t.artists.iter().map(map_artist_credit).collect(),
|
||||
work: t.work.as_ref().map(map_work),
|
||||
external_ids: t.external_ids.iter().map(map_external_id).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn search_artists(
|
||||
State(state): State<AppState>,
|
||||
Query(query): Query<SearchQuery>,
|
||||
) -> Result<Json<SearchArtistsResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let response = state
|
||||
.metadata_service
|
||||
.search_artists(&query.q, query.limit, query.offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(SearchArtistsResponse {
|
||||
artists: response.artists.iter().map(map_artist).collect(),
|
||||
total: response.total,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_artist(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<ArtistResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let artist = state
|
||||
.metadata_service
|
||||
.get_artist(&id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(map_artist(&artist)))
|
||||
}
|
||||
|
||||
async fn get_artist_albums(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Query(query): Query<PaginationQuery>,
|
||||
) -> Result<Json<ArtistAlbumsResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let response = state
|
||||
.metadata_service
|
||||
.get_artist_albums(&id, query.limit, query.offset)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(ArtistAlbumsResponse {
|
||||
albums: response.albums.iter().map(map_album).collect(),
|
||||
total: response.total,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PaginationQuery {
|
||||
pub limit: Option<i32>,
|
||||
pub offset: Option<i32>,
|
||||
}
|
||||
|
||||
async fn get_album(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<AlbumResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let album = state
|
||||
.metadata_service
|
||||
.get_album(&id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(map_album(&album)))
|
||||
}
|
||||
|
||||
async fn get_album_tracks(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<AlbumTracksResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let response = state
|
||||
.metadata_service
|
||||
.get_album_tracks(&id)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(AlbumTracksResponse {
|
||||
tracks: response.tracks.iter().map(map_track).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn sync_artist(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<SyncRequest>,
|
||||
) -> Result<Json<SyncResponse>, (StatusCode, String)> {
|
||||
let state = state.read().await;
|
||||
let response = state
|
||||
.metadata_service
|
||||
.sync_artist(&req.name)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::BAD_GATEWAY, e.to_string()))?;
|
||||
|
||||
Ok(Json(SyncResponse {
|
||||
artist: response.artist.as_ref().map(map_artist),
|
||||
albums_synced: response.albums_synced,
|
||||
tracks_synced: response.tracks_synced,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StatusResponse {
|
||||
pub connected: bool,
|
||||
}
|
||||
|
||||
async fn connection_status(State(state): State<AppState>) -> Json<StatusResponse> {
|
||||
let state = state.read().await;
|
||||
Json(StatusResponse {
|
||||
connected: state.metadata_service.is_connected(),
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod indexer_controller;
|
||||
mod metadata_controller;
|
||||
mod torrent_controller;
|
||||
|
||||
use axum::{
|
||||
@@ -23,6 +24,7 @@ pub fn routes(state: AppState) -> Router {
|
||||
.route("/stats", get(get_stats))
|
||||
.nest("/indexers", indexer_controller::routes())
|
||||
.nest("/torrents", torrent_controller::routes())
|
||||
.nest("/metadata", metadata_controller::routes())
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,16 @@ pub enum ConfigError {
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Config {
|
||||
pub database: DatabaseConfig,
|
||||
pub metadata: MetadataConfig,
|
||||
pub indexers: Vec<IndexerConfig>,
|
||||
pub torrent: TorrentConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct MetadataConfig {
|
||||
pub endpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct DatabaseConfig {
|
||||
pub url: String,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod api;
|
||||
pub mod config;
|
||||
pub mod indexer;
|
||||
pub mod metadata;
|
||||
pub mod models;
|
||||
pub mod services;
|
||||
pub mod torrent;
|
||||
@@ -12,17 +13,20 @@ pub struct AppServices {
|
||||
pub aggregator: services::Aggregator,
|
||||
pub indexer_service: services::IndexerService,
|
||||
pub torrent_service: services::TorrentService,
|
||||
pub metadata_service: services::MetadataService,
|
||||
}
|
||||
|
||||
impl AppServices {
|
||||
pub fn new(
|
||||
indexer_service: services::IndexerService,
|
||||
torrent_service: services::TorrentService,
|
||||
metadata_service: services::MetadataService,
|
||||
) -> Self {
|
||||
Self {
|
||||
aggregator: services::Aggregator::new(),
|
||||
indexer_service,
|
||||
torrent_service,
|
||||
metadata_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -4,7 +4,7 @@ use tokio::sync::RwLock;
|
||||
use axum::Router;
|
||||
use music_agregator::{
|
||||
api, config,
|
||||
services::{IndexerService, TorrentService},
|
||||
services::{IndexerService, MetadataService, TorrentService},
|
||||
AppServices, AppState,
|
||||
};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
@@ -60,9 +60,26 @@ async fn main() {
|
||||
TorrentService::new()
|
||||
};
|
||||
|
||||
let mut metadata_service = MetadataService::new(&config.metadata.endpoint);
|
||||
match metadata_service.connect().await {
|
||||
Ok(()) => {
|
||||
tracing::info!(
|
||||
"connected to metadata service at {}",
|
||||
config.metadata.endpoint
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"failed to connect to metadata service: {} (continuing without metadata)",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let state: AppState = Arc::new(RwLock::new(AppServices::new(
|
||||
indexer_service,
|
||||
torrent_service,
|
||||
metadata_service,
|
||||
)));
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use thiserror::Error;
|
||||
use tonic::transport::Channel;
|
||||
|
||||
use super::proto::{
|
||||
get_album_request, get_artist_request, metadata_service_client::MetadataServiceClient,
|
||||
sync_artist_request, Album, Artist, GetAlbumRequest, GetAlbumTracksRequest,
|
||||
GetAlbumTracksResponse, GetArtistAlbumsRequest, GetArtistAlbumsResponse, GetArtistRequest,
|
||||
Provider, SearchArtistsRequest, SearchArtistsResponse, SyncArtistRequest, SyncArtistResponse,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MetadataClientError {
|
||||
#[error("connection failed: {0}")]
|
||||
ConnectionFailed(String),
|
||||
|
||||
#[error("request failed: {0}")]
|
||||
RequestFailed(#[from] tonic::Status),
|
||||
|
||||
#[error("transport error: {0}")]
|
||||
Transport(#[from] tonic::transport::Error),
|
||||
}
|
||||
|
||||
pub struct MetadataClient {
|
||||
client: MetadataServiceClient<Channel>,
|
||||
}
|
||||
|
||||
impl MetadataClient {
|
||||
pub async fn connect(endpoint: &str) -> Result<Self, MetadataClientError> {
|
||||
let client = MetadataServiceClient::connect(endpoint.to_string()).await?;
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub async fn search_artists(
|
||||
&mut self,
|
||||
query: &str,
|
||||
limit: Option<i32>,
|
||||
offset: Option<i32>,
|
||||
) -> Result<SearchArtistsResponse, MetadataClientError> {
|
||||
let request = SearchArtistsRequest {
|
||||
query: query.to_string(),
|
||||
limit: limit.unwrap_or(20),
|
||||
offset: offset.unwrap_or(0),
|
||||
provider: Provider::Unspecified as i32,
|
||||
};
|
||||
|
||||
let response = self.client.search_artists(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_artist(&mut self, id: &str) -> Result<Artist, MetadataClientError> {
|
||||
let request = GetArtistRequest {
|
||||
identifier: Some(get_artist_request::Identifier::Id(id.to_string())),
|
||||
provider: Provider::Unspecified as i32,
|
||||
};
|
||||
|
||||
let response = self.client.get_artist(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_artist_albums(
|
||||
&mut self,
|
||||
artist_id: &str,
|
||||
limit: Option<i32>,
|
||||
offset: Option<i32>,
|
||||
) -> Result<GetArtistAlbumsResponse, MetadataClientError> {
|
||||
let request = GetArtistAlbumsRequest {
|
||||
artist_id: artist_id.to_string(),
|
||||
limit: limit.unwrap_or(50),
|
||||
offset: offset.unwrap_or(0),
|
||||
provider: Provider::Unspecified as i32,
|
||||
};
|
||||
|
||||
let response = self.client.get_artist_albums(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_album(&mut self, id: &str) -> Result<Album, MetadataClientError> {
|
||||
let request = GetAlbumRequest {
|
||||
identifier: Some(get_album_request::Identifier::Id(id.to_string())),
|
||||
provider: Provider::Unspecified as i32,
|
||||
};
|
||||
|
||||
let response = self.client.get_album(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn get_album_tracks(
|
||||
&mut self,
|
||||
album_id: &str,
|
||||
) -> Result<GetAlbumTracksResponse, MetadataClientError> {
|
||||
let request = GetAlbumTracksRequest {
|
||||
album_id: album_id.to_string(),
|
||||
provider: Provider::Unspecified as i32,
|
||||
};
|
||||
|
||||
let response = self.client.get_album_tracks(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn sync_artist(
|
||||
&mut self,
|
||||
name: &str,
|
||||
) -> Result<SyncArtistResponse, MetadataClientError> {
|
||||
let request = SyncArtistRequest {
|
||||
target: Some(sync_artist_request::Target::Name(name.to_string())),
|
||||
provider: Provider::Musicbrainz as i32,
|
||||
};
|
||||
|
||||
let response = self.client.sync_artist(request).await?;
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
mod client;
|
||||
|
||||
pub use client::{MetadataClient, MetadataClientError};
|
||||
|
||||
pub mod proto {
|
||||
tonic::include_proto!("metadata.v1");
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::metadata::{MetadataClient, MetadataClientError};
|
||||
|
||||
pub struct MetadataService {
|
||||
client: Option<Arc<Mutex<MetadataClient>>>,
|
||||
endpoint: String,
|
||||
}
|
||||
|
||||
impl MetadataService {
|
||||
pub fn new(endpoint: &str) -> Self {
|
||||
Self {
|
||||
client: None,
|
||||
endpoint: endpoint.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect(&mut self) -> Result<(), MetadataClientError> {
|
||||
let client = MetadataClient::connect(&self.endpoint).await?;
|
||||
self.client = Some(Arc::new(Mutex::new(client)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_connected(&self) -> bool {
|
||||
self.client.is_some()
|
||||
}
|
||||
|
||||
fn client(&self) -> Result<Arc<Mutex<MetadataClient>>, MetadataClientError> {
|
||||
self.client
|
||||
.clone()
|
||||
.ok_or_else(|| MetadataClientError::ConnectionFailed("not connected".into()))
|
||||
}
|
||||
|
||||
pub async fn search_artists(
|
||||
&self,
|
||||
query: &str,
|
||||
limit: Option<i32>,
|
||||
offset: Option<i32>,
|
||||
) -> Result<crate::metadata::proto::SearchArtistsResponse, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.search_artists(query, limit, offset).await
|
||||
}
|
||||
|
||||
pub async fn get_artist(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<crate::metadata::proto::Artist, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.get_artist(id).await
|
||||
}
|
||||
|
||||
pub async fn get_artist_albums(
|
||||
&self,
|
||||
artist_id: &str,
|
||||
limit: Option<i32>,
|
||||
offset: Option<i32>,
|
||||
) -> Result<crate::metadata::proto::GetArtistAlbumsResponse, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.get_artist_albums(artist_id, limit, offset).await
|
||||
}
|
||||
|
||||
pub async fn get_album(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<crate::metadata::proto::Album, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.get_album(id).await
|
||||
}
|
||||
|
||||
pub async fn get_album_tracks(
|
||||
&self,
|
||||
album_id: &str,
|
||||
) -> Result<crate::metadata::proto::GetAlbumTracksResponse, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.get_album_tracks(album_id).await
|
||||
}
|
||||
|
||||
pub async fn sync_artist(
|
||||
&self,
|
||||
name: &str,
|
||||
) -> Result<crate::metadata::proto::SyncArtistResponse, MetadataClientError> {
|
||||
let client = self.client()?;
|
||||
let mut guard = client.lock().await;
|
||||
guard.sync_artist(name).await
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
mod indexer_service;
|
||||
mod metadata_service;
|
||||
mod torrent_service;
|
||||
|
||||
pub use indexer_service::{IndexerInfo, IndexerService};
|
||||
pub use metadata_service::MetadataService;
|
||||
pub use torrent_service::TorrentService;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
Reference in New Issue
Block a user