Compare commits
4 commits
52ef924f12
...
fa8ed2b599
Author | SHA1 | Date | |
---|---|---|---|
fa8ed2b599 | |||
292ff60720 | |||
96a091f068 | |||
57588c7e13 |
33 changed files with 1272 additions and 303 deletions
545
Cargo.lock
generated
545
Cargo.lock
generated
|
@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -179,7 +179,7 @@ dependencies = [
|
|||
"actix-router",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -251,13 +251,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.76"
|
||||
version = "0.1.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514"
|
||||
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -437,6 +437,16 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
|
@ -445,9 +455,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -517,7 +527,7 @@ dependencies = [
|
|||
"diesel_table_macro_syntax",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -537,7 +547,7 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
||||
dependencies = [
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -566,12 +576,41 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
|
@ -588,6 +627,21 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
|
@ -597,12 +651,27 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
|
@ -622,9 +691,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -681,6 +753,12 @@ version = "0.14.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
|
@ -701,6 +779,17 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
|
@ -713,6 +802,49 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.59"
|
||||
|
@ -765,6 +897,23 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
|
@ -818,9 +967,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
version = "0.2.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "local-channel"
|
||||
|
@ -906,7 +1061,25 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -939,6 +1112,16 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
|
@ -954,6 +1137,50 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
|
@ -1040,18 +1267,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.73"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.34"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -1135,6 +1362,44 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.7"
|
||||
|
@ -1146,7 +1411,7 @@ dependencies = [
|
|||
"libc",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1164,12 +1429,34 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scheduled-thread-pool"
|
||||
version = "0.2.7"
|
||||
|
@ -1186,36 +1473,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.20"
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
||||
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
|
||||
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.193"
|
||||
version = "1.0.195"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.109"
|
||||
version = "1.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9"
|
||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1308,7 +1618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1336,9 +1646,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.44"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1346,23 +1656,66 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.53"
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.53"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1419,11 +1772,22 @@ dependencies = [
|
|||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"windows-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1474,6 +1838,12 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
@ -1494,6 +1864,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
@ -1555,6 +1931,7 @@ name = "vybr-api"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"actix-service",
|
||||
"actix-web",
|
||||
"async-trait",
|
||||
"bcrypt",
|
||||
|
@ -1562,14 +1939,26 @@ dependencies = [
|
|||
"diesel",
|
||||
"diesel_migrations",
|
||||
"dotenvy",
|
||||
"env_logger",
|
||||
"hmac",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -1597,10 +1986,22 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
|
@ -1619,7 +2020,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -1630,6 +2031,47 @@ version = "0.2.89"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
@ -1648,6 +2090,15 @@ dependencies = [
|
|||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
|
@ -1764,13 +2215,23 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.31"
|
||||
version = "0.5.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
|
||||
checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
|
@ -1788,7 +2249,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.44",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -14,9 +14,13 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||
diesel = { version = "2", features = ["r2d2", "postgres", "chrono"] }
|
||||
diesel_migrations = "2"
|
||||
dotenvy = "*"
|
||||
env_logger = "0.10"
|
||||
hmac = "0.12"
|
||||
log = "0.4"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
sha2 = "0.10"
|
||||
lazy_static = "1"
|
||||
jsonwebtoken = "9"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
actix-service = "2.0.2"
|
||||
|
|
|
@ -34,3 +34,99 @@ BEGIN
|
|||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- nanoid function
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- The `nanoid()` function generates a compact, URL-friendly unique identifier.
|
||||
-- Based on the given size and alphabet, it creates a randomized string that's ideal for
|
||||
-- use-cases requiring small, unpredictable IDs (e.g., URL shorteners, generated file names, etc.).
|
||||
-- While it comes with a default configuration, the function is designed to be flexible,
|
||||
-- allowing for customization to meet specific needs.
|
||||
DROP FUNCTION IF EXISTS nanoid(int, text, float);
|
||||
CREATE OR REPLACE FUNCTION nanoid(
|
||||
size int DEFAULT 24, -- The number of symbols in the NanoId String. Must be greater than 0.
|
||||
alphabet text DEFAULT '_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', -- The symbols used in the NanoId String. Must contain between 1 and 255 symbols.
|
||||
additionalBytesFactor float DEFAULT 1.6 -- The additional bytes factor used for calculating the step size. Must be equal or greater then 1.
|
||||
)
|
||||
RETURNS text -- A randomly generated NanoId String
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
LEAKPROOF
|
||||
PARALLEL SAFE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
alphabetArray text[];
|
||||
alphabetLength int := 64;
|
||||
mask int := 63;
|
||||
step int := 34;
|
||||
BEGIN
|
||||
IF size IS NULL OR size < 1 THEN
|
||||
RAISE EXCEPTION 'The size must be defined and greater than 0!';
|
||||
END IF;
|
||||
|
||||
IF alphabet IS NULL OR length(alphabet) = 0 OR length(alphabet) > 255 THEN
|
||||
RAISE EXCEPTION 'The alphabet can''t be undefined, zero or bigger than 255 symbols!';
|
||||
END IF;
|
||||
|
||||
IF additionalBytesFactor IS NULL OR additionalBytesFactor < 1 THEN
|
||||
RAISE EXCEPTION 'The additional bytes factor can''t be less than 1!';
|
||||
END IF;
|
||||
|
||||
alphabetArray := regexp_split_to_array(alphabet, '');
|
||||
alphabetLength := array_length(alphabetArray, 1);
|
||||
mask := (2 << cast(floor(log(alphabetLength - 1) / log(2)) as int)) - 1;
|
||||
step := cast(ceil(additionalBytesFactor * mask * size / alphabetLength) AS int);
|
||||
|
||||
IF step > 1024 THEN
|
||||
step := 1024; -- The step size % can''t be bigger then 1024!
|
||||
END IF;
|
||||
|
||||
RETURN nanoid_optimized(size, alphabet, mask, step);
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Generates an optimized random string of a specified size using the given alphabet, mask, and step.
|
||||
-- This optimized version is designed for higher performance and lower memory overhead.
|
||||
-- No checks are performed! Use it only if you really know what you are doing.
|
||||
DROP FUNCTION IF EXISTS nanoid_optimized(int, text, int, int);
|
||||
CREATE OR REPLACE FUNCTION nanoid_optimized(
|
||||
size int, -- The desired length of the generated string.
|
||||
alphabet text, -- The set of characters to choose from for generating the string.
|
||||
mask int, -- The mask used for mapping random bytes to alphabet indices. Should be `(2^n) - 1` where `n` is a power of 2 less than or equal to the alphabet size.
|
||||
step int -- The number of random bytes to generate in each iteration. A larger value may speed up the function but increase memory usage.
|
||||
)
|
||||
RETURNS text -- A randomly generated NanoId String
|
||||
LANGUAGE plpgsql
|
||||
VOLATILE
|
||||
LEAKPROOF
|
||||
PARALLEL SAFE
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
idBuilder text := '';
|
||||
counter int := 0;
|
||||
bytes bytea;
|
||||
alphabetIndex int;
|
||||
alphabetArray text[];
|
||||
alphabetLength int := 64;
|
||||
BEGIN
|
||||
alphabetArray := regexp_split_to_array(alphabet, '');
|
||||
alphabetLength := array_length(alphabetArray, 1);
|
||||
|
||||
LOOP
|
||||
bytes := gen_random_bytes(step);
|
||||
FOR counter IN 0..step - 1
|
||||
LOOP
|
||||
alphabetIndex := (get_byte(bytes, counter) & mask) + 1;
|
||||
IF alphabetIndex <= alphabetLength THEN
|
||||
idBuilder := idBuilder || alphabetArray[alphabetIndex];
|
||||
IF length(idBuilder) = size THEN
|
||||
RETURN idBuilder;
|
||||
END IF;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
|
|
@ -7,7 +7,7 @@ create table if not exists users
|
|||
password text not null,
|
||||
|
||||
updated_at timestamp,
|
||||
created_at timestamp default now() not null,
|
||||
created_at timestamp default now(),
|
||||
|
||||
primary key (id)
|
||||
);
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
create type playlist_type as enum ('playlist', 'folder');
|
||||
|
||||
create table if not exists playlists
|
||||
(
|
||||
id varchar(24) default nanoid(24),
|
||||
name varchar(255) not null,
|
||||
public bool default false,
|
||||
public bool default false not null,
|
||||
type playlist_type default 'playlist' not null,
|
||||
|
||||
creator_id varchar(24) not null,
|
||||
parent_id varchar(24) references playlists (id) on delete set null,
|
||||
|
||||
created_at timestamp default now() not null,
|
||||
created_at timestamp default now(),
|
||||
updated_at timestamp,
|
||||
|
||||
primary key (id),
|
||||
|
|
|
@ -5,11 +5,11 @@ create table if not exists tracks
|
|||
title varchar(255) not null,
|
||||
duration_ms int not null default 0,
|
||||
|
||||
created_at timestamp default now() not null,
|
||||
created_at timestamp default now(),
|
||||
updated_at timestamp,
|
||||
|
||||
-- music services
|
||||
spotify_id varchar(21) unique,
|
||||
spotify_id varchar(22) unique,
|
||||
tidal_id varchar(10) unique,
|
||||
|
||||
primary key (id)
|
||||
|
|
|
@ -2,5 +2,6 @@ create table if not exists playlists_tracks
|
|||
(
|
||||
playlist_id varchar(24) references playlists (id) on delete cascade,
|
||||
track_id varchar(24) references tracks (id) on delete cascade,
|
||||
added_at timestamp default now() not null,
|
||||
primary key (playlist_id, track_id)
|
||||
);
|
||||
|
|
|
@ -3,13 +3,14 @@ create table if not exists artists
|
|||
(
|
||||
id varchar(24) default nanoid(24),
|
||||
name varchar(255) NOT NULL,
|
||||
slug varchar unique not null generated always as (lower(replace(name, ' ', '-'))) stored,
|
||||
|
||||
created_at TIMESTAMP DEFAULT now() NOT NULL,
|
||||
updated_at TIMESTAMP,
|
||||
created_at timestamp default now(),
|
||||
updated_at timestamp,
|
||||
|
||||
-- music services
|
||||
spotify_id VARCHAR(21) UNIQUE,
|
||||
tidal_id VARCHAR(10) UNIQUE,
|
||||
spotify_id varchar(22) unique,
|
||||
tidal_id varchar(10) unique,
|
||||
|
||||
primary key (id)
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ lazy_static! {
|
|||
}
|
||||
|
||||
pub fn init() {
|
||||
log::info!("Initializing db pool.");
|
||||
lazy_static::initialize(&POOL);
|
||||
let _conn = connection().expect("Failed to get db connection.");
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use jsonwebtoken::{decode, DecodingKey, EncodingKey, TokenData, Validation};
|
|||
const JWT_SECRET: &str = "secret";
|
||||
|
||||
pub fn get_token(token: &str) -> Result<TokenData<JWTClaims>, Error> {
|
||||
let token = decode::<JWTClaims>(&token, &get_decoding_key(), &Validation::default())?;
|
||||
let token = decode::<JWTClaims>(token, &get_decoding_key(), &Validation::default())?;
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
|
|
17
src/main.rs
17
src/main.rs
|
@ -5,23 +5,32 @@ mod middlewares;
|
|||
mod models;
|
||||
mod routes;
|
||||
mod schema;
|
||||
mod services;
|
||||
mod utils;
|
||||
|
||||
use crate::helpers::db;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
use actix_web::{App, HttpServer};
|
||||
|
||||
dotenvy::dotenv().expect("No .env file found");
|
||||
std::env::set_var("RUST_LOG", "debug");
|
||||
env_logger::init();
|
||||
|
||||
// Register services.
|
||||
let _ = services::spotify::instance().await;
|
||||
|
||||
db::init();
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.service(web::scope("/playlists").service(routes::playlists::get_playlist))
|
||||
.service(routes::auth::login)
|
||||
.service(routes::auth::routes())
|
||||
// .wrap(middlewares::auth::auth())
|
||||
.service(routes::playlists::routes())
|
||||
.service(routes::me::routes())
|
||||
.service(routes::import::routes())
|
||||
.service(routes::users::routes())
|
||||
.service(routes::search::routes())
|
||||
})
|
||||
.bind(("127.0.0.1", 9000))?
|
||||
.run()
|
||||
|
|
|
@ -9,6 +9,15 @@ pub struct ErrorResponse {
|
|||
pub status: StatusCode,
|
||||
}
|
||||
|
||||
impl ErrorResponse {
|
||||
pub fn new(message: &str, status: StatusCode) -> Self {
|
||||
ErrorResponse {
|
||||
message: message.to_string(),
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ErrorResponse {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||
write!(f, "{}: {}", self.status, self.message)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::helpers::jwt::get_token;
|
||||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::models::user::Users;
|
||||
use crate::models::users::Users;
|
||||
use actix_web::http::{header, StatusCode};
|
||||
use actix_web::HttpRequest;
|
||||
|
||||
|
@ -19,23 +19,29 @@ pub fn get_user(req: HttpRequest) -> Result<Users, ErrorResponse> {
|
|||
}
|
||||
Err(e) => {
|
||||
return Err(match e.kind() {
|
||||
jsonwebtoken::errors::ErrorKind::ExpiredSignature => ErrorResponse {
|
||||
message: "Not Authorized".to_string(),
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
},
|
||||
_ => ErrorResponse {
|
||||
message: e.to_string(),
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
},
|
||||
jsonwebtoken::errors::ErrorKind::ExpiredSignature => {
|
||||
ErrorResponse::new("Not Authorized", StatusCode::UNAUTHORIZED)
|
||||
}
|
||||
_ => ErrorResponse::new(
|
||||
e.to_string().as_str(),
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err(ErrorResponse {
|
||||
message: "Not Authorized".to_string(),
|
||||
status: StatusCode::UNAUTHORIZED,
|
||||
});
|
||||
}
|
||||
None => Err(ErrorResponse::new(
|
||||
"Not Authorized",
|
||||
StatusCode::UNAUTHORIZED,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user_option(req: HttpRequest) -> Option<Users> {
|
||||
let user = get_user(req);
|
||||
|
||||
match user {
|
||||
Ok(user) => Some(user),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,29 @@
|
|||
use crate::helpers::db;
|
||||
use crate::schema::{artists, artists_tracks};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error;
|
||||
use diesel::{
|
||||
AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = crate::schema::artists)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct Artist {
|
||||
#[derive(AsChangeset, Identifiable, Insertable, Queryable, Selectable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = artists)]
|
||||
pub struct Artists {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
|
||||
created_at: NaiveDateTime,
|
||||
updated_at: Option<NaiveDateTime>,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
|
||||
pub spotify_id: Option<String>,
|
||||
pub tidal_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Queryable, Serialize)]
|
||||
pub struct Artists {
|
||||
#[derive(Debug, Insertable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = artists)]
|
||||
pub struct NewArtist {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
|
||||
created_at: NaiveDateTime,
|
||||
updated_at: Option<NaiveDateTime>,
|
||||
pub name: String,
|
||||
|
||||
pub spotify_id: Option<String>,
|
||||
pub tidal_id: Option<String>,
|
||||
|
@ -36,14 +32,14 @@ pub struct Artists {
|
|||
impl Artists {
|
||||
pub fn find(id: String) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let playlist = artists::table.filter(artists::id.eq(id)).first(conn)?;
|
||||
Ok(playlist)
|
||||
let artist = artists::table.filter(artists::id.eq(id)).first(conn)?;
|
||||
Ok(artist)
|
||||
}
|
||||
|
||||
pub fn create(artist: Artist) -> Result<Self, Error> {
|
||||
pub fn create(artist: NewArtist) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let playlist = diesel::insert_into(artists::table)
|
||||
.values(Artist::from(artist))
|
||||
.values(artist)
|
||||
.get_result(conn)?;
|
||||
Ok(playlist)
|
||||
}
|
||||
|
@ -62,18 +58,3 @@ impl Artists {
|
|||
Ok(artists)
|
||||
}
|
||||
}
|
||||
|
||||
impl Artist {
|
||||
fn from(artist: Artist) -> Artist {
|
||||
Artist {
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
|
||||
created_at: artist.created_at,
|
||||
updated_at: artist.updated_at,
|
||||
|
||||
spotify_id: artist.spotify_id,
|
||||
tidal_id: artist.tidal_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
52
src/models/artists_tracks.rs
Normal file
52
src/models/artists_tracks.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use diesel::prelude::*;
|
||||
use diesel::result::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::helpers::db;
|
||||
|
||||
use super::artists::Artists;
|
||||
use super::tracks::Tracks;
|
||||
|
||||
use crate::schema::{artists, artists_tracks, tracks};
|
||||
|
||||
#[derive(Identifiable, Selectable, Queryable, Associations, Debug)]
|
||||
#[diesel(belongs_to(Artists, foreign_key = artist_id))]
|
||||
#[diesel(belongs_to(Tracks, foreign_key = track_id))]
|
||||
#[diesel(table_name = artists_tracks)]
|
||||
#[diesel(primary_key(artist_id, track_id))]
|
||||
pub struct ArtistTracks {
|
||||
pub artist_id: String,
|
||||
pub track_id: String,
|
||||
}
|
||||
|
||||
impl ArtistTracks {
|
||||
pub fn get_tracks(artist: &Artists) -> Result<Vec<Tracks>, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let track_ids = ArtistTracks::belonging_to(artist).select(artists_tracks::track_id);
|
||||
|
||||
let tracks: Vec<Tracks> = tracks::table
|
||||
.filter(tracks::id.eq_any(track_ids))
|
||||
.load::<Tracks>(conn)?;
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub fn get_artists(track: &Tracks) -> Result<Vec<TrackArtist>, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let artist_ids = ArtistTracks::belonging_to(track).select(artists_tracks::artist_id);
|
||||
|
||||
let artists = artists::table
|
||||
.select((artists::id, artists::name))
|
||||
.filter(artists::id.eq_any(artist_ids))
|
||||
.load::<TrackArtist>(conn)?;
|
||||
|
||||
Ok(artists)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Queryable)]
|
||||
#[diesel(table_name = artists)]
|
||||
pub struct TrackArtist {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
pub mod artists;
|
||||
pub mod artists_tracks;
|
||||
pub mod playlists;
|
||||
pub mod playlists_tracks;
|
||||
pub mod spotify;
|
||||
pub mod tracks;
|
||||
pub mod user;
|
||||
pub mod users;
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
use crate::helpers::db;
|
||||
use crate::models::tracks::{Track, Tracks, TracksWithArtists};
|
||||
use crate::models::user::Users;
|
||||
use crate::schema::playlists;
|
||||
use crate::schema::{playlists, playlists_tracks};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::result::Error;
|
||||
use diesel::{
|
||||
AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
|
||||
};
|
||||
use diesel::{prelude::*, result::Error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
use super::artists_tracks::{ArtistTracks, TrackArtist};
|
||||
use super::{playlists_tracks::PlaylistTracks, tracks::Tracks, users::Users};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct PlaylistCreator {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = crate::schema::playlists)]
|
||||
#[diesel(belongs_to(Users))]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct Playlist {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub public: bool,
|
||||
pub creator_id: String,
|
||||
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Queryable, Serialize)]
|
||||
#[derive(
|
||||
AsChangeset, Debug, Deserialize, Identifiable, Queryable, Selectable, Serialize, PartialEq,
|
||||
)]
|
||||
#[diesel(table_name = playlists)]
|
||||
pub struct Playlists {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub public: bool,
|
||||
|
||||
pub playlist_type: String,
|
||||
pub parent_id: Option<String>,
|
||||
|
||||
pub creator_id: String,
|
||||
|
||||
pub created_at: NaiveDateTime,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Insertable)]
|
||||
#[diesel(table_name = playlists)]
|
||||
pub struct NewPlaylist {
|
||||
pub name: String,
|
||||
pub public: bool,
|
||||
// pub playlist_type: String,
|
||||
pub creator_id: String,
|
||||
}
|
||||
|
||||
impl Playlists {
|
||||
pub fn find(id: &str) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
|
@ -59,23 +59,26 @@ impl Playlists {
|
|||
let conn = &mut db::connection()?;
|
||||
|
||||
let mut playlists = playlists::table
|
||||
.distinct()
|
||||
.select(Playlists::as_select())
|
||||
.order((playlists::playlist_type.desc(), playlists::parent_id.desc()))
|
||||
.filter(playlists::creator_id.eq(user_id))
|
||||
.into_boxed();
|
||||
|
||||
if filter_public {
|
||||
playlists = playlists.filter(playlists::public.eq(true));
|
||||
playlists = playlists.filter(
|
||||
playlists::public
|
||||
.eq(true)
|
||||
.and(playlists::playlist_type.ne("folder")),
|
||||
);
|
||||
}
|
||||
|
||||
let playlists = playlists.get_results(conn)?;
|
||||
Ok(playlists)
|
||||
}
|
||||
|
||||
pub fn get_tracks(&self) -> Result<Vec<TracksWithArtists>, Error> {
|
||||
let tracks = Tracks::find_by_playlist(&self.id)?;
|
||||
let tracks: Vec<TracksWithArtists> = tracks
|
||||
.into_iter()
|
||||
.map(Track::with_artists)
|
||||
.collect::<Result<Vec<TracksWithArtists>, _>>()?;
|
||||
pub fn get_tracks(&self) -> Result<Vec<PlaylistTrack>, Error> {
|
||||
let tracks: Vec<PlaylistTrack> = PlaylistTracks::get_tracks(self)?;
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
@ -84,34 +87,69 @@ impl Playlists {
|
|||
let creator = Users::find(&self.creator_id)?;
|
||||
Ok(creator)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Insertable, Deserialize)]
|
||||
#[diesel(table_name = playlists)]
|
||||
pub struct NewPlaylist {
|
||||
pub name: String,
|
||||
pub public: bool,
|
||||
pub creator_id: Option<String>,
|
||||
}
|
||||
|
||||
impl Playlist {
|
||||
pub fn create(name: &str, public: bool, creator_id: &str) -> NewPlaylist {
|
||||
NewPlaylist {
|
||||
name: name.to_string(),
|
||||
public,
|
||||
creator_id: Some(creator_id.to_string()),
|
||||
}
|
||||
pub fn get_data(&self, tracks: &[PlaylistTrack]) -> (usize, usize) {
|
||||
if tracks.is_empty() {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
fn from(playlist: Playlist) -> Playlist {
|
||||
Playlist {
|
||||
id: playlist.id,
|
||||
name: playlist.name,
|
||||
public: playlist.public,
|
||||
creator_id: playlist.creator_id,
|
||||
let duration = tracks
|
||||
.iter()
|
||||
.map(|track| track.duration_ms)
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap() as usize;
|
||||
|
||||
created_at: playlist.created_at,
|
||||
updated_at: playlist.updated_at,
|
||||
(duration, tracks.len())
|
||||
}
|
||||
|
||||
pub fn can_see(&self, creator: Option<Users>) -> bool {
|
||||
self.public || creator.map_or(false, |user| user.id == self.creator_id)
|
||||
}
|
||||
}
|
||||
|
||||
// // Folder
|
||||
// struct Folder {
|
||||
// id: String,
|
||||
// name: String,
|
||||
// playlists: Vec<Playlists>,
|
||||
// }
|
||||
|
||||
// impl Folder {
|
||||
// pub fn get_playlists(&self) -> Result<Vec<Playlists>, Error> {
|
||||
// let playlists = playlists::table
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PlaylistTrack {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub duration_ms: i32,
|
||||
|
||||
pub artists: Vec<TrackArtist>,
|
||||
pub added_at: NaiveDateTime,
|
||||
pub spotify_id: Option<String>,
|
||||
pub tidal_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Tracks> for PlaylistTrack {
|
||||
fn from(track: Tracks) -> Self {
|
||||
let artists = ArtistTracks::get_artists(&track).unwrap();
|
||||
|
||||
let added_at = playlists_tracks::table
|
||||
.filter(playlists_tracks::track_id.eq(&track.id))
|
||||
.select(playlists_tracks::added_at)
|
||||
.first::<NaiveDateTime>(&mut db::connection().unwrap())
|
||||
.unwrap();
|
||||
|
||||
PlaylistTrack {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
duration_ms: track.duration_ms,
|
||||
artists,
|
||||
added_at,
|
||||
spotify_id: track.spotify_id,
|
||||
tidal_id: track.tidal_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
38
src/models/playlists_tracks.rs
Normal file
38
src/models/playlists_tracks.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use diesel::prelude::*;
|
||||
use diesel::result::Error;
|
||||
|
||||
use crate::helpers::db;
|
||||
|
||||
use super::playlists::{PlaylistTrack, Playlists};
|
||||
use super::tracks::Tracks;
|
||||
|
||||
use crate::schema::{playlists_tracks, tracks};
|
||||
|
||||
#[derive(Identifiable, Selectable, Queryable, Associations, Debug)]
|
||||
#[diesel(belongs_to(Playlists, foreign_key = playlist_id))]
|
||||
#[diesel(belongs_to(Tracks, foreign_key = track_id))]
|
||||
#[diesel(table_name = playlists_tracks)]
|
||||
#[diesel(primary_key(playlist_id, track_id))]
|
||||
pub struct PlaylistTracks {
|
||||
pub playlist_id: String,
|
||||
pub track_id: String,
|
||||
pub added_at: String,
|
||||
}
|
||||
|
||||
impl PlaylistTracks {
|
||||
pub fn get_tracks(playlist: &Playlists) -> Result<Vec<PlaylistTrack>, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let track_ids = PlaylistTracks::belonging_to(playlist).select(playlists_tracks::track_id);
|
||||
|
||||
let tracks: Vec<Tracks> = tracks::table
|
||||
.filter(tracks::id.eq_any(track_ids))
|
||||
.load::<Tracks>(conn)?;
|
||||
|
||||
let tracks = tracks
|
||||
.into_iter()
|
||||
.map(PlaylistTrack::from)
|
||||
.collect::<Vec<PlaylistTrack>>();
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
}
|
42
src/models/spotify.rs
Normal file
42
src/models/spotify.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Track {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub duration_ms: i32,
|
||||
pub artists: Vec<Artist>,
|
||||
pub album: Album,
|
||||
|
||||
pub external_ids: ExternalIds,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ExternalIds {
|
||||
pub isrc: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Artist {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Album {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub artists: Vec<Artist>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct APISearchResponse {
|
||||
pub albums: APISearchItems<Album>,
|
||||
pub artists: APISearchItems<Artist>,
|
||||
pub tracks: APISearchItems<Track>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct APISearchItems<T> {
|
||||
pub items: Vec<T>,
|
||||
}
|
|
@ -1,39 +1,34 @@
|
|||
use crate::helpers::db;
|
||||
use crate::models::artists::Artists;
|
||||
use crate::schema::tracks;
|
||||
use crate::models::{artists::Artists, spotify};
|
||||
use crate::schema::{playlists_tracks, tracks};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error;
|
||||
use diesel::{
|
||||
AsChangeset, ExpressionMethods, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::schema::playlists_tracks;
|
||||
|
||||
#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = crate::schema::tracks)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct Track {
|
||||
#[derive(
|
||||
AsChangeset, Debug, Deserialize, Serialize, Queryable, Identifiable, Selectable, Clone,
|
||||
)]
|
||||
#[diesel(table_name = tracks)]
|
||||
pub struct Tracks {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub duration_ms: i32,
|
||||
|
||||
pub created_at: NaiveDateTime,
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
|
||||
pub spotify_id: Option<String>,
|
||||
pub tidal_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Queryable, Serialize)]
|
||||
pub struct Tracks {
|
||||
#[derive(AsChangeset, Clone, Debug, Deserialize, Insertable)]
|
||||
#[diesel(table_name = tracks)]
|
||||
pub struct NewTrack {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub duration_ms: i32,
|
||||
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
|
||||
pub spotify_id: Option<String>,
|
||||
pub tidal_id: Option<String>,
|
||||
}
|
||||
|
@ -45,51 +40,29 @@ impl Tracks {
|
|||
Ok(playlist)
|
||||
}
|
||||
|
||||
pub fn create(track: Track) -> Result<Self, Error> {
|
||||
pub fn create(track: NewTrack) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let playlist = diesel::insert_into(tracks::table)
|
||||
.values(Track::from(track))
|
||||
.values(track)
|
||||
.get_result(conn)?;
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
pub fn find_by_playlist(playlist_id: &str) -> Result<Vec<Tracks>, Error> {
|
||||
pub fn create_or_update(track: NewTrack) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let tracks: Vec<(String, String)> = playlists_tracks::table
|
||||
.filter(playlists_tracks::playlist_id.eq(playlist_id))
|
||||
.get_results::<(String, String)>(conn)?;
|
||||
|
||||
let tracks = tracks
|
||||
.into_iter()
|
||||
.map(|(playlist_id, track_id)| {
|
||||
println!("{}: {}", playlist_id, track_id);
|
||||
Tracks::find(track_id).unwrap()
|
||||
})
|
||||
.collect::<Vec<Tracks>>();
|
||||
|
||||
Ok(tracks)
|
||||
let playlist = diesel::insert_into(tracks::table)
|
||||
.values(track.clone())
|
||||
.on_conflict(tracks::id)
|
||||
.do_update()
|
||||
.set(track)
|
||||
.get_result(conn)?;
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
pub fn get_artists(&self) -> Result<Vec<Artists>, Error> {
|
||||
let artists = Artists::find_by_track(&self.id)?;
|
||||
Ok(artists)
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
fn from(track: Track) -> Track {
|
||||
Track {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
duration_ms: track.duration_ms,
|
||||
|
||||
created_at: track.created_at,
|
||||
updated_at: track.updated_at,
|
||||
|
||||
spotify_id: track.spotify_id,
|
||||
tidal_id: track.tidal_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_artists(track: Tracks) -> Result<TracksWithArtists, Error> {
|
||||
let artists = track.get_artists()?;
|
||||
|
@ -107,6 +80,20 @@ impl Track {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<spotify::Track> for Tracks {
|
||||
fn from(value: spotify::Track) -> Tracks {
|
||||
Tracks {
|
||||
id: value.external_ids.isrc,
|
||||
title: value.name,
|
||||
duration_ms: value.duration_ms,
|
||||
spotify_id: Some(value.id),
|
||||
tidal_id: None,
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TracksWithArtists {
|
||||
pub id: String,
|
||||
|
|
|
@ -11,18 +11,6 @@ use serde::{Deserialize, Serialize};
|
|||
#[derive(AsChangeset, Insertable, Queryable, Selectable, Deserialize, Serialize)]
|
||||
#[diesel(table_name = crate::schema::users)]
|
||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
|
||||
pub password: String,
|
||||
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Queryable, Serialize)]
|
||||
pub struct Users {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
|
@ -30,8 +18,8 @@ pub struct Users {
|
|||
|
||||
pub password: String,
|
||||
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
pub updated_at: Option<NaiveDateTime>,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
|
||||
impl Users {
|
||||
|
@ -41,14 +29,6 @@ impl Users {
|
|||
Ok(user)
|
||||
}
|
||||
|
||||
pub fn create(user: User) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let user = diesel::insert_into(users::table)
|
||||
.values(User::from(user))
|
||||
.get_result(conn)?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub fn find_by_email(email: &str) -> Result<Self, Error> {
|
||||
let conn = &mut db::connection()?;
|
||||
let user = users::table.filter(users::email.eq(email)).first(conn)?;
|
||||
|
@ -59,16 +39,3 @@ impl Users {
|
|||
bcrypt::verify(password, hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
fn from(user: User) -> User {
|
||||
User {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +1,28 @@
|
|||
use crate::helpers::jwt::get_encoding_key;
|
||||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::models::user::Users;
|
||||
use crate::models::users::Users;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{post, web, HttpResponse};
|
||||
use actix_web::{post, web, HttpResponse, Scope};
|
||||
use chrono::{Days, Utc};
|
||||
use jsonwebtoken::{encode, Header};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn routes() -> Scope {
|
||||
web::scope("/auth").service(login)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct JWTClaims {
|
||||
pub user_id: String,
|
||||
pub exp: usize,
|
||||
pub exp: i64,
|
||||
}
|
||||
|
||||
#[post("/auth/login")]
|
||||
#[post("/login")]
|
||||
async fn login(body: web::Json<LoginBody>) -> Result<HttpResponse, ErrorResponse> {
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Response {
|
||||
access_token: String,
|
||||
exp: usize,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
let user = Users::find_by_email(&body.email);
|
||||
|
@ -32,28 +36,24 @@ async fn login(body: web::Json<LoginBody>) -> Result<HttpResponse, ErrorResponse
|
|||
|
||||
let claims = JWTClaims {
|
||||
user_id: user.id.to_string(),
|
||||
exp: exp as usize,
|
||||
exp,
|
||||
};
|
||||
let token = encode(&Header::default(), &claims, &get_encoding_key()).unwrap();
|
||||
|
||||
Ok(HttpResponse::Ok().json(Response {
|
||||
access_token: token,
|
||||
exp: exp as usize,
|
||||
exp,
|
||||
}))
|
||||
}
|
||||
Err(_e) => {
|
||||
return Err(ErrorResponse {
|
||||
message: "Invalid credentials.".to_string(),
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
})
|
||||
}
|
||||
Err(_e) => Err(ErrorResponse::new(
|
||||
"Invalid credentials",
|
||||
StatusCode::BAD_REQUEST,
|
||||
)),
|
||||
},
|
||||
Err(_err) => {
|
||||
return Err(ErrorResponse {
|
||||
message: "Invalid credentials.".to_string(),
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
})
|
||||
}
|
||||
Err(_err) => Err(ErrorResponse::new(
|
||||
"Invalid credentials",
|
||||
StatusCode::BAD_REQUEST,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
src/routes/import.rs
Normal file
12
src/routes/import.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use actix_web::{get, web, HttpResponse, Result, Scope};
|
||||
|
||||
use crate::middlewares::error::ErrorResponse;
|
||||
|
||||
pub fn routes() -> Scope {
|
||||
web::scope("/import")
|
||||
}
|
||||
|
||||
#[get("/spotify/{playlist_id}")]
|
||||
async fn import_spotify() -> Result<HttpResponse, ErrorResponse> {
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::middlewares::user::get_user;
|
||||
use crate::models::playlists::{NewPlaylist, Playlists};
|
||||
use crate::models::playlists::{NewPlaylist, PlaylistCreator, Playlists};
|
||||
use crate::routes::playlists::GetPlaylistResponse;
|
||||
use actix_web::{get, post, web, HttpRequest, HttpResponse, Scope};
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -33,14 +34,32 @@ async fn me(req: HttpRequest) -> Result<HttpResponse, ErrorResponse> {
|
|||
async fn me_playlists(req: HttpRequest) -> Result<HttpResponse, ErrorResponse> {
|
||||
let user = get_user(req)?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Response {
|
||||
playlists: Vec<Playlists>,
|
||||
}
|
||||
let playlists = Playlists::find_for_user(&user.id, false)?;
|
||||
let playlists = playlists
|
||||
.into_iter()
|
||||
.map(|playlist| {
|
||||
let creator = playlist.get_creator().unwrap();
|
||||
|
||||
Ok(HttpResponse::Ok().json(Response {
|
||||
playlists: Playlists::find_for_user(&user.id, false)?,
|
||||
}))
|
||||
let tracks = playlist.get_tracks().unwrap();
|
||||
let (duration, tracks_count) = playlist.get_data(&tracks);
|
||||
|
||||
GetPlaylistResponse {
|
||||
id: playlist.id.to_string(),
|
||||
name: playlist.name.to_string(),
|
||||
playlist_type: playlist.playlist_type.to_string(),
|
||||
parent_id: playlist.parent_id,
|
||||
creator: PlaylistCreator {
|
||||
id: creator.id,
|
||||
name: creator.name,
|
||||
},
|
||||
tracks,
|
||||
tracks_count,
|
||||
duration,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<GetPlaylistResponse>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(playlists))
|
||||
}
|
||||
|
||||
#[post("/playlists")]
|
||||
|
@ -50,7 +69,7 @@ async fn create_playlist(
|
|||
) -> Result<HttpResponse, ErrorResponse> {
|
||||
let user = get_user(req)?;
|
||||
|
||||
playlist.creator_id = Option::from(user.id);
|
||||
playlist.creator_id = user.id;
|
||||
|
||||
Playlists::create(playlist.into_inner())?;
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
pub mod auth;
|
||||
pub mod import;
|
||||
pub mod me;
|
||||
pub mod playlists;
|
||||
pub mod search;
|
||||
pub mod users;
|
||||
|
|
|
@ -1,37 +1,51 @@
|
|||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::models::playlists::{PlaylistCreator, Playlists};
|
||||
use crate::models::tracks::TracksWithArtists;
|
||||
use actix_web::{get, web, HttpResponse};
|
||||
use crate::middlewares::user::get_user_option;
|
||||
use crate::models::playlists::{PlaylistCreator, PlaylistTrack, Playlists};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse, Scope};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn routes() -> Scope {
|
||||
web::scope("/playlists").service(get_playlist)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct GetPlaylistResponse {
|
||||
pub struct GetPlaylistResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub playlist_type: String,
|
||||
pub parent_id: Option<String>,
|
||||
pub creator: PlaylistCreator,
|
||||
pub tracks: Vec<TracksWithArtists>,
|
||||
pub tracks: Vec<PlaylistTrack>,
|
||||
pub tracks_count: usize,
|
||||
pub duration: usize,
|
||||
}
|
||||
|
||||
#[get("/{playlist_id}")]
|
||||
pub async fn get_playlist(path: web::Path<String>) -> Result<HttpResponse, ErrorResponse> {
|
||||
async fn get_playlist(
|
||||
req: HttpRequest,
|
||||
path: web::Path<String>,
|
||||
) -> Result<HttpResponse, ErrorResponse> {
|
||||
let playlist_id = path.into_inner();
|
||||
let playlist = Playlists::find(playlist_id.as_str())?;
|
||||
|
||||
if !playlist.can_see(get_user_option(req)) {
|
||||
return Err(ErrorResponse::new(
|
||||
"Playlist not found",
|
||||
StatusCode::NOT_FOUND,
|
||||
));
|
||||
}
|
||||
|
||||
let creator = playlist.get_creator()?;
|
||||
|
||||
let tracks = playlist.get_tracks()?;
|
||||
let tracks_count = tracks.len();
|
||||
let duration = tracks
|
||||
.iter()
|
||||
.map(|track| track.duration_ms)
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap() as usize;
|
||||
let (duration, tracks_count) = playlist.get_data(&tracks);
|
||||
|
||||
Ok(HttpResponse::Ok().json(GetPlaylistResponse {
|
||||
id: playlist.id.to_string(),
|
||||
name: playlist.name.to_string(),
|
||||
playlist_type: playlist.playlist_type.to_string(),
|
||||
parent_id: playlist.parent_id,
|
||||
creator: PlaylistCreator {
|
||||
id: creator.id,
|
||||
name: creator.name,
|
||||
|
|
52
src/routes/search.rs
Normal file
52
src/routes/search.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::models::spotify;
|
||||
use crate::services::spotify as Spotify;
|
||||
use actix_web::{get, web, HttpResponse, Result, Scope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn routes() -> Scope {
|
||||
web::scope("/search").service(search)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SearchQuery {
|
||||
q: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchResponse {
|
||||
tracks: Vec<SearchTracks>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SearchTracks {
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
// pub artists: String,
|
||||
pub duration_ms: i32,
|
||||
}
|
||||
|
||||
impl From<spotify::Track> for SearchTracks {
|
||||
fn from(value: spotify::Track) -> SearchTracks {
|
||||
SearchTracks {
|
||||
id: value.id,
|
||||
title: value.name,
|
||||
duration_ms: value.duration_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("")]
|
||||
async fn search(query: web::Query<SearchQuery>) -> Result<HttpResponse, ErrorResponse> {
|
||||
let spotify = Spotify::instance().await;
|
||||
let search = spotify.search(&query.q).await.unwrap();
|
||||
|
||||
Ok(HttpResponse::Ok().json(SearchResponse {
|
||||
tracks: search
|
||||
.tracks
|
||||
.items
|
||||
.into_iter()
|
||||
.map(SearchTracks::from)
|
||||
.collect::<Vec<SearchTracks>>(),
|
||||
}))
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
use crate::middlewares::error::ErrorResponse;
|
||||
use crate::models::playlists::Playlists;
|
||||
use crate::models::user::Users;
|
||||
use crate::models::{
|
||||
playlists::{PlaylistCreator, Playlists},
|
||||
users::Users,
|
||||
};
|
||||
use crate::routes::playlists::GetPlaylistResponse;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{get, web, HttpResponse, Result, Scope};
|
||||
use diesel::result::Error as DBError;
|
||||
|
@ -24,18 +27,11 @@ fn get_a_user(user_id: &str) -> Result<Users, ErrorResponse> {
|
|||
|
||||
match user {
|
||||
Ok(user) => Ok(user),
|
||||
Err(DBError::NotFound) => {
|
||||
return Err(ErrorResponse {
|
||||
message: "User not found".to_string(),
|
||||
status: StatusCode::NOT_FOUND,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return Err(ErrorResponse {
|
||||
message: "Unknown error".to_string(),
|
||||
status: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
})
|
||||
}
|
||||
Err(DBError::NotFound) => Err(ErrorResponse::new("User not found", StatusCode::NOT_FOUND)),
|
||||
_ => Err(ErrorResponse::new(
|
||||
"Unknown error",
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,6 +52,29 @@ async fn get_user_playlists(path: web::Path<String>) -> Result<HttpResponse, Err
|
|||
let user = get_a_user(&path.into_inner())?;
|
||||
|
||||
let playlists = Playlists::find_for_user(&user.id, true)?;
|
||||
let playlists = playlists
|
||||
.into_iter()
|
||||
.map(|playlist| {
|
||||
let creator = playlist.get_creator().unwrap();
|
||||
|
||||
let tracks = playlist.get_tracks().unwrap();
|
||||
let (duration, tracks_count) = playlist.get_data(&tracks);
|
||||
|
||||
GetPlaylistResponse {
|
||||
id: playlist.id.to_string(),
|
||||
name: playlist.name.to_string(),
|
||||
playlist_type: playlist.playlist_type.to_string(),
|
||||
parent_id: playlist.parent_id,
|
||||
creator: PlaylistCreator {
|
||||
id: creator.id,
|
||||
name: creator.name,
|
||||
},
|
||||
tracks,
|
||||
tracks_count,
|
||||
duration,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<GetPlaylistResponse>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(playlists))
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ diesel::table! {
|
|||
id -> Varchar,
|
||||
#[max_length = 255]
|
||||
name -> Varchar,
|
||||
created_at -> Timestamp,
|
||||
slug -> Varchar,
|
||||
created_at -> Nullable<Timestamp>,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
#[max_length = 21]
|
||||
#[max_length = 22]
|
||||
spotify_id -> Nullable<Varchar>,
|
||||
#[max_length = 10]
|
||||
tidal_id -> Nullable<Varchar>,
|
||||
|
@ -32,8 +33,12 @@ diesel::table! {
|
|||
name -> Varchar,
|
||||
public -> Bool,
|
||||
#[max_length = 24]
|
||||
playlist_type -> Varchar,
|
||||
#[max_length = 24]
|
||||
parent_id -> Nullable<Varchar>,
|
||||
#[max_length = 24]
|
||||
creator_id -> Varchar,
|
||||
created_at -> Timestamp,
|
||||
created_at -> Nullable<Timestamp>,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +49,7 @@ diesel::table! {
|
|||
playlist_id -> Varchar,
|
||||
#[max_length = 24]
|
||||
track_id -> Varchar,
|
||||
added_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,9 +60,9 @@ diesel::table! {
|
|||
#[max_length = 255]
|
||||
title -> Varchar,
|
||||
duration_ms -> Int4,
|
||||
created_at -> Timestamp,
|
||||
created_at -> Nullable<Timestamp>,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
#[max_length = 21]
|
||||
#[max_length = 22]
|
||||
spotify_id -> Nullable<Varchar>,
|
||||
#[max_length = 10]
|
||||
tidal_id -> Nullable<Varchar>,
|
||||
|
@ -73,7 +79,7 @@ diesel::table! {
|
|||
email -> Varchar,
|
||||
password -> Text,
|
||||
updated_at -> Nullable<Timestamp>,
|
||||
created_at -> Timestamp,
|
||||
created_at -> Nullable<Timestamp>,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
src/services/mod.rs
Normal file
2
src/services/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod service;
|
||||
pub mod spotify;
|
11
src/services/service.rs
Normal file
11
src/services/service.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait Service: Send + Sync {
|
||||
fn get_token(&self) -> &String;
|
||||
|
||||
async fn fetch_token(&mut self);
|
||||
// async fn get_track_by_isrc(&self, isrc: &str);
|
||||
|
||||
fn is_expired(&self) -> bool;
|
||||
}
|
129
src/services/spotify.rs
Normal file
129
src/services/spotify.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use crate::{models::spotify::APISearchResponse, services::service::Service, utils::get_env};
|
||||
use actix_web::http::Method;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{Duration, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SpotifyResponse {
|
||||
access_token: String,
|
||||
expires_in: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Spotify {
|
||||
token: String,
|
||||
expires_at: i64,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref SPOTIFY_INSTANCE: Mutex<Spotify> = Mutex::new(Spotify::new());
|
||||
}
|
||||
|
||||
pub async fn instance() -> Spotify {
|
||||
let mut spotify = SPOTIFY_INSTANCE.lock().unwrap().clone();
|
||||
|
||||
// Fetch token if expired
|
||||
spotify.fetch_token().await;
|
||||
|
||||
spotify
|
||||
}
|
||||
|
||||
impl Spotify {
|
||||
pub fn new() -> Spotify {
|
||||
Spotify {
|
||||
token: String::new(),
|
||||
expires_at: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn api(&self, url: &str, method: Method) -> RequestBuilder {
|
||||
let client = Client::new();
|
||||
|
||||
client
|
||||
.request(method, format!("https://api.spotify.com/v1{url}"))
|
||||
.bearer_auth(self.get_token())
|
||||
}
|
||||
|
||||
// pub async fn get_track(self, spotify_id: &str) {
|
||||
// let spotify = &SPOTIFY;
|
||||
// }
|
||||
|
||||
// pub async fn get_artist(self, spotify_id: &str) {}
|
||||
|
||||
// pub async fn get_track_by_isrc(self, isrc: &str) {}
|
||||
|
||||
pub async fn search(&self, search: &str) -> Result<APISearchResponse, ()> {
|
||||
let response = self
|
||||
.api("/search", Method::GET)
|
||||
.query(&[("q", search), ("type", "album,artist,track")])
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(res) => {
|
||||
let data = res.json::<APISearchResponse>().await;
|
||||
|
||||
match data {
|
||||
Ok(data) => Ok(data),
|
||||
Err(_) => {
|
||||
panic!("Invalid response (Spotify Search)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Invalid response (Spotify Search)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Service for Spotify {
|
||||
fn get_token(&self) -> &String {
|
||||
&self.token
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
self.expires_at < chrono::Utc::now().timestamp()
|
||||
}
|
||||
|
||||
async fn fetch_token(&mut self) {
|
||||
if !self.is_expired() {
|
||||
return;
|
||||
}
|
||||
|
||||
let response = Client::new()
|
||||
.post("https://accounts.spotify.com/api/token")
|
||||
.basic_auth(
|
||||
get_env("SPOTIFY_CLIENT_ID"),
|
||||
Some(get_env("SPOTIFY_CLIENT_SECRET")),
|
||||
)
|
||||
.form(&[("grant_type", "client_credentials")])
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match response {
|
||||
Ok(res) => {
|
||||
let data = res.json::<SpotifyResponse>().await;
|
||||
|
||||
match data {
|
||||
Ok(data) => {
|
||||
self.token = data.access_token;
|
||||
self.expires_at =
|
||||
(Utc::now() + Duration::seconds(data.expires_in)).timestamp();
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Invalid response (Spotify Token)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Invalid response (Spotify Token)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
src/utils.rs
Normal file
3
src/utils.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn get_env(key: &str) -> String {
|
||||
std::env::var(key).unwrap()
|
||||
}
|
Loading…
Reference in a new issue