Removed last.fm support

This commit is contained in:
Antoine Gersant 2024-10-06 00:29:23 -07:00
parent 2c2b12f536
commit a5061dfc92
18 changed files with 35 additions and 1046 deletions

View file

@ -17,6 +17,7 @@
- Added a new `/peaks` endpoint which returns audio signal peaks that can be used to draw waveform visualizations.
- The `/thumbnail` endpoint supports a new size labeled `tiny` which returns 40x40px images.
- Persistent data, such as playlists, is now saved in a directory that may be configured with the `--data` CLI option or the `POLARIS_DATA_DIR` environment variable.
- Removed last.fm integration due to maintenance concerns (abandoned libraries, broken account linking) and mismatch with project goals.
### Web client

468
Cargo.lock generated
View file

@ -213,7 +213,7 @@ dependencies = [
"auto-future",
"axum",
"bytes",
"cookie 0.18.1",
"cookie",
"http 1.1.0",
"http-body-util",
"hyper",
@ -252,12 +252,6 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.7"
@ -344,12 +338,6 @@ dependencies = [
"orion",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytecount"
version = "0.6.8"
@ -406,7 +394,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
dependencies = [
"camino",
"cargo-platform",
"semver 1.0.23",
"semver",
"serde",
"serde_json",
]
@ -433,12 +421,6 @@ dependencies = [
"stacker",
]
[[package]]
name = "chunked_transfer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
name = "color_quant"
version = "1.1.0"
@ -460,49 +442,16 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_fn"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d"
[[package]]
name = "cookie"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
dependencies = [
"percent-encoding",
"time 0.2.27",
"version_check",
]
[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
dependencies = [
"time 0.3.36",
"time",
"version_check",
]
[[package]]
name = "cookie_store"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3"
dependencies = [
"cookie 0.14.4",
"idna 0.2.3",
"log",
"publicsuffix",
"serde",
"serde_json",
"time 0.2.27",
"url",
]
[[package]]
name = "cpufeatures"
version = "0.2.12"
@ -633,12 +582,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dotenvy"
version = "0.15.7"
@ -662,7 +605,7 @@ checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602"
dependencies = [
"cc",
"memchr",
"rustc_version 0.4.0",
"rustc_version",
"toml 0.8.19",
"vswhom",
"winreg",
@ -800,7 +743,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"futures-core",
"futures-sink",
"spin 0.9.8",
"spin",
]
[[package]]
@ -995,7 +938,7 @@ dependencies = [
"http 1.1.0",
"httpdate",
"mime",
"sha1 0.10.6",
"sha1",
]
[[package]]
@ -1166,17 +1109,6 @@ dependencies = [
"flate2",
]
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.5.0"
@ -1219,15 +1151,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lasso2"
version = "0.8.2"
@ -1244,7 +1167,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin 0.9.8",
"spin",
]
[[package]]
@ -1313,12 +1236,6 @@ dependencies = [
"serde",
]
[[package]]
name = "matches"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "matchit"
version = "0.7.3"
@ -1335,12 +1252,6 @@ dependencies = [
"digest",
]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.7.4"
@ -1459,7 +1370,7 @@ dependencies = [
"native_model",
"redb 1.5.1",
"redb 2.1.3",
"semver 1.0.23",
"semver",
"serde",
"skeptic",
"thiserror",
@ -1826,7 +1737,6 @@ dependencies = [
"rand",
"rayon",
"regex",
"rustfm-scrobble",
"sd-notify",
"serde",
"serde_derive",
@ -1843,7 +1753,7 @@ dependencies = [
"tower-http",
"trie-rs",
"unicase",
"ureq 2.10.0",
"ureq",
"winres",
]
@ -1890,12 +1800,6 @@ dependencies = [
"toml 0.5.11",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.86"
@ -1914,16 +1818,6 @@ dependencies = [
"cc",
]
[[package]]
name = "publicsuffix"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f"
dependencies = [
"idna 0.2.3",
"url",
]
[[package]]
name = "pulldown-cmark"
version = "0.9.6"
@ -1935,15 +1829,6 @@ dependencies = [
"unicase",
]
[[package]]
name = "qstring"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e"
dependencies = [
"percent-encoding",
]
[[package]]
name = "quote"
version = "1.0.36"
@ -2078,21 +1963,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]]
name = "ring"
version = "0.17.8"
@ -2103,8 +1973,8 @@ dependencies = [
"cfg-if",
"getrandom",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
@ -2150,22 +2020,13 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver 1.0.23",
"semver",
]
[[package]]
@ -2183,19 +2044,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "rustfm-scrobble"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c46a75fb6409a528f7e0d8e99826684f88461d1b0d0edeec60d82e3f554dad5"
dependencies = [
"md5",
"serde",
"serde_json",
"ureq 1.5.5",
"wrapped-vec",
]
[[package]]
name = "rustix"
version = "0.38.34"
@ -2209,19 +2057,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64 0.13.1",
"log",
"ring 0.16.20",
"sct",
"webpki",
]
[[package]]
name = "rustls"
version = "0.23.12"
@ -2230,7 +2065,7 @@ checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
dependencies = [
"log",
"once_cell",
"ring 0.17.8",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
@ -2249,9 +2084,9 @@ version = "0.102.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
dependencies = [
"ring 0.17.8",
"ring",
"rustls-pki-types",
"untrusted 0.9.0",
"untrusted",
]
[[package]]
@ -2281,31 +2116,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
"ring 0.16.20",
"untrusted 0.7.1",
]
[[package]]
name = "sd-notify"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4646d6f919800cd25c50edb49438a1381e2cd4833c027e75e8897981c50b8b5e"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.23"
@ -2315,12 +2131,6 @@ dependencies = [
"serde",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.204"
@ -2384,15 +2194,6 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
dependencies = [
"sha1_smol",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -2404,12 +2205,6 @@ dependencies = [
"digest",
]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "sha2"
version = "0.10.8"
@ -2445,7 +2240,7 @@ checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
dependencies = [
"log",
"termcolor",
"time 0.3.36",
"time",
]
[[package]]
@ -2491,12 +2286,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
@ -2646,7 +2435,7 @@ dependencies = [
"percent-encoding",
"rand",
"rsa",
"sha1 0.10.6",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
@ -2730,64 +2519,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "standback"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
dependencies = [
"version_check",
]
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version 0.2.3",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_derive",
"syn 1.0.109",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde",
"serde_derive",
"serde_json",
"sha1 0.6.1",
"syn 1.0.109",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "strength_reduce"
version = "0.2.4"
@ -3083,21 +2814,6 @@ dependencies = [
"syn 2.0.72",
]
[[package]]
name = "time"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
dependencies = [
"const_fn",
"libc",
"standback",
"stdweb",
"time-macros 0.1.1",
"version_check",
"winapi",
]
[[package]]
name = "time"
version = "0.3.36"
@ -3112,7 +2828,7 @@ dependencies = [
"powerfmt",
"serde",
"time-core",
"time-macros 0.2.18",
"time-macros",
]
[[package]]
@ -3121,16 +2837,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
dependencies = [
"proc-macro-hack",
"time-macros-impl",
]
[[package]]
name = "time-macros"
version = "0.2.18"
@ -3141,19 +2847,6 @@ dependencies = [
"time-core",
]
[[package]]
name = "time-macros-impl"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"standback",
"syn 1.0.109",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
@ -3446,37 +3139,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b8b063c2d59218ae09f22b53c42eaad0d53516457905f5235ca4bc9e99daa71"
dependencies = [
"base64 0.13.1",
"chunked_transfer",
"cookie 0.14.4",
"cookie_store",
"log",
"once_cell",
"qstring",
"rustls 0.19.1",
"url",
"webpki",
"webpki-roots 0.21.1",
]
[[package]]
name = "ureq"
version = "2.10.0"
@ -3486,10 +3154,10 @@ dependencies = [
"base64 0.22.1",
"log",
"once_cell",
"rustls 0.23.12",
"rustls",
"rustls-pki-types",
"url",
"webpki-roots 0.26.3",
"webpki-roots",
]
[[package]]
@ -3499,7 +3167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
dependencies = [
"form_urlencoded",
"idna 0.5.0",
"idna",
"percent-encoding",
]
@ -3566,89 +3234,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.72",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring 0.16.20",
"untrusted 0.7.1",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]]
name = "webpki-roots"
version = "0.26.3"
@ -3887,17 +3472,6 @@ dependencies = [
"toml 0.5.11",
]
[[package]]
name = "wrapped-vec"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85e08702c1e919669e1e90213c9c75ea4bb689d0f3970347e2b37c04600b4e5"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "yansi"
version = "0.5.1"

View file

@ -33,11 +33,9 @@ nohash-hasher = "0.2.0"
num_cpus = "1.14.0"
opus_headers = "0.1.2"
pbkdf2 = "0.11"
percent-encoding = "2.2"
rand = "0.8"
rayon = "1.10.0"
regex = "1.10.5"
rustfm-scrobble = "1.1.1"
serde = { version = "1.0.147", features = ["derive"] }
serde_derive = "1.0.147"
serde_json = "1.0.122"
@ -98,6 +96,7 @@ winres = "0.1"
[dev-dependencies]
axum-test = "15.7"
bytes = "1.7.1"
percent-encoding = "2.2"
[profile.dev.package.sqlx-macros]
opt-level = 3

View file

@ -963,163 +963,6 @@
}
]
}
},
"/lastfm/now_playing/{song}": {
"put": {
"tags": [
"Last.fm"
],
"summary": "Tells Last.fm the song currently being played",
"operationId": "putLastFMNowPlaying",
"parameters": [
{
"name": "song",
"in": "path",
"description": "Path to the song being played",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful operation"
}
},
"security": [
{
"auth_http_bearer": [],
"auth_query_parameter": []
}
]
}
},
"/lastfm/scrobble/{song}": {
"post": {
"tags": [
"Last.fm"
],
"summary": "Tells Last.fm that a song has been playing for long enough to be scrobbled",
"operationId": "postLastFMScrobble",
"parameters": [
{
"name": "song",
"in": "path",
"description": "Path to the song being played",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful operation"
}
},
"security": [
{
"auth_http_bearer": [],
"auth_query_parameter": []
}
]
}
},
"/lastfm/link_token": {
"get": {
"tags": [
"Last.fm"
],
"summary": "Obtain an authentication token to be used when linking a Polaris account to a Last.fm account. The token is only valid for 10 minutes.",
"operationId": "getLastFMLinkToken",
"responses": {
"200": {
"description": "Successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LastFMLinkToken"
}
}
}
}
},
"security": [
{
"auth_http_bearer": [],
"auth_query_parameter": []
}
]
}
},
"/lastfm/link": {
"get": {
"tags": [
"Last.fm"
],
"summary": "Links a Polaris user with a Last.fm account.",
"externalDocs": {
"description": "This endpoint is meant to be used as a Last.fm authentication handler, as described here:",
"url": "https://www.last.fm/api/webauth"
},
"operationId": "getLastFMLink",
"parameters": [
{
"name": "auth_token",
"in": "query",
"required": true,
"description": "Polaris authentication token received from the `lastfm/link_token` endpoint",
"schema": {
"type": "string"
}
},
{
"name": "token",
"in": "query",
"required": true,
"description": "Last.fm authentication token",
"schema": {
"type": "string"
}
},
{
"name": "content",
"in": "query",
"required": true,
"description": "Base64 encoded HTML content to be returned to the client initiating the link operation",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful operation",
"content": {
"text/html": {
"description": "The same content originally present in the 'content' parameter"
}
}
}
}
},
"delete": {
"tags": [
"Last.fm"
],
"summary": "Unlinks Polaris user and Last.fm account",
"operationId": "deleteLastFMLink",
"responses": {
"200": {
"description": "Successful operation"
}
},
"security": [
{
"auth_http_bearer": [],
"auth_query_parameter": []
}
]
}
}
},
"components": {
@ -1262,9 +1105,6 @@
"Preferences": {
"type": "object",
"properties": {
"lastfm_username": {
"type": "string"
},
"web_theme_base": {
"type": "string"
},
@ -1298,14 +1138,6 @@
}
}
},
"LastFMLinkToken": {
"type": "object",
"properties": {
"value": {
"type": "string"
}
}
},
"CollectionFile": {
"oneOf": [
{

View file

@ -8,7 +8,6 @@ pub mod config;
pub mod ddns;
pub mod formats;
pub mod index;
pub mod lastfm;
pub mod ndb;
pub mod peaks;
pub mod playlist;
@ -133,21 +132,12 @@ pub enum Error {
InvalidAuthToken,
#[error("Incorrect authorization scope")]
IncorrectAuthorizationScope,
#[error("Last.fm session key is missing")]
MissingLastFMSessionKey,
#[error("Failed to hash password")]
PasswordHashing,
#[error("Failed to encode authorization token")]
AuthorizationTokenEncoding,
#[error("Failed to encode Branca token")]
BrancaTokenEncoding,
#[error("Failed to authenticate with last.fm")]
ScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
#[error("Failed to emit last.fm scrobble")]
Scrobble(rustfm_scrobble::ScrobblerError),
#[error("Failed to emit last.fm now playing update")]
NowPlaying(rustfm_scrobble::ScrobblerError),
}
#[derive(Clone)]
@ -159,7 +149,6 @@ pub struct App {
pub index_manager: index::Manager,
pub config_manager: config::Manager,
pub ddns_manager: ddns::Manager,
pub lastfm_manager: lastfm::Manager,
pub peaks_manager: peaks::Manager,
pub playlist_manager: playlist::Manager,
pub settings_manager: settings::Manager,
@ -210,7 +199,6 @@ impl App {
let peaks_manager = peaks::Manager::new(peaks_dir_path);
let playlist_manager = playlist::Manager::new(ndb_manager);
let thumbnail_manager = thumbnail::Manager::new(thumbnails_dir_path);
let lastfm_manager = lastfm::Manager::new(index_manager.clone(), user_manager.clone());
if let Some(config_path) = paths.config_file_path {
let config = config::Config::from_path(&config_path)?;
@ -225,7 +213,6 @@ impl App {
index_manager,
config_manager,
ddns_manager,
lastfm_manager,
peaks_manager,
playlist_manager,
settings_manager,

View file

@ -1,5 +1,4 @@
use std::{
borrow::Borrow,
collections::HashMap,
path::PathBuf,
sync::{Arc, RwLock},
@ -248,29 +247,6 @@ impl Manager {
.unwrap()
}
fn get_song_internal(virtual_path: &PathBuf, index: &Index) -> Result<Song, Error> {
let Some(virtual_path) = virtual_path.get(&index.strings) else {
return Err(Error::SongNotFound);
};
let song_key = SongKey { virtual_path };
index
.collection
.get_song(&index.strings, song_key)
.ok_or_else(|| Error::SongNotFound)
}
pub async fn get_song(&self, virtual_path: PathBuf) -> Result<Song, Error> {
spawn_blocking({
let index_manager = self.clone();
move || {
let index = index_manager.index.read().unwrap();
Self::get_song_internal(&virtual_path, index.borrow())
}
})
.await
.unwrap()
}
pub async fn get_songs(&self, virtual_paths: Vec<PathBuf>) -> Vec<Result<Song, Error>> {
spawn_blocking({
let index_manager = self.clone();
@ -278,7 +254,14 @@ impl Manager {
let index = index_manager.index.read().unwrap();
virtual_paths
.into_iter()
.map(|path| Self::get_song_internal(&path, index.borrow()))
.map(|p| {
p.get(&index.strings)
.and_then(|virtual_path| {
let key = SongKey { virtual_path };
index.collection.get_song(&index.strings, key)
})
.ok_or_else(|| Error::SongNotFound)
})
.collect()
}
})

View file

@ -1,77 +0,0 @@
use rustfm_scrobble::{Scrobble, Scrobbler};
use std::path::Path;
use user::AuthToken;
use crate::app::{index, user, Error};
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
#[derive(Clone)]
pub struct Manager {
index_manager: index::Manager,
user_manager: user::Manager,
}
impl Manager {
pub fn new(index_manager: index::Manager, user_manager: user::Manager) -> Self {
Self {
index_manager,
user_manager,
}
}
pub fn generate_link_token(&self, username: &str) -> Result<AuthToken, Error> {
self.user_manager
.generate_lastfm_link_token(username)
.map_err(|e| e.into())
}
pub async fn link(&self, username: &str, lastfm_token: &str) -> Result<(), Error> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let auth_response = scrobbler
.authenticate_with_token(lastfm_token)
.map_err(Error::ScrobblerAuthentication)?;
self.user_manager
.lastfm_link(username, &auth_response.name, &auth_response.key)
.await
.map_err(|e| e.into())
}
pub async fn unlink(&self, username: &str) -> Result<(), Error> {
self.user_manager
.lastfm_unlink(username)
.await
.map_err(|e| e.into())
}
pub async fn scrobble(&self, username: &str, track: &Path) -> Result<(), Error> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track).await?;
let auth_token = self.user_manager.get_lastfm_session_key(username).await?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler.scrobble(&scrobble).map_err(Error::Scrobble)?;
Ok(())
}
pub async fn now_playing(&self, username: &str, track: &Path) -> Result<(), Error> {
let mut scrobbler = Scrobbler::new(LASTFM_API_KEY, LASTFM_API_SECRET);
let scrobble = self.scrobble_from_path(track).await?;
let auth_token = self.user_manager.get_lastfm_session_key(username).await?;
scrobbler.authenticate_with_session_key(&auth_token);
scrobbler
.now_playing(&scrobble)
.map_err(Error::NowPlaying)?;
Ok(())
}
async fn scrobble_from_path(&self, track: &Path) -> Result<Scrobble, Error> {
let song = self.index_manager.get_song(track.to_owned()).await?;
Ok(Scrobble::new(
song.artists.first().map(|s| s.as_str()).unwrap_or(""),
song.title.as_deref().unwrap_or(""),
song.album.as_deref().unwrap_or(""),
))
}
}

View file

@ -33,7 +33,6 @@ pub struct AuthToken(pub String);
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum AuthorizationScope {
PolarisAuth,
LastFMLink,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
@ -44,7 +43,6 @@ pub struct Authorization {
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Preferences {
pub lastfm_username: Option<String>,
pub web_theme_base: Option<String>,
pub web_theme_accent: Option<String>,
}
@ -150,8 +148,7 @@ impl Manager {
) -> Result<Authorization, Error> {
let AuthToken(data) = auth_token;
let ttl = match scope {
AuthorizationScope::PolarisAuth => 0, // permanent
AuthorizationScope::LastFMLink => 10 * 60, // 10 minutes
AuthorizationScope::PolarisAuth => 0, // permanent
};
let authorization = branca::decode(data, &self.auth_secret.key, ttl)
.map_err(|_| Error::InvalidAuthToken)?;
@ -211,7 +208,7 @@ impl Manager {
pub async fn read_preferences(&self, username: &str) -> Result<Preferences, Error> {
Ok(sqlx::query_as!(
Preferences,
"SELECT web_theme_base, web_theme_accent, lastfm_username FROM users WHERE name = $1",
"SELECT web_theme_base, web_theme_accent FROM users WHERE name = $1",
username
)
.fetch_one(self.db.connect().await?.as_mut())
@ -233,56 +230,6 @@ impl Manager {
.await?;
Ok(())
}
pub async fn lastfm_link(
&self,
username: &str,
lastfm_login: &str,
session_key: &str,
) -> Result<(), Error> {
sqlx::query!(
"UPDATE users SET lastfm_username = $1, lastfm_session_key = $2 WHERE name = $3",
lastfm_login,
session_key,
username
)
.execute(self.db.connect().await?.as_mut())
.await?;
Ok(())
}
pub fn generate_lastfm_link_token(&self, username: &str) -> Result<AuthToken, Error> {
self.generate_auth_token(&Authorization {
username: username.to_owned(),
scope: AuthorizationScope::LastFMLink,
})
}
pub async fn get_lastfm_session_key(&self, username: &str) -> Result<String, Error> {
let token: Option<String> = sqlx::query_scalar!(
"SELECT lastfm_session_key FROM users WHERE name = $1",
username
)
.fetch_one(self.db.connect().await?.as_mut())
.await?;
token.ok_or(Error::MissingLastFMSessionKey)
}
pub async fn is_lastfm_linked(&self, username: &str) -> bool {
self.get_lastfm_session_key(username).await.is_ok()
}
pub async fn lastfm_unlink(&self, username: &str) -> Result<(), Error> {
let null: Option<String> = None;
sqlx::query!(
"UPDATE users SET lastfm_session_key = $1, lastfm_username = $1 WHERE name = $2",
null,
username
)
.execute(self.db.connect().await?.as_mut())
.await?;
Ok(())
}
}
fn hash_password(password: &str) -> Result<String, Error> {
@ -378,7 +325,6 @@ mod test {
let new_preferences = Preferences {
web_theme_base: Some("very-dark-theme".to_owned()),
web_theme_accent: Some("#FF0000".to_owned()),
lastfm_username: None,
};
let new_user = NewUser {
@ -481,29 +427,4 @@ mod test {
}
)
}
#[tokio::test]
async fn authenticate_validates_scope() {
let ctx = test::ContextBuilder::new(test_name!()).build().await;
let new_user = NewUser {
name: TEST_USERNAME.to_owned(),
password: TEST_PASSWORD.to_owned(),
admin: false,
};
ctx.user_manager.create(&new_user).await.unwrap();
let token = ctx
.user_manager
.generate_lastfm_link_token(TEST_USERNAME)
.unwrap();
let authorization = ctx
.user_manager
.authenticate(&token, AuthorizationScope::PolarisAuth)
.await;
assert!(matches!(
authorization.unwrap_err(),
Error::IncorrectAuthorizationScope
));
}
}

View file

@ -38,8 +38,6 @@ CREATE TABLE users (
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
admin INTEGER NOT NULL,
lastfm_username TEXT,
lastfm_session_key TEXT,
web_theme_base TEXT,
web_theme_accent TEXT,
UNIQUE(name)

View file

@ -65,12 +65,6 @@ impl FromRef<App> for app::ddns::Manager {
}
}
impl FromRef<App> for app::lastfm::Manager {
fn from_ref(app: &App) -> Self {
app.lastfm_manager.clone()
}
}
impl FromRef<App> for app::peaks::Manager {
fn from_ref(app: &App) -> Self {
app.peaks_manager.clone()

View file

@ -2,21 +2,17 @@ use std::path::PathBuf;
use axum::{
extract::{DefaultBodyLimit, Path, Query, State},
response::{Html, IntoResponse, Response},
response::{IntoResponse, Response},
routing::{delete, get, post, put},
Json, Router,
};
use axum_extra::headers::Range;
use axum_extra::TypedHeader;
use axum_range::{KnownSize, Ranged};
use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine};
use percent_encoding::percent_decode_str;
use tower_http::{compression::CompressionLayer, CompressionLevel};
use crate::{
app::{
config, ddns, index, lastfm, peaks, playlist, scanner, settings, thumbnail, user, vfs, App,
},
app::{config, ddns, index, peaks, playlist, scanner, settings, thumbnail, user, vfs, App},
server::{
dto, error::APIError, APIMajorVersion, API_ARRAY_SEPARATOR, API_MAJOR_VERSION,
API_MINOR_VERSION,
@ -73,12 +69,6 @@ pub fn router() -> Router<App> {
.route("/playlist/:name", put(put_playlist))
.route("/playlist/:name", get(get_playlist))
.route("/playlist/:name", delete(delete_playlist))
// LastFM
.route("/lastfm/now_playing/*path", put(put_lastfm_now_playing))
.route("/lastfm/scrobble/*path", post(post_lastfm_scrobble))
.route("/lastfm/link_token", get(get_lastfm_link_token))
.route("/lastfm/link", get(get_lastfm_link))
.route("/lastfm/link", delete(delete_lastfm_link))
// Media
.route("/songs", post(get_songs)) // post because of https://github.com/whatwg/fetch/issues/551
.route("/peaks/*path", get(get_peaks))
@ -728,77 +718,3 @@ async fn get_thumbnail(
let range = range.map(|TypedHeader(r)| r);
Ok(Ranged::new(range, body))
}
async fn put_lastfm_now_playing(
auth: Auth,
State(lastfm_manager): State<lastfm::Manager>,
State(user_manager): State<user::Manager>,
Path(path): Path<PathBuf>,
) -> Result<(), APIError> {
if !user_manager.is_lastfm_linked(auth.get_username()).await {
return Err(APIError::LastFMAccountNotLinked);
}
lastfm_manager
.now_playing(auth.get_username(), &path)
.await?;
Ok(())
}
async fn post_lastfm_scrobble(
auth: Auth,
State(lastfm_manager): State<lastfm::Manager>,
State(user_manager): State<user::Manager>,
Path(path): Path<PathBuf>,
) -> Result<(), APIError> {
if !user_manager.is_lastfm_linked(auth.get_username()).await {
return Err(APIError::LastFMAccountNotLinked);
}
lastfm_manager.scrobble(auth.get_username(), &path).await?;
Ok(())
}
async fn get_lastfm_link_token(
auth: Auth,
State(lastfm_manager): State<lastfm::Manager>,
) -> Result<Json<dto::LastFMLinkToken>, APIError> {
let user::AuthToken(value) = lastfm_manager.generate_link_token(auth.get_username())?;
Ok(Json(dto::LastFMLinkToken { value }))
}
async fn get_lastfm_link(
State(lastfm_manager): State<lastfm::Manager>,
State(user_manager): State<user::Manager>,
Query(payload): Query<dto::LastFMLink>,
) -> Result<Html<String>, APIError> {
let auth_token = user::AuthToken(payload.auth_token.clone());
let authorization = user_manager
.authenticate(&auth_token, user::AuthorizationScope::LastFMLink)
.await?;
let lastfm_token = &payload.token;
lastfm_manager
.link(&authorization.username, lastfm_token)
.await?;
// Percent decode
let base64_content = percent_decode_str(&payload.content).decode_utf8_lossy();
// Base64 decode
let popup_content = BASE64_STANDARD_NO_PAD
.decode(base64_content.as_bytes())
.map_err(|_| APIError::LastFMLinkContentBase64DecodeError)?;
// UTF-8 decode
let popup_content_string = std::str::from_utf8(&popup_content)
.map_err(|_| APIError::LastFMLinkContentEncodingError)
.map(|s| s.to_owned())?;
Ok(Html(popup_content_string))
}
async fn delete_lastfm_link(
auth: Auth,
State(lastfm_manager): State<lastfm::Manager>,
) -> Result<(), APIError> {
lastfm_manager.unlink(auth.get_username()).await?;
Ok(())
}

View file

@ -32,12 +32,6 @@ impl IntoResponse for APIError {
APIError::IncorrectCredentials => StatusCode::UNAUTHORIZED,
APIError::Internal => StatusCode::INTERNAL_SERVER_ERROR,
APIError::Io(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
APIError::LastFMAccountNotLinked => StatusCode::NO_CONTENT,
APIError::LastFMLinkContentBase64DecodeError => StatusCode::BAD_REQUEST,
APIError::LastFMLinkContentEncodingError => StatusCode::BAD_REQUEST,
APIError::LastFMNowPlaying(_) => StatusCode::FAILED_DEPENDENCY,
APIError::LastFMScrobble(_) => StatusCode::FAILED_DEPENDENCY,
APIError::LastFMScrobblerAuthentication(_) => StatusCode::FAILED_DEPENDENCY,
APIError::OwnAdminPrivilegeRemoval => StatusCode::CONFLICT,
APIError::PasswordHashing => StatusCode::INTERNAL_SERVER_ERROR,
APIError::PlaylistNotFound => StatusCode::NOT_FOUND,

View file

@ -76,18 +76,6 @@ pub struct SavePlaylistInput {
pub tracks: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct LastFMLink {
pub auth_token: String, // user::AuthToken emitted by Polaris, valid for LastFMLink scope
pub token: String, // LastFM token for use in scrobble calls
pub content: String, // Payload to send back to client after successful link
}
#[derive(Serialize, Deserialize)]
pub struct LastFMLinkToken {
pub value: String,
}
#[derive(Serialize, Deserialize)]
pub struct User {
pub name: String,

View file

@ -105,18 +105,6 @@ pub struct SavePlaylistInput {
pub tracks: Vec<PathBuf>,
}
#[derive(Serialize, Deserialize)]
pub struct LastFMLink {
pub auth_token: String, // user::AuthToken emitted by Polaris, valid for LastFMLink scope
pub token: String, // LastFM token for use in scrobble calls
pub content: String, // Payload to send back to client after successful link
}
#[derive(Serialize, Deserialize)]
pub struct LastFMLinkToken {
pub value: String,
}
#[derive(Serialize, Deserialize)]
pub struct User {
pub name: String,

View file

@ -47,18 +47,6 @@ pub enum APIError {
EmptyPassword,
#[error("Incorrect Credentials")]
IncorrectCredentials,
#[error("No last.fm account has been linked")]
LastFMAccountNotLinked,
#[error("Could not decode content as base64 after linking last.fm account")]
LastFMLinkContentBase64DecodeError,
#[error("Could not decode content as UTF-8 after linking last.fm account")]
LastFMLinkContentEncodingError,
#[error("Could send Now Playing update to last.fm:\n\n{0}")]
LastFMNowPlaying(rustfm_scrobble::ScrobblerError),
#[error("Could emit scrobble with last.fm:\n\n{0}")]
LastFMScrobble(rustfm_scrobble::ScrobblerError),
#[error("Could authenticate with last.fm:\n\n{0}")]
LastFMScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
#[error("Internal server error")]
Internal,
#[error("File I/O error for `{0}`:\n\n{1}")]
@ -156,14 +144,9 @@ impl From<app::Error> for APIError {
app::Error::IncorrectPassword => APIError::IncorrectCredentials,
app::Error::InvalidAuthToken => APIError::IncorrectCredentials,
app::Error::IncorrectAuthorizationScope => APIError::IncorrectCredentials,
app::Error::MissingLastFMSessionKey => APIError::IncorrectCredentials,
app::Error::PasswordHashing => APIError::PasswordHashing,
app::Error::AuthorizationTokenEncoding => APIError::AuthorizationTokenEncoding,
app::Error::BrancaTokenEncoding => APIError::BrancaTokenEncoding,
app::Error::ScrobblerAuthentication(e) => APIError::LastFMScrobblerAuthentication(e),
app::Error::Scrobble(e) => APIError::LastFMScrobble(e),
app::Error::NowPlaying(e) => APIError::LastFMNowPlaying(e),
}
}
}

View file

@ -15,7 +15,6 @@ mod auth;
mod browser;
mod collection;
mod ddns;
mod lastfm;
mod media;
mod playlist;
mod search;

View file

@ -1,63 +0,0 @@
use http::StatusCode;
use std::path::PathBuf;
use crate::server::dto;
use crate::server::test::{constants::*, protocol, ServiceType, TestService};
use crate::test_name;
#[tokio::test]
async fn lastfm_scrobble_ignores_unlinked_user() {
let mut service = ServiceType::new(&test_name!()).await;
service.complete_initial_setup().await;
service.login_admin().await;
service.index().await;
service.login().await;
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
.iter()
.collect();
let request = protocol::lastfm_scrobble(&path);
let response = service.fetch(&request).await;
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[tokio::test]
async fn lastfm_now_playing_ignores_unlinked_user() {
let mut service = ServiceType::new(&test_name!()).await;
service.complete_initial_setup().await;
service.login_admin().await;
service.index().await;
service.login().await;
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted", "02 - Candlelight.mp3"]
.iter()
.collect();
let request = protocol::lastfm_now_playing(&path);
let response = service.fetch(&request).await;
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[tokio::test]
async fn lastfm_link_token_requires_auth() {
let mut service = ServiceType::new(&test_name!()).await;
let request = protocol::lastfm_link_token();
let response = service.fetch(&request).await;
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn lastfm_link_token_golden_path() {
let mut service = ServiceType::new(&test_name!()).await;
service.complete_initial_setup().await;
service.login().await;
let request = protocol::lastfm_link_token();
let response = service
.fetch_json::<_, dto::LastFMLinkToken>(&request)
.await;
assert_eq!(response.status(), StatusCode::OK);
let link_token = response.body();
assert!(!link_token.value.is_empty());
}

View file

@ -364,34 +364,6 @@ pub fn delete_playlist(name: &str) -> Request<()> {
.unwrap()
}
pub fn lastfm_link_token() -> Request<()> {
Request::builder()
.method(Method::GET)
.uri("/api/lastfm/link_token")
.body(())
.unwrap()
}
pub fn lastfm_now_playing(path: &Path) -> Request<()> {
let path = path.to_string_lossy();
let endpoint = format!("/api/lastfm/now_playing/{}", url_encode(path.as_ref()));
Request::builder()
.method(Method::PUT)
.uri(&endpoint)
.body(())
.unwrap()
}
pub fn lastfm_scrobble(path: &Path) -> Request<()> {
let path = path.to_string_lossy();
let endpoint = format!("/api/lastfm/scrobble/{}", url_encode(path.as_ref()));
Request::builder()
.method(Method::POST)
.uri(&endpoint)
.body(())
.unwrap()
}
fn url_encode(input: &str) -> String {
percent_encode(input.as_bytes(), NON_ALPHANUMERIC).to_string()
}