Shulkers of fixes (#327)

* Shulkers of fixes

* Fix validation message

* Update deps

* Bump docker image version
This commit is contained in:
Geometrically
2022-03-27 19:12:42 -07:00
committed by GitHub
parent 7415b07586
commit d1c0c9739d
42 changed files with 683 additions and 700 deletions

241
Cargo.lock generated
View File

@@ -45,11 +45,10 @@ dependencies = [
[[package]] [[package]]
name = "actix-cors" name = "actix-cors"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30dbd116ef7532f56e2f6d7c511736ea0b124d914ee8820a5271247bf89f06aa" checksum = "414360eed71ba2d5435b185ba43ecbe281dfab5df3898286d6b7be8074372c92"
dependencies = [ dependencies = [
"actix-service",
"actix-utils", "actix-utils",
"actix-web", "actix-web",
"derive_more", "derive_more",
@@ -61,9 +60,9 @@ dependencies = [
[[package]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.0.0" version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3fdd63b9cfeaf92eeeece719dabbddddb420a57d3fd171ce1490ecfb7086b1" checksum = "a5885cb81a0d4d0d322864bea1bb6c2a8144626b4fdc625d4c51eba197e7797a"
dependencies = [ dependencies = [
"actix-codec", "actix-codec",
"actix-rt", "actix-rt",
@@ -140,9 +139,9 @@ dependencies = [
[[package]] [[package]]
name = "actix-rt" name = "actix-rt"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf3f2183be1241ed4dd22611850b85d38de0b08a09f1f7bcccbd0809084b359" checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000"
dependencies = [ dependencies = [
"actix-macros", "actix-macros",
"futures-core", "futures-core",
@@ -151,20 +150,20 @@ dependencies = [
[[package]] [[package]]
name = "actix-server" name = "actix-server"
version = "2.0.0" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9e7472ac180abb0a8e592b653744345983a7a14f44691c8394a799d0df4dbbf" checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824"
dependencies = [ dependencies = [
"actix-rt", "actix-rt",
"actix-service", "actix-service",
"actix-utils", "actix-utils",
"futures-core", "futures-core",
"futures-util", "futures-util",
"log",
"mio", "mio",
"num_cpus", "num_cpus",
"socket2", "socket2",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
@@ -224,7 +223,7 @@ dependencies = [
"serde_urlencoded", "serde_urlencoded",
"smallvec", "smallvec",
"socket2", "socket2",
"time 0.3.7", "time 0.3.9",
"url", "url",
] ]
@@ -300,9 +299,15 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.55" version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "async-channel" name = "async-channel"
@@ -317,9 +322,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.52" version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -555,7 +560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
"time 0.3.7", "time 0.3.9",
"version_check", "version_check",
] ]
@@ -577,9 +582,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -610,9 +615,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.2" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@@ -620,9 +625,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce" checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"crossbeam-utils", "crossbeam-utils",
@@ -630,9 +635,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.7" version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"lazy_static", "lazy_static",
@@ -660,9 +665,9 @@ dependencies = [
[[package]] [[package]]
name = "curl" name = "curl"
version = "0.4.42" version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de97b894edd5b5bcceef8b78d7da9b75b1d2f2f9a910569d0bde3dd31d84939" checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f"
dependencies = [ dependencies = [
"curl-sys", "curl-sys",
"libc", "libc",
@@ -675,9 +680,9 @@ dependencies = [
[[package]] [[package]]
name = "curl-sys" name = "curl-sys"
version = "0.4.52+curl-7.81.0" version = "0.4.53+curl-7.82.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b8c2d1023ea5fded5b7b892e4b8e95f70038a421126a056761a84246a28971" checksum = "8092905a5a9502c312f223b2775f57ec5c5b715f9a15ee9d2a8591d1364a0352"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -777,9 +782,9 @@ dependencies = [
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [ dependencies = [
"libc", "libc",
"redox_users", "redox_users",
@@ -1051,18 +1056,18 @@ dependencies = [
[[package]] [[package]]
name = "gumdrop" name = "gumdrop"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3"
dependencies = [ dependencies = [
"gumdrop_derive", "gumdrop_derive",
] ]
[[package]] [[package]]
name = "gumdrop_derive" name = "gumdrop_derive"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1071,9 +1076,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.11" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" checksum = "62eeb471aa3e3c9197aa4bfeabfe02982f6dc96f750486c0bb0009ac58b26d2b"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -1191,9 +1196,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.17" version = "0.14.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
@@ -1270,15 +1275,15 @@ dependencies = [
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
[[package]] [[package]]
name = "isahc" name = "isahc"
version = "1.6.0" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d140e84730d325378912ede32d7cd53ef1542725503b3353e5ec8113c7c6f588" checksum = "437f8808009c031df3c1d532c8fd7e3d73239dfe522ebf0b94b5e34d5d01044b"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"castaway", "castaway",
@@ -1301,6 +1306,15 @@ dependencies = [
"waker-fn", "waker-fn",
] ]
[[package]]
name = "iso8601-duration"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b51dd97fa24074214b9eb14da518957573f4dec3189112610ae1ccec9ac464"
dependencies = [
"nom 5.1.2",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.3" version = "0.10.3"
@@ -1393,10 +1407,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "lexical-core"
version = "0.2.119" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]] [[package]]
name = "libnghttp2-sys" name = "libnghttp2-sys"
@@ -1410,9 +1437,9 @@ dependencies = [
[[package]] [[package]]
name = "libz-sys" name = "libz-sys"
version = "1.1.3" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" checksum = "6f35facd4a5673cb5a48822be2be1d4236c1c99cb4113cab7061ac720d5bf859"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -1449,9 +1476,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.14" version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@@ -1492,14 +1519,19 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "meilisearch-sdk" name = "meilisearch-sdk"
version = "0.6.0" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8972f69aef330566ece2a76e61ebb9383565b03c45aeb95dbd66ad672186497" checksum = "4eae404a5052ee03460ad87998e00cc78e5c68ec3eb23f673f1c13007d150697"
dependencies = [ dependencies = [
"async-trait",
"futures",
"isahc", "isahc",
"iso8601-duration",
"js-sys",
"log", "log",
"serde", "serde",
"serde_json", "serde_json",
"time 0.3.9",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
@@ -1544,14 +1576,15 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"miow", "miow",
"ntapi", "ntapi",
"wasi 0.11.0+wasi-snapshot-preview1",
"winapi", "winapi",
] ]
@@ -1566,9 +1599,9 @@ dependencies = [
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.8" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" checksum = "09bf6f32a3afefd0b587ee42ed19acd945c6d1f3b5424040f50b2f24ab16be77"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
@@ -1584,13 +1617,23 @@ dependencies = [
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.0" version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [ dependencies = [
"memchr", "memchr",
"minimal-lexical", "minimal-lexical",
"version_check",
] ]
[[package]] [[package]]
@@ -1633,18 +1676,18 @@ dependencies = [
[[package]] [[package]]
name = "num_threads" name = "num_threads"
version = "0.1.3" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@@ -1751,9 +1794,9 @@ dependencies = [
[[package]] [[package]]
name = "paste" name = "paste"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
@@ -1862,9 +1905,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1942,28 +1985,29 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.10" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" checksum = "7776223e2696f1aa4c6b0170e83212f47296a00424305117d013dfe86fb0fe55"
dependencies = [ dependencies = [
"getrandom 0.2.5", "getrandom 0.2.5",
"redox_syscall", "redox_syscall",
"thiserror",
] ]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.5.4" version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -1987,9 +2031,9 @@ dependencies = [
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.9" version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -2072,7 +2116,7 @@ dependencies = [
"serde-xml-rs", "serde-xml-rs",
"serde_derive", "serde_derive",
"sha2", "sha2",
"time 0.3.7", "time 0.3.9",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"url", "url",
@@ -2352,7 +2396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
dependencies = [ dependencies = [
"itertools", "itertools",
"nom", "nom 7.1.1",
"unicode_categories", "unicode_categories",
] ]
@@ -2451,6 +2495,12 @@ dependencies = [
"tokio-rustls", "tokio-rustls",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "stringprep" name = "stringprep"
version = "0.1.2" version = "0.1.2"
@@ -2475,9 +2525,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.86" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2500,9 +2550,9 @@ dependencies = [
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [ dependencies = [
"winapi-util", "winapi-util",
] ]
@@ -2539,21 +2589,22 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.7" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
dependencies = [ dependencies = [
"itoa", "itoa",
"libc", "libc",
"num_threads", "num_threads",
"serde",
"time-macros", "time-macros",
] ]
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
@@ -2657,9 +2708,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.31" version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"log", "log",
@@ -2670,9 +2721,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.19" version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2681,9 +2732,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.22" version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
] ]
@@ -2862,6 +2913,12 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.79" version = "0.2.79"
@@ -3058,9 +3115,9 @@ checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.7.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [ dependencies = [
"winapi", "winapi",
] ]

View File

@@ -4,6 +4,7 @@ version = "0.2.0"
#Team members, please add your emails and usernames #Team members, please add your emails and usernames
authors = ["geometrically <jai.a@tuta.io>", "Redblueflame <contact@redblueflame.com>", "Aeledfyr <aeledfyr@gmail.com>", "Charalampos Fanoulis <yo@fanoulis.dev>", "AppleTheGolden <scotsbox@protonmail.com>"] authors = ["geometrically <jai.a@tuta.io>", "Redblueflame <contact@redblueflame.com>", "Aeledfyr <aeledfyr@gmail.com>", "Charalampos Fanoulis <yo@fanoulis.dev>", "AppleTheGolden <scotsbox@protonmail.com>"]
edition = "2018" edition = "2018"
license = "AGPL-3.0"
[[bin]] [[bin]]
name = "labrinth" name = "labrinth"
@@ -19,7 +20,7 @@ tokio-stream = "0.1.8"
actix-multipart = "0.4.0" actix-multipart = "0.4.0"
actix-cors = "0.6.0" actix-cors = "0.6.0"
meilisearch-sdk = "0.6.0" meilisearch-sdk = "0.15.0"
reqwest = { version = "0.11.9", features = ["json"] } reqwest = { version = "0.11.9", features = ["json"] }
yaserde = "0.6.0" yaserde = "0.6.0"

View File

@@ -1,4 +1,4 @@
FROM rust:1.55.0 as build FROM rust:1.59.0 as build
ENV PKG_CONFIG_ALLOW_CROSS=1 ENV PKG_CONFIG_ALLOW_CROSS=1
WORKDIR /usr/src/labrinth WORKDIR /usr/src/labrinth

View File

@@ -12,7 +12,7 @@ services:
POSTGRES_PASSWORD: labrinth POSTGRES_PASSWORD: labrinth
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
meilisearch: meilisearch:
image: getmeili/meilisearch:v0.19.0 image: getmeili/meilisearch:v0.25.0
restart: on-failure restart: on-failure
ports: ports:
- "7700:7700" - "7700:7700"

View File

@@ -163,6 +163,19 @@
"nullable": [] "nullable": []
} }
}, },
"06a92b638c77276f36185788748191e7731a2cce874ecca4af913d0d0412d223": {
"query": "\n UPDATE versions\n SET downloads = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int8"
]
},
"nullable": []
}
},
"06c2d67bcbc95baa4b7e5865ec9adec7f068c1dfd3f859c29465b8d8a40343e0": { "06c2d67bcbc95baa4b7e5865ec9adec7f068c1dfd3f859c29465b8d8a40343e0": {
"query": "\n SELECT m.id FROM mods m\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE\n WHERE tm.user_id = $1\n ", "query": "\n SELECT m.id FROM mods m\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE\n WHERE tm.user_id = $1\n ",
"describe": { "describe": {
@@ -851,6 +864,19 @@
] ]
} }
}, },
"33a965c7dc615d3b701c05299889357db8dd36d378850625d2602ba471af4885": {
"query": "\n UPDATE mods\n SET downloads = downloads + $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int8"
]
},
"nullable": []
}
},
"33fc96ac71cfa382991cfb153e89da1e9f43ebf5367c28b30c336b758222307b": { "33fc96ac71cfa382991cfb153e89da1e9f43ebf5367c28b30c336b758222307b": {
"query": "\n DELETE FROM loaders_versions\n WHERE loaders_versions.version_id = $1\n ", "query": "\n DELETE FROM loaders_versions\n WHERE loaders_versions.version_id = $1\n ",
"describe": { "describe": {
@@ -4102,6 +4128,18 @@
"nullable": [] "nullable": []
} }
}, },
"bd0d1da185dc7d21ccbbfde86fc093ce9eda7dd7e07f7a53882d427010fd58ca": {
"query": "\n DELETE FROM dependencies WHERE dependent_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
}
},
"bdaab7da16d07169c29d96330fcc17ef2fb87fdfbadca23b7289c64420ac3a04": { "bdaab7da16d07169c29d96330fcc17ef2fb87fdfbadca23b7289c64420ac3a04": {
"query": "\n SELECT id, user_id, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2)\n ", "query": "\n SELECT id, user_id, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2)\n ",
"describe": { "describe": {
@@ -5127,18 +5165,6 @@
] ]
} }
}, },
"e1f910af9153befd7faed7ae9db87d2886f211cb43f61b6bb093ac2bfb8a0555": {
"query": "\n DELETE FROM dependencies WHERE dependent_id = $1 AND dependency_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
}
},
"e29da865af4a0a110275b9756394546a3bb88bff40e18c66029651f515caed98": { "e29da865af4a0a110275b9756394546a3bb88bff40e18c66029651f515caed98": {
"query": "\n SELECT f.id id FROM files f\n WHERE f.version_id = $1\n ", "query": "\n SELECT f.id id FROM files f\n WHERE f.version_id = $1\n ",
"describe": { "describe": {

View File

@@ -28,7 +28,7 @@ macro_rules! generate_ids {
retry_count += 1; retry_count += 1;
if retry_count > ID_RETRY_COUNT { if retry_count > ID_RETRY_COUNT {
return Err(DatabaseError::RandomIdError); return Err(DatabaseError::RandomId);
} }
} }

View File

@@ -24,16 +24,16 @@ pub use version_item::VersionFile;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum DatabaseError { pub enum DatabaseError {
#[error("Error while interacting with the database: {0}")] #[error("Error while interacting with the database: {0}")]
DatabaseError(#[from] sqlx::error::Error), Database(#[from] sqlx::error::Error),
#[error("Error while trying to generate random ID")] #[error("Error while trying to generate random ID")]
RandomIdError, RandomId,
#[error( #[error(
"Invalid identifier: Category/version names must contain only ASCII \ "Invalid identifier: Category/version names must contain only ASCII \
alphanumeric characters or '_-'." alphanumeric characters or '_-'."
)] )]
InvalidIdentifier(String), InvalidIdentifier(String),
#[error("Invalid permissions bitflag!")] #[error("Invalid permissions bitflag!")]
BitflagError, Bitflag,
#[error("A database request failed")] #[error("A database request failed")]
Other(String), Other(String),
} }

View File

@@ -124,7 +124,7 @@ impl TeamMember {
accepted: m.accepted, accepted: m.accepted,
}))) })))
} else { } else {
Ok(Some(Err(super::DatabaseError::BitflagError))) Ok(Some(Err(super::DatabaseError::Bitflag)))
} }
} else { } else {
Ok(None) Ok(None)
@@ -186,7 +186,7 @@ impl TeamMember {
}, },
}))) })))
} else { } else {
Ok(Some(Err(super::DatabaseError::BitflagError))) Ok(Some(Err(super::DatabaseError::Bitflag)))
} }
} else { } else {
Ok(None) Ok(None)
@@ -234,7 +234,7 @@ impl TeamMember {
accepted: m.accepted, accepted: m.accepted,
}))) })))
} else { } else {
Ok(Some(Err(super::DatabaseError::BitflagError))) Ok(Some(Err(super::DatabaseError::Bitflag)))
} }
} else { } else {
Ok(None) Ok(None)
@@ -282,7 +282,7 @@ impl TeamMember {
accepted: m.accepted, accepted: m.accepted,
}))) })))
} else { } else {
Ok(Some(Err(super::DatabaseError::BitflagError))) Ok(Some(Err(super::DatabaseError::Bitflag)))
} }
} else { } else {
Ok(None) Ok(None)
@@ -326,7 +326,7 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::BitflagError)?, .ok_or(super::DatabaseError::Bitflag)?,
accepted: m.accepted, accepted: m.accepted,
})) }))
} else { } else {
@@ -362,7 +362,7 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::BitflagError)?, .ok_or(super::DatabaseError::Bitflag)?,
accepted: m.accepted, accepted: m.accepted,
})) }))
} else { } else {
@@ -510,7 +510,7 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::BitflagError)?, .ok_or(super::DatabaseError::Bitflag)?,
accepted: m.accepted, accepted: m.accepted,
})) }))
} else { } else {
@@ -546,7 +546,7 @@ impl TeamMember {
user_id, user_id,
role: m.role, role: m.role,
permissions: Permissions::from_bits(m.permissions as u64) permissions: Permissions::from_bits(m.permissions as u64)
.ok_or(super::DatabaseError::BitflagError)?, .ok_or(super::DatabaseError::Bitflag)?,
accepted: m.accepted, accepted: m.accepted,
})) }))
} else { } else {

View File

@@ -460,7 +460,7 @@ impl Version {
sqlx::query!( sqlx::query!(
" "
DELETE FROM dependencies WHERE dependent_id = $1 AND dependency_id = $1 DELETE FROM dependencies WHERE dependent_id = $1
", ",
id as VersionId, id as VersionId,
) )

View File

@@ -31,8 +31,6 @@ struct Config {
#[options(no_short, help = "Skip indexing on startup")] #[options(no_short, help = "Skip indexing on startup")]
skip_first_index: bool, skip_first_index: bool,
#[options(no_short, help = "Reset the settings of the indices")]
reconfigure_indices: bool,
#[options(no_short, help = "Reset the documents in the indices")] #[options(no_short, help = "Reset the documents in the indices")]
reset_indices: bool, reset_indices: bool,
@@ -79,12 +77,6 @@ async fn main() -> std::io::Result<()> {
.await .await
.unwrap(); .unwrap();
return Ok(()); return Ok(());
} else if config.reconfigure_indices {
info!("Reconfiguring indices");
search::indexing::reconfigure_indices(&search_config)
.await
.unwrap();
return Ok(());
} }
// Allow manually skipping the initial indexing for quicker iteration // Allow manually skipping the initial indexing for quicker iteration
@@ -252,18 +244,18 @@ async fn main() -> std::io::Result<()> {
if let Some(header) = if let Some(header) =
req.headers().get("CF-Connecting-IP") req.headers().get("CF-Connecting-IP")
{ {
header.to_str().map_err(|_| { header
ARError::IdentificationError .to_str()
})? .map_err(|_| ARError::Identification)?
} else { } else {
connection_info connection_info
.peer_addr() .peer_addr()
.ok_or(ARError::IdentificationError)? .ok_or(ARError::Identification)?
} }
} else { } else {
connection_info connection_info
.peer_addr() .peer_addr()
.ok_or(ARError::IdentificationError)? .ok_or(ARError::Identification)?
}, },
); );

View File

@@ -475,13 +475,14 @@ pub struct Loader(pub String);
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct SearchRequest { pub struct SearchRequest {
pub query: Option<String>, pub query: Option<String>,
/// Must match a json 2 deep array of strings `[["categories:misc"]]`
// TODO: We may want to have a better representation of this, so that
// we are less likely to break backwards compatibility
pub facets: Option<String>,
pub filters: Option<String>,
pub version: Option<String>,
pub offset: Option<String>, pub offset: Option<String>,
pub index: Option<String>, pub index: Option<String>,
pub limit: Option<String>, pub limit: Option<String>,
pub new_filters: Option<String>,
// Deprecated values below. WILL BE REMOVED V3!
pub facets: Option<String>,
pub filters: Option<String>,
pub version: Option<String>,
} }

View File

@@ -1,4 +1,5 @@
//! Errors that can occur during middleware processing stage //! Errors that can occur during middleware processing stage
use crate::models::error::ApiError;
use actix_web::ResponseError; use actix_web::ResponseError;
use log::*; use log::*;
use thiserror::Error; use thiserror::Error;
@@ -11,14 +12,14 @@ use thiserror::Error;
pub enum ARError { pub enum ARError {
/// Read/Write error on store /// Read/Write error on store
#[error("read/write operatiion failed: {0}")] #[error("read/write operatiion failed: {0}")]
ReadWriteError(String), ReadWrite(String),
/// Identifier error /// Identifier error
#[error("client identification failed")] #[error("client identification failed")]
IdentificationError, Identification,
/// Limited Error /// Limited Error
#[error("You are being ratelimited. Please wait {reset} seconds. {remaining}/{max_requests} remaining.")] #[error("You are being ratelimited. Please wait {reset} seconds. {remaining}/{max_requests} remaining.")]
LimitedError { Limited {
max_requests: usize, max_requests: usize,
remaining: usize, remaining: usize,
reset: u64, reset: u64,
@@ -28,7 +29,7 @@ pub enum ARError {
impl ResponseError for ARError { impl ResponseError for ARError {
fn error_response(&self) -> actix_web::HttpResponse { fn error_response(&self) -> actix_web::HttpResponse {
match self { match self {
Self::LimitedError { Self::Limited {
max_requests, max_requests,
remaining, remaining,
reset, reset,
@@ -44,10 +45,17 @@ impl ResponseError for ARError {
)); ));
response response
.insert_header(("x-ratelimit-reset", reset.to_string())); .insert_header(("x-ratelimit-reset", reset.to_string()));
response.body(self.to_string()) response.json(ApiError {
error: "ratelimit_error",
description: &self.to_string(),
})
} }
_ => actix_web::HttpResponse::build(self.status_code()) _ => actix_web::HttpResponse::build(self.status_code()).json(
.body(self.to_string()), ApiError {
error: "ratelimit_error",
description: &self.to_string(),
},
),
} }
} }
} }

View File

@@ -107,13 +107,11 @@ impl Handler<ActorMessage> for MemoryStoreActor {
let new_val = val_mut.0; let new_val = val_mut.0;
ActorResponse::Update(Box::pin(future::ready(Ok(new_val)))) ActorResponse::Update(Box::pin(future::ready(Ok(new_val))))
} }
None => { None => ActorResponse::Update(Box::pin(future::ready(Err(
return ActorResponse::Update(Box::pin(future::ready(Err( ARError::ReadWrite(
ARError::ReadWriteError( "memory store: read failed!".to_string(),
"memory store: read failed!".to_string(), ),
), )))),
))))
}
}, },
ActorMessage::Get(key) => { ActorMessage::Get(key) => {
if self.inner.contains_key(&key) { if self.inner.contains_key(&key) {
@@ -121,7 +119,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(c) => c, Some(c) => c,
None => { None => {
return ActorResponse::Get(Box::pin(future::ready( return ActorResponse::Get(Box::pin(future::ready(
Err(ARError::ReadWriteError( Err(ARError::ReadWrite(
"memory store: read failed!".to_string(), "memory store: read failed!".to_string(),
)), )),
))) )))
@@ -138,7 +136,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(d) => d, Some(d) => d,
None => { None => {
return ActorResponse::Expire(Box::pin(future::ready( return ActorResponse::Expire(Box::pin(future::ready(
Err(ARError::ReadWriteError( Err(ARError::ReadWrite(
"memory store: read failed!".to_string(), "memory store: read failed!".to_string(),
)), )),
))) )))
@@ -156,7 +154,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(c) => c, Some(c) => c,
None => { None => {
return ActorResponse::Remove(Box::pin(future::ready( return ActorResponse::Remove(Box::pin(future::ready(
Err(ARError::ReadWriteError( Err(ARError::ReadWrite(
"memory store: remove failed!".to_string(), "memory store: remove failed!".to_string(),
)), )),
))) )))

View File

@@ -1,4 +1,3 @@
//! RateLimiter middleware for actix application
use crate::ratelimit::errors::ARError; use crate::ratelimit::errors::ARError;
use crate::ratelimit::{ActorMessage, ActorResponse}; use crate::ratelimit::{ActorMessage, ActorResponse};
use actix::dev::*; use actix::dev::*;
@@ -19,28 +18,9 @@ use std::{
time::Duration, time::Duration,
}; };
/// Type that implements the ratelimit middleware. type RateLimiterIdentifier =
/// Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>;
/// This accepts _interval_ which specifies the
/// window size, _max_requests_ which specifies the maximum number of requests in that window, and
/// _store_ which is essentially a data store used to store client access information. Entry is removed from
/// the store after _interval_.
///
/// # Example
/// ```rust
/// # use std::time::Duration;
/// use actix_ratelimit::{MemoryStore, MemoryStoreActor};
/// use actix_ratelimit::RateLimiter;
///
/// #[actix_rt::main]
/// async fn main() {
/// let store = MemoryStore::new();
/// let ratelimiter = RateLimiter::new(
/// MemoryStoreActor::from(store.clone()).start())
/// .with_interval(Duration::from_secs(60))
/// .with_max_requests(100);
/// }
/// ```
pub struct RateLimiter<T> pub struct RateLimiter<T>
where where
T: Handler<ActorMessage> + Send + Sync + 'static, T: Handler<ActorMessage> + Send + Sync + 'static,
@@ -49,7 +29,7 @@ where
interval: Duration, interval: Duration,
max_requests: usize, max_requests: usize,
store: Addr<T>, store: Addr<T>,
identifier: Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError>>>, identifier: RateLimiterIdentifier,
ignore_ips: Vec<String>, ignore_ips: Vec<String>,
} }
@@ -62,9 +42,8 @@ where
pub fn new(store: Addr<T>) -> Self { pub fn new(store: Addr<T>) -> Self {
let identifier = |req: &ServiceRequest| { let identifier = |req: &ServiceRequest| {
let connection_info = req.connection_info(); let connection_info = req.connection_info();
let ip = connection_info let ip =
.peer_addr() connection_info.peer_addr().ok_or(ARError::Identification)?;
.ok_or(ARError::IdentificationError)?;
Ok(String::from(ip)) Ok(String::from(ip))
}; };
RateLimiter { RateLimiter {
@@ -144,8 +123,7 @@ where
// Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter // Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter
max_requests: usize, max_requests: usize,
interval: u64, interval: u64,
identifier: identifier: RateLimiterIdentifier,
Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>,
ignore_ips: Vec<String>, ignore_ips: Vec<String>,
} }
@@ -187,7 +165,7 @@ where
let remaining: ActorResponse = store let remaining: ActorResponse = store
.send(ActorMessage::Get(String::from(&identifier))) .send(ActorMessage::Get(String::from(&identifier)))
.await .await
.map_err(|_| ARError::IdentificationError)?; .map_err(|_| ARError::Identification)?;
match remaining { match remaining {
ActorResponse::Get(opt) => { ActorResponse::Get(opt) => {
let opt = opt.await?; let opt = opt.await?;
@@ -199,7 +177,7 @@ where
))) )))
.await .await
.map_err(|_| { .map_err(|_| {
ARError::ReadWriteError( ARError::ReadWrite(
"Setting timeout".to_string(), "Setting timeout".to_string(),
) )
})?; })?;
@@ -209,7 +187,7 @@ where
}; };
if c == 0 { if c == 0 {
info!("Limit exceeded for client: {}", &identifier); info!("Limit exceeded for client: {}", &identifier);
Err(ARError::LimitedError { Err(ARError::Limited {
max_requests, max_requests,
remaining: c, remaining: c,
reset: reset.as_secs(), reset: reset.as_secs(),
@@ -224,7 +202,7 @@ where
}) })
.await .await
.map_err(|_| { .map_err(|_| {
ARError::ReadWriteError( ARError::ReadWrite(
"Decrementing ratelimit".to_string(), "Decrementing ratelimit".to_string(),
) )
})?; })?;
@@ -270,7 +248,7 @@ where
}) })
.await .await
.map_err(|_| { .map_err(|_| {
ARError::ReadWriteError( ARError::ReadWrite(
"Creating store entry".to_string(), "Creating store entry".to_string(),
) )
})?; })?;

View File

@@ -19,61 +19,51 @@ pub fn config(cfg: &mut ServiceConfig) {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AuthorizationError { pub enum AuthorizationError {
#[error("Environment Error")] #[error("Environment Error")]
EnvError(#[from] dotenv::Error), Env(#[from] dotenv::Error),
#[error("An unknown database error occured: {0}")] #[error("An unknown database error occured: {0}")]
SqlxDatabaseError(#[from] sqlx::Error), SqlxDatabase(#[from] sqlx::Error),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError), Database(#[from] crate::database::models::DatabaseError),
#[error("Error while parsing JSON: {0}")] #[error("Error while parsing JSON: {0}")]
SerDeError(#[from] serde_json::Error), SerDe(#[from] serde_json::Error),
#[error("Error while communicating to GitHub OAuth2")] #[error("Error while communicating to GitHub OAuth2")]
GithubError(#[from] reqwest::Error), Github(#[from] reqwest::Error),
#[error("Invalid Authentication credentials")] #[error("Invalid Authentication credentials")]
InvalidCredentialsError, InvalidCredentials,
#[error("Authentication Error: {0}")] #[error("Authentication Error: {0}")]
AuthenticationError(#[from] crate::util::auth::AuthenticationError), Authentication(#[from] crate::util::auth::AuthenticationError),
#[error("Error while decoding Base62")] #[error("Error while decoding Base62")]
DecodingError(#[from] DecodingError), Decoding(#[from] DecodingError),
} }
impl actix_web::ResponseError for AuthorizationError { impl actix_web::ResponseError for AuthorizationError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
AuthorizationError::EnvError(..) => { AuthorizationError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
AuthorizationError::SqlxDatabase(..) => {
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
} }
AuthorizationError::SqlxDatabaseError(..) => { AuthorizationError::Database(..) => {
StatusCode::INTERNAL_SERVER_ERROR StatusCode::INTERNAL_SERVER_ERROR
} }
AuthorizationError::DatabaseError(..) => { AuthorizationError::SerDe(..) => StatusCode::BAD_REQUEST,
StatusCode::INTERNAL_SERVER_ERROR AuthorizationError::Github(..) => StatusCode::FAILED_DEPENDENCY,
} AuthorizationError::InvalidCredentials => StatusCode::UNAUTHORIZED,
AuthorizationError::SerDeError(..) => StatusCode::BAD_REQUEST, AuthorizationError::Decoding(..) => StatusCode::BAD_REQUEST,
AuthorizationError::GithubError(..) => { AuthorizationError::Authentication(..) => StatusCode::UNAUTHORIZED,
StatusCode::FAILED_DEPENDENCY
}
AuthorizationError::InvalidCredentialsError => {
StatusCode::UNAUTHORIZED
}
AuthorizationError::DecodingError(..) => StatusCode::BAD_REQUEST,
AuthorizationError::AuthenticationError(..) => {
StatusCode::UNAUTHORIZED
}
} }
} }
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).json(ApiError { HttpResponse::build(self.status_code()).json(ApiError {
error: match self { error: match self {
AuthorizationError::EnvError(..) => "environment_error", AuthorizationError::Env(..) => "environment_error",
AuthorizationError::SqlxDatabaseError(..) => "database_error", AuthorizationError::SqlxDatabase(..) => "database_error",
AuthorizationError::DatabaseError(..) => "database_error", AuthorizationError::Database(..) => "database_error",
AuthorizationError::SerDeError(..) => "invalid_input", AuthorizationError::SerDe(..) => "invalid_input",
AuthorizationError::GithubError(..) => "github_error", AuthorizationError::Github(..) => "github_error",
AuthorizationError::InvalidCredentialsError => { AuthorizationError::InvalidCredentials => "invalid_credentials",
"invalid_credentials" AuthorizationError::Decoding(..) => "decoding_error",
} AuthorizationError::Authentication(..) => {
AuthorizationError::DecodingError(..) => "decoding_error",
AuthorizationError::AuthenticationError(..) => {
"authentication_error" "authentication_error"
} }
}, },
@@ -159,7 +149,7 @@ pub async fn auth_callback(
let duration = result.expires.signed_duration_since(now); let duration = result.expires.signed_duration_since(now);
if duration.num_seconds() < 0 { if duration.num_seconds() < 0 {
return Err(AuthorizationError::InvalidCredentialsError); return Err(AuthorizationError::InvalidCredentials);
} }
sqlx::query!( sqlx::query!(
@@ -256,6 +246,6 @@ pub async fn auth_callback(
.append_header(("Location", &*redirect_url)) .append_header(("Location", &*redirect_url))
.json(AuthorizationInit { url: redirect_url })) .json(AuthorizationInit { url: redirect_url }))
} else { } else {
Err(AuthorizationError::InvalidCredentialsError) Err(AuthorizationError::InvalidCredentials)
} }
} }

View File

@@ -114,7 +114,7 @@ pub async fn maven_metadata(
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/xml") .content_type("text/xml")
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?)) .body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?))
} }
fn find_file<'a>( fn find_file<'a>(
@@ -211,9 +211,9 @@ pub async fn version_file(
name: project.inner.title, name: project.inner.title,
description: project.inner.description, description: project.inner.description,
}; };
return Ok(HttpResponse::Ok().content_type("text/xml").body( return Ok(HttpResponse::Ok()
yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?, .content_type("text/xml")
)); .body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
} else if let Some(selected_file) = } else if let Some(selected_file) =
find_file(&project_id, &project, &version, &file) find_file(&project_id, &project, &version, &file)
{ {

View File

@@ -159,66 +159,66 @@ pub fn reports_config(cfg: &mut web::ServiceConfig) {
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ApiError { pub enum ApiError {
#[error("Environment Error")] #[error("Environment Error")]
EnvError(#[from] dotenv::Error), Env(#[from] dotenv::Error),
#[error("Error while uploading file")] #[error("Error while uploading file")]
FileHostingError(#[from] FileHostingError), FileHosting(#[from] FileHostingError),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError), Database(#[from] crate::database::models::DatabaseError),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
SqlxDatabaseError(#[from] sqlx::Error), SqlxDatabase(#[from] sqlx::Error),
#[error("Internal server error: {0}")] #[error("Internal server error: {0}")]
XmlError(String), Xml(String),
#[error("Deserialization error: {0}")] #[error("Deserialization error: {0}")]
JsonError(#[from] serde_json::Error), Json(#[from] serde_json::Error),
#[error("Authentication Error: {0}")] #[error("Authentication Error: {0}")]
AuthenticationError(#[from] crate::util::auth::AuthenticationError), Authentication(#[from] crate::util::auth::AuthenticationError),
#[error("Authentication Error: {0}")] #[error("Authentication Error: {0}")]
CustomAuthenticationError(String), CustomAuthentication(String),
#[error("Invalid Input: {0}")] #[error("Invalid Input: {0}")]
InvalidInputError(String), InvalidInput(String),
#[error("Error while validating input: {0}")] #[error("Error while validating input: {0}")]
ValidationError(String), Validation(String),
#[error("Search Error: {0}")] #[error("Search Error: {0}")]
SearchError(#[from] meilisearch_sdk::errors::Error), Search(#[from] meilisearch_sdk::errors::Error),
#[error("Indexing Error: {0}")] #[error("Indexing Error: {0}")]
IndexingError(#[from] crate::search::indexing::IndexingError), Indexing(#[from] crate::search::indexing::IndexingError),
} }
impl actix_web::ResponseError for ApiError { impl actix_web::ResponseError for ApiError {
fn status_code(&self) -> actix_web::http::StatusCode { fn status_code(&self) -> actix_web::http::StatusCode {
match self { match self {
ApiError::EnvError(..) => { ApiError::Env(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::DatabaseError(..) => { ApiError::Database(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::SqlxDatabaseError(..) => { ApiError::SqlxDatabase(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::AuthenticationError(..) => { ApiError::Authentication(..) => {
actix_web::http::StatusCode::UNAUTHORIZED actix_web::http::StatusCode::UNAUTHORIZED
} }
ApiError::CustomAuthenticationError(..) => { ApiError::CustomAuthentication(..) => {
actix_web::http::StatusCode::UNAUTHORIZED actix_web::http::StatusCode::UNAUTHORIZED
} }
ApiError::XmlError(..) => { ApiError::Xml(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST, ApiError::Json(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::SearchError(..) => { ApiError::Search(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::IndexingError(..) => { ApiError::Indexing(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::FileHostingError(..) => { ApiError::FileHosting(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
} }
ApiError::InvalidInputError(..) => { ApiError::InvalidInput(..) => {
actix_web::http::StatusCode::BAD_REQUEST actix_web::http::StatusCode::BAD_REQUEST
} }
ApiError::ValidationError(..) => { ApiError::Validation(..) => {
actix_web::http::StatusCode::BAD_REQUEST actix_web::http::StatusCode::BAD_REQUEST
} }
} }
@@ -228,18 +228,18 @@ impl actix_web::ResponseError for ApiError {
actix_web::HttpResponse::build(self.status_code()).json( actix_web::HttpResponse::build(self.status_code()).json(
crate::models::error::ApiError { crate::models::error::ApiError {
error: match self { error: match self {
ApiError::EnvError(..) => "environment_error", ApiError::Env(..) => "environment_error",
ApiError::SqlxDatabaseError(..) => "database_error", ApiError::SqlxDatabase(..) => "database_error",
ApiError::DatabaseError(..) => "database_error", ApiError::Database(..) => "database_error",
ApiError::AuthenticationError(..) => "unauthorized", ApiError::Authentication(..) => "unauthorized",
ApiError::CustomAuthenticationError(..) => "unauthorized", ApiError::CustomAuthentication(..) => "unauthorized",
ApiError::XmlError(..) => "xml_error", ApiError::Xml(..) => "xml_error",
ApiError::JsonError(..) => "json_error", ApiError::Json(..) => "json_error",
ApiError::SearchError(..) => "search_error", ApiError::Search(..) => "search_error",
ApiError::IndexingError(..) => "indexing_error", ApiError::Indexing(..) => "indexing_error",
ApiError::FileHostingError(..) => "file_hosting_error", ApiError::FileHosting(..) => "file_hosting_error",
ApiError::InvalidInputError(..) => "invalid_input", ApiError::InvalidInput(..) => "invalid_input",
ApiError::ValidationError(..) => "invalid_input", ApiError::Validation(..) => "invalid_input",
}, },
description: &self.to_string(), description: &self.to_string(),
}, },

View File

@@ -105,7 +105,7 @@ pub async fn notification_delete(
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::CustomAuthenticationError( Err(ApiError::CustomAuthentication(
"You are not authorized to delete this notification!" "You are not authorized to delete this notification!"
.to_string(), .to_string(),
)) ))

View File

@@ -295,7 +295,7 @@ Get logged in user
pub async fn project_create_inner( pub async fn project_create_inner(
req: HttpRequest, req: HttpRequest,
mut payload: Multipart, mut payload: Multipart,
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
file_host: &dyn FileHost, file_host: &dyn FileHost,
uploaded_files: &mut Vec<UploadedFile>, uploaded_files: &mut Vec<UploadedFile>,
) -> Result<HttpResponse, CreateError> { ) -> Result<HttpResponse, CreateError> {
@@ -535,7 +535,7 @@ pub async fn project_create_inner(
all_game_versions.clone(), all_game_versions.clone(),
version_data.primary_file.is_some(), version_data.primary_file.is_some(),
version_data.primary_file.as_deref() == Some(name), version_data.primary_file.as_deref() == Some(name),
&mut transaction, transaction,
) )
.await?; .await?;
} }

View File

@@ -102,9 +102,10 @@ pub async fn dependency_list(
) -> Result<HttpResponse, ApiError> { ) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0; let string = info.into_inner().0;
let result = let result = database::models::Project::get_full_from_slug_or_project_id(
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool) &string, &**pool,
.await?; )
.await?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
@@ -152,7 +153,7 @@ pub async fn dependency_list(
database::Project::get_many_full( database::Project::get_many_full(
dependencies dependencies
.iter() .iter()
.map(|x| if x.0.is_none() { .filter_map(|x| if x.0.is_none() {
if let Some(mod_dependency_id) = x.2 { if let Some(mod_dependency_id) = x.2 {
Some(mod_dependency_id) Some(mod_dependency_id)
} else { } else {
@@ -161,12 +162,11 @@ pub async fn dependency_list(
} else { } else {
x.1 x.1
}) })
.flatten()
.collect(), .collect(),
&**pool, &**pool,
), ),
database::Version::get_many_full( database::Version::get_many_full(
dependencies.iter().map(|x| x.0).flatten().collect(), dependencies.iter().filter_map(|x| x.0).collect(),
&**pool, &**pool,
) )
); );
@@ -282,7 +282,7 @@ pub async fn project_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?; let user = get_user_from_headers(req.headers(), &**pool).await?;
new_project.validate().map_err(|err| { new_project.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let string = info.into_inner().0; let string = info.into_inner().0;
@@ -315,7 +315,7 @@ pub async fn project_edit(
if let Some(title) = &new_project.title { if let Some(title) = &new_project.title {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the title of this project!" "You do not have the permissions to edit the title of this project!"
.to_string(), .to_string(),
)); ));
@@ -336,7 +336,7 @@ pub async fn project_edit(
if let Some(description) = &new_project.description { if let Some(description) = &new_project.description {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the description of this project!" "You do not have the permissions to edit the description of this project!"
.to_string(), .to_string(),
)); ));
@@ -357,7 +357,7 @@ pub async fn project_edit(
if let Some(status) = &new_project.status { if let Some(status) = &new_project.status {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the status of this project!" "You do not have the permissions to edit the status of this project!"
.to_string(), .to_string(),
)); ));
@@ -367,7 +367,7 @@ pub async fn project_edit(
|| status == &ProjectStatus::Approved) || status == &ProjectStatus::Approved)
&& !user.role.is_mod() && !user.role.is_mod()
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to set this status" "You don't have permission to set this status"
.to_string(), .to_string(),
)); ));
@@ -375,7 +375,7 @@ pub async fn project_edit(
if status == &ProjectStatus::Processing { if status == &ProjectStatus::Processing {
if project_item.versions.is_empty() { if project_item.versions.is_empty() {
return Err(ApiError::InvalidInputError(String::from( return Err(ApiError::InvalidInput(String::from(
"Project submitted for review with no initial versions", "Project submitted for review with no initial versions",
))); )));
} }
@@ -420,7 +420,7 @@ pub async fn project_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"No database entry for status provided.".to_string(), "No database entry for status provided.".to_string(),
) )
})?; })?;
@@ -457,7 +457,7 @@ pub async fn project_edit(
if let Some(categories) = &new_project.categories { if let Some(categories) = &new_project.categories {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the categories of this project!" "You do not have the permissions to edit the categories of this project!"
.to_string(), .to_string(),
)); ));
@@ -481,7 +481,7 @@ pub async fn project_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Category {} does not exist.", "Category {} does not exist.",
category.clone() category.clone()
)) ))
@@ -502,7 +502,7 @@ pub async fn project_edit(
if let Some(issues_url) = &new_project.issues_url { if let Some(issues_url) = &new_project.issues_url {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the issues URL of this project!" "You do not have the permissions to edit the issues URL of this project!"
.to_string(), .to_string(),
)); ));
@@ -523,7 +523,7 @@ pub async fn project_edit(
if let Some(source_url) = &new_project.source_url { if let Some(source_url) = &new_project.source_url {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the source URL of this project!" "You do not have the permissions to edit the source URL of this project!"
.to_string(), .to_string(),
)); ));
@@ -544,7 +544,7 @@ pub async fn project_edit(
if let Some(wiki_url) = &new_project.wiki_url { if let Some(wiki_url) = &new_project.wiki_url {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the wiki URL of this project!" "You do not have the permissions to edit the wiki URL of this project!"
.to_string(), .to_string(),
)); ));
@@ -565,7 +565,7 @@ pub async fn project_edit(
if let Some(license_url) = &new_project.license_url { if let Some(license_url) = &new_project.license_url {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the license URL of this project!" "You do not have the permissions to edit the license URL of this project!"
.to_string(), .to_string(),
)); ));
@@ -586,7 +586,7 @@ pub async fn project_edit(
if let Some(discord_url) = &new_project.discord_url { if let Some(discord_url) = &new_project.discord_url {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the discord URL of this project!" "You do not have the permissions to edit the discord URL of this project!"
.to_string(), .to_string(),
)); ));
@@ -607,7 +607,7 @@ pub async fn project_edit(
if let Some(slug) = &new_project.slug { if let Some(slug) = &new_project.slug {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the slug of this project!" "You do not have the permissions to edit the slug of this project!"
.to_string(), .to_string(),
)); ));
@@ -629,7 +629,7 @@ pub async fn project_edit(
.await?; .await?;
if results.exists.unwrap_or(true) { if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"Slug collides with other project's id!" "Slug collides with other project's id!"
.to_string(), .to_string(),
)); ));
@@ -652,7 +652,7 @@ pub async fn project_edit(
if let Some(new_side) = &new_project.client_side { if let Some(new_side) = &new_project.client_side {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the side type of this mod!" "You do not have the permissions to edit the side type of this mod!"
.to_string(), .to_string(),
)); ));
@@ -680,7 +680,7 @@ pub async fn project_edit(
if let Some(new_side) = &new_project.server_side { if let Some(new_side) = &new_project.server_side {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the side type of this project!" "You do not have the permissions to edit the side type of this project!"
.to_string(), .to_string(),
)); ));
@@ -708,7 +708,7 @@ pub async fn project_edit(
if let Some(license) = &new_project.license_id { if let Some(license) = &new_project.license_id {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the license of this project!" "You do not have the permissions to edit the license of this project!"
.to_string(), .to_string(),
)); ));
@@ -736,7 +736,7 @@ pub async fn project_edit(
if let Some(donations) = &new_project.donation_urls { if let Some(donations) = &new_project.donation_urls {
if !perms.contains(Permissions::EDIT_DETAILS) { if !perms.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the donation links of this project!" "You do not have the permissions to edit the donation links of this project!"
.to_string(), .to_string(),
)); ));
@@ -760,7 +760,7 @@ pub async fn project_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Platform {} does not exist.", "Platform {} does not exist.",
donation.id.clone() donation.id.clone()
)) ))
@@ -784,7 +784,7 @@ pub async fn project_edit(
if !user.role.is_mod() if !user.role.is_mod()
&& project_item.status != ProjectStatus::Approved && project_item.status != ProjectStatus::Approved
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the moderation message of this project!" "You do not have the permissions to edit the moderation message of this project!"
.to_string(), .to_string(),
)); ));
@@ -809,7 +809,7 @@ pub async fn project_edit(
if !user.role.is_mod() if !user.role.is_mod()
&& project_item.status != ProjectStatus::Approved && project_item.status != ProjectStatus::Approved
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the moderation message body of this project!" "You do not have the permissions to edit the moderation message body of this project!"
.to_string(), .to_string(),
)); ));
@@ -830,7 +830,7 @@ pub async fn project_edit(
if let Some(body) = &new_project.body { if let Some(body) = &new_project.body {
if !perms.contains(Permissions::EDIT_BODY) { if !perms.contains(Permissions::EDIT_BODY) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the body of this project!" "You do not have the permissions to edit the body of this project!"
.to_string(), .to_string(),
)); ));
@@ -852,7 +852,7 @@ pub async fn project_edit(
transaction.commit().await?; transaction.commit().await?;
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::CustomAuthenticationError( Err(ApiError::CustomAuthentication(
"You do not have permission to edit this project!".to_string(), "You do not have permission to edit this project!".to_string(),
)) ))
} }
@@ -889,7 +889,7 @@ pub async fn project_icon_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -901,15 +901,15 @@ pub async fn project_icon_edit(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this project's icon." "You don't have permission to edit this project's icon."
.to_string(), .to_string(),
)); ));
@@ -958,7 +958,7 @@ pub async fn project_icon_edit(
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::InvalidInputError(format!( Err(ApiError::InvalidInput(format!(
"Invalid format for project icon: {}", "Invalid format for project icon: {}",
ext.ext ext.ext
))) )))
@@ -981,7 +981,7 @@ pub async fn delete_project_icon(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -993,15 +993,15 @@ pub async fn delete_project_icon(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this project's icon." "You don't have permission to edit this project's icon."
.to_string(), .to_string(),
)); ));
@@ -1057,7 +1057,7 @@ pub async fn add_gallery_item(
crate::util::ext::get_image_content_type(&*ext.ext) crate::util::ext::get_image_content_type(&*ext.ext)
{ {
item.validate().map_err(|err| { item.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let cdn_url = dotenv::var("CDN_URL")?; let cdn_url = dotenv::var("CDN_URL")?;
@@ -1071,7 +1071,7 @@ pub async fn add_gallery_item(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1083,15 +1083,15 @@ pub async fn add_gallery_item(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this project's gallery." "You don't have permission to edit this project's gallery."
.to_string(), .to_string(),
)); ));
@@ -1143,7 +1143,7 @@ pub async fn add_gallery_item(
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::InvalidInputError(format!( Err(ApiError::InvalidInput(format!(
"Invalid format for gallery image: {}", "Invalid format for gallery image: {}",
ext.ext ext.ext
))) )))
@@ -1182,7 +1182,7 @@ pub async fn edit_gallery_item(
let string = info.into_inner().0; let string = info.into_inner().0;
item.validate().map_err(|err| { item.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let project_item = database::models::Project::get_from_slug_or_project_id( let project_item = database::models::Project::get_from_slug_or_project_id(
@@ -1191,7 +1191,7 @@ pub async fn edit_gallery_item(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1203,15 +1203,15 @@ pub async fn edit_gallery_item(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this project's gallery." "You don't have permission to edit this project's gallery."
.to_string(), .to_string(),
)); ));
@@ -1229,7 +1229,7 @@ pub async fn edit_gallery_item(
.fetch_optional(&mut *transaction) .fetch_optional(&mut *transaction)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Gallery item at URL {} is not part of the project's gallery.", "Gallery item at URL {} is not part of the project's gallery.",
item.url item.url
)) ))
@@ -1319,7 +1319,7 @@ pub async fn delete_gallery_item(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1331,15 +1331,15 @@ pub async fn delete_gallery_item(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { if !team_member.permissions.contains(Permissions::EDIT_DETAILS) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this project's gallery." "You don't have permission to edit this project's gallery."
.to_string(), .to_string(),
)); ));
@@ -1357,7 +1357,7 @@ pub async fn delete_gallery_item(
.fetch_optional(&mut *transaction) .fetch_optional(&mut *transaction)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Gallery item at URL {} is not part of the project's gallery.", "Gallery item at URL {} is not part of the project's gallery.",
item.url item.url
)) ))
@@ -1403,7 +1403,7 @@ pub async fn project_delete(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1416,9 +1416,9 @@ pub async fn project_delete(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1427,7 +1427,7 @@ pub async fn project_delete(
.permissions .permissions
.contains(Permissions::DELETE_PROJECT) .contains(Permissions::DELETE_PROJECT)
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to delete this project!".to_string(), "You don't have permission to delete this project!".to_string(),
)); ));
} }
@@ -1463,7 +1463,7 @@ pub async fn project_follow(
database::models::Project::get_from_slug_or_project_id(string, &**pool) database::models::Project::get_from_slug_or_project_id(string, &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1512,7 +1512,7 @@ pub async fn project_follow(
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::InvalidInputError( Err(ApiError::InvalidInput(
"You are already following this project!".to_string(), "You are already following this project!".to_string(),
)) ))
} }
@@ -1531,7 +1531,7 @@ pub async fn project_unfollow(
database::models::Project::get_from_slug_or_project_id(string, &**pool) database::models::Project::get_from_slug_or_project_id(string, &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The specified project does not exist!".to_string(), "The specified project does not exist!".to_string(),
) )
})?; })?;
@@ -1580,7 +1580,7 @@ pub async fn project_unfollow(
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::InvalidInputError( Err(ApiError::InvalidInput(
"You are not following this project!".to_string(), "You are not following this project!".to_string(),
)) ))
} }

View File

@@ -31,7 +31,7 @@ pub async fn report_create(
let mut bytes = web::BytesMut::new(); let mut bytes = web::BytesMut::new();
while let Some(item) = body.next().await { while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.map_err(|_| { bytes.extend_from_slice(&item.map_err(|_| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"Error while parsing request payload!".to_string(), "Error while parsing request payload!".to_string(),
) )
})?); })?);
@@ -46,7 +46,7 @@ pub async fn report_create(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Invalid report type: {}", "Invalid report type: {}",
new_report.report_type new_report.report_type
)) ))
@@ -91,7 +91,7 @@ pub async fn report_create(
) )
} }
ItemType::Unknown => { ItemType::Unknown => {
return Err(ApiError::InvalidInputError(format!( return Err(ApiError::InvalidInput(format!(
"Invalid report item type: {}", "Invalid report item type: {}",
new_report.item_type.as_str() new_report.item_type.as_str()
))) )))

View File

@@ -74,7 +74,7 @@ pub async fn category_create(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"Specified project type does not exist!".to_string(), "Specified project type does not exist!".to_string(),
) )
})?; })?;

View File

@@ -38,7 +38,7 @@ pub async fn team_members_get_project(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)?; .map_err(ApiError::Database)?;
if team_member.is_some() { if team_member.is_some() {
let team_members: Vec<_> = members_data let team_members: Vec<_> = members_data
@@ -80,7 +80,7 @@ pub async fn team_members_get(
let team_member = let team_member =
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool) TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
.await .await
.map_err(ApiError::DatabaseError)?; .map_err(ApiError::Database)?;
if team_member.is_some() { if team_member.is_some() {
let team_members: Vec<_> = members_data let team_members: Vec<_> = members_data
@@ -119,7 +119,7 @@ pub async fn join_team(
if let Some(member) = member { if let Some(member) = member {
if member.accepted { if member.accepted {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"You are already a member of this team".to_string(), "You are already a member of this team".to_string(),
)); ));
} }
@@ -138,7 +138,7 @@ pub async fn join_team(
transaction.commit().await?; transaction.commit().await?;
} else { } else {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"There is no pending request from this team".to_string(), "There is no pending request from this team".to_string(),
)); ));
} }
@@ -175,26 +175,26 @@ pub async fn add_team_member(
TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool) TeamMember::get_from_user_id(team_id, current_user.id.into(), &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
) )
})?; })?;
if !member.permissions.contains(Permissions::MANAGE_INVITES) { if !member.permissions.contains(Permissions::MANAGE_INVITES) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to invite users to this team" "You don't have permission to invite users to this team"
.to_string(), .to_string(),
)); ));
} }
if !member.permissions.contains(new_member.permissions) { if !member.permissions.contains(new_member.permissions) {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The new member has permissions that you don't have".to_string(), "The new member has permissions that you don't have".to_string(),
)); ));
} }
if new_member.role == crate::models::teams::OWNER_ROLE { if new_member.role == crate::models::teams::OWNER_ROLE {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The `Owner` role is restricted to one person".to_string(), "The `Owner` role is restricted to one person".to_string(),
)); ));
} }
@@ -207,11 +207,11 @@ pub async fn add_team_member(
if let Some(req) = request { if let Some(req) = request {
if req.accepted { if req.accepted {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The user is already a member of that team".to_string(), "The user is already a member of that team".to_string(),
)); ));
} else { } else {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"There is already a pending member request for this user" "There is already a pending member request for this user"
.to_string(), .to_string(),
)); ));
@@ -221,9 +221,7 @@ pub async fn add_team_member(
crate::database::models::User::get(member.user_id, &**pool) crate::database::models::User::get(member.user_id, &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput("An invalid User ID specified".to_string())
"An invalid User ID specified".to_string(),
)
})?; })?;
let new_id = let new_id =
@@ -312,7 +310,7 @@ pub async fn edit_team_member(
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
) )
@@ -321,7 +319,7 @@ pub async fn edit_team_member(
TeamMember::get_from_user_id_pending(id, user_id, &**pool) TeamMember::get_from_user_id_pending(id, user_id, &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
) )
@@ -330,13 +328,13 @@ pub async fn edit_team_member(
let mut transaction = pool.begin().await?; let mut transaction = pool.begin().await?;
if &*edit_member_db.role == crate::models::teams::OWNER_ROLE { if &*edit_member_db.role == crate::models::teams::OWNER_ROLE {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The owner of a team cannot be edited".to_string(), "The owner of a team cannot be edited".to_string(),
)); ));
} }
if !member.permissions.contains(Permissions::EDIT_MEMBER) { if !member.permissions.contains(Permissions::EDIT_MEMBER) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
)); ));
@@ -344,7 +342,7 @@ pub async fn edit_team_member(
if let Some(new_permissions) = edit_member.permissions { if let Some(new_permissions) = edit_member.permissions {
if !member.permissions.contains(new_permissions) { if !member.permissions.contains(new_permissions) {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The new permissions have permissions that you don't have" "The new permissions have permissions that you don't have"
.to_string(), .to_string(),
)); ));
@@ -352,7 +350,7 @@ pub async fn edit_team_member(
} }
if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) { if edit_member.role.as_deref() == Some(crate::models::teams::OWNER_ROLE) {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"The `Owner` role is restricted to one person".to_string(), "The `Owner` role is restricted to one person".to_string(),
)); ));
} }
@@ -394,7 +392,7 @@ pub async fn transfer_ownership(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
) )
@@ -406,20 +404,20 @@ pub async fn transfer_ownership(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"The new owner specified does not exist".to_string(), "The new owner specified does not exist".to_string(),
) )
})?; })?;
if member.role != crate::models::teams::OWNER_ROLE { if member.role != crate::models::teams::OWNER_ROLE {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit the ownership of this team" "You don't have permission to edit the ownership of this team"
.to_string(), .to_string(),
)); ));
} }
if !new_member.accepted { if !new_member.accepted {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"You can only transfer ownership to members who are currently in your team".to_string(), "You can only transfer ownership to members who are currently in your team".to_string(),
)); ));
} }
@@ -466,7 +464,7 @@ pub async fn remove_team_member(
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool) TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to edit members of this team" "You don't have permission to edit members of this team"
.to_string(), .to_string(),
) )
@@ -478,7 +476,7 @@ pub async fn remove_team_member(
if let Some(delete_member) = delete_member { if let Some(delete_member) = delete_member {
if delete_member.role == crate::models::teams::OWNER_ROLE { if delete_member.role == crate::models::teams::OWNER_ROLE {
// The owner cannot be removed from a team // The owner cannot be removed from a team
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"The owner can't be removed from a team".to_string(), "The owner can't be removed from a team".to_string(),
)); ));
} }
@@ -492,7 +490,7 @@ pub async fn remove_team_member(
{ {
TeamMember::delete(id, user_id, &**pool).await?; TeamMember::delete(id, user_id, &**pool).await?;
} else { } else {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to remove a member from this team".to_string(), "You do not have permission to remove a member from this team".to_string(),
)); ));
} }
@@ -505,7 +503,7 @@ pub async fn remove_team_member(
// permission can remove it. // permission can remove it.
TeamMember::delete(id, user_id, &**pool).await?; TeamMember::delete(id, user_id, &**pool).await?;
} else { } else {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to cancel a team invite" "You do not have permission to cancel a team invite"
.to_string(), .to_string(),
)); ));

View File

@@ -24,12 +24,12 @@ pub async fn forge_updates(
&id, &**pool, &id, &**pool,
) )
.await? .await?
.ok_or_else(|| ApiError::InvalidInputError(ERROR.to_string()))?; .ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
if !is_authorized(&project, &user_option, &pool).await? { if !is_authorized(&project, &user_option, &pool).await? {
return Err(ApiError::InvalidInputError(ERROR.to_string())); return Err(ApiError::InvalidInput(ERROR.to_string()));
} }
let version_ids = database::models::Version::get_project_versions( let version_ids = database::models::Version::get_project_versions(

View File

@@ -166,7 +166,7 @@ pub async fn user_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?; let user = get_user_from_headers(req.headers(), &**pool).await?;
new_user.validate().map_err(|err| { new_user.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let id_option = crate::database::models::User::get_id_from_username_or_id( let id_option = crate::database::models::User::get_id_from_username_or_id(
@@ -201,7 +201,7 @@ pub async fn user_edit(
.execute(&mut *transaction) .execute(&mut *transaction)
.await?; .await?;
} else { } else {
return Err(ApiError::InvalidInputError(format!( return Err(ApiError::InvalidInput(format!(
"Username {} is taken!", "Username {} is taken!",
username username
))); )));
@@ -252,7 +252,7 @@ pub async fn user_edit(
if let Some(role) = &new_user.role { if let Some(role) = &new_user.role {
if !user.role.is_mod() { if !user.role.is_mod() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit the role of this user!" "You do not have the permissions to edit the role of this user!"
.to_string(), .to_string(),
)); ));
@@ -276,7 +276,7 @@ pub async fn user_edit(
transaction.commit().await?; transaction.commit().await?;
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::CustomAuthenticationError( Err(ApiError::CustomAuthentication(
"You do not have permission to edit this user!".to_string(), "You do not have permission to edit this user!".to_string(),
)) ))
} }
@@ -313,7 +313,7 @@ pub async fn user_icon_edit(
if let Some(id) = id_option { if let Some(id) = id_option {
if user.id != id.into() && !user.role.is_mod() { if user.id != id.into() && !user.role.is_mod() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to edit this user's icon." "You don't have permission to edit this user's icon."
.to_string(), .to_string(),
)); ));
@@ -374,7 +374,7 @@ pub async fn user_icon_edit(
Ok(HttpResponse::NotFound().body("")) Ok(HttpResponse::NotFound().body(""))
} }
} else { } else {
Err(ApiError::InvalidInputError(format!( Err(ApiError::InvalidInput(format!(
"Invalid format for user icon: {}", "Invalid format for user icon: {}",
ext.ext ext.ext
))) )))
@@ -407,24 +407,18 @@ pub async fn user_delete(
if let Some(id) = id_option { if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() { if !user.role.is_mod() && user.id != id.into() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to delete this user!".to_string(), "You do not have permission to delete this user!".to_string(),
)); ));
} }
let mut transaction = pool.begin().await?; let mut transaction = pool.begin().await?;
let result; let result = if &*removal_type.removal_type == "full" {
if &*removal_type.removal_type == "full" { crate::database::models::User::remove_full(id, &mut transaction)
result = crate::database::models::User::remove_full( .await?
id,
&mut transaction,
)
.await?;
} else { } else {
result = crate::database::models::User::remove(id, &mut transaction).await?
crate::database::models::User::remove(id, &mut transaction)
.await?;
}; };
transaction.commit().await?; transaction.commit().await?;
@@ -454,7 +448,7 @@ pub async fn user_follows(
if let Some(id) = id_option { if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() { if !user.role.is_mod() && user.id != id.into() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to see the projects this user follows!".to_string(), "You do not have permission to see the projects this user follows!".to_string(),
)); ));
} }
@@ -504,7 +498,7 @@ pub async fn user_notifications(
if let Some(id) = id_option { if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() { if !user.role.is_mod() && user.id != id.into() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to see the notifications of this user!".to_string(), "You do not have permission to see the notifications of this user!".to_string(),
)); ));
} }

View File

@@ -64,7 +64,7 @@ pub async fn report_create(
let mut bytes = web::BytesMut::new(); let mut bytes = web::BytesMut::new();
while let Some(item) = body.next().await { while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.map_err(|_| { bytes.extend_from_slice(&item.map_err(|_| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"Error while parsing request payload!".to_string(), "Error while parsing request payload!".to_string(),
) )
})?); })?);
@@ -79,7 +79,7 @@ pub async fn report_create(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Invalid report type: {}", "Invalid report type: {}",
new_report.report_type new_report.report_type
)) ))
@@ -124,7 +124,7 @@ pub async fn report_create(
) )
} }
ItemType::Unknown => { ItemType::Unknown => {
return Err(ApiError::InvalidInputError(format!( return Err(ApiError::InvalidInput(format!(
"Invalid report item type: {}", "Invalid report item type: {}",
new_report.item_type.as_str() new_report.item_type.as_str()
))) )))

View File

@@ -38,7 +38,7 @@ pub async fn category_create(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"Specified project type does not exist!".to_string(), "Specified project type does not exist!".to_string(),
) )
})?; })?;

View File

@@ -42,7 +42,7 @@ pub async fn team_members_get(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)?; .map_err(ApiError::Database)?;
if team_member.is_some() { if team_member.is_some() {
let team_members: Vec<TeamMember> = members_data let team_members: Vec<TeamMember> = members_data

View File

@@ -66,7 +66,7 @@ pub async fn user_follows(
if let Some(id) = id_option { if let Some(id) = id_option {
if !user.role.is_mod() && user.id != id.into() { if !user.role.is_mod() && user.id != id.into() {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to see the projects this user follows!".to_string(), "You do not have permission to see the projects this user follows!".to_string(),
)); ));
} }

View File

@@ -269,7 +269,7 @@ pub async fn download_version(
) )
.fetch_optional(&**pool) .fetch_optional(&**pool)
.await .await
.map_err(|e| ApiError::DatabaseError(e.into()))?; .map_err(|e| ApiError::Database(e.into()))?;
if let Some(id) = result { if let Some(id) = result {
Ok(HttpResponse::TemporaryRedirect() Ok(HttpResponse::TemporaryRedirect()
@@ -316,9 +316,9 @@ pub async fn delete_file(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to delete this file!" "You don't have permission to delete this file!"
.to_string(), .to_string(),
) )
@@ -328,7 +328,7 @@ pub async fn delete_file(
.permissions .permissions
.contains(Permissions::DELETE_VERSION) .contains(Permissions::DELETE_VERSION)
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to delete this file!" "You don't have permission to delete this file!"
.to_string(), .to_string(),
)); ));

View File

@@ -103,7 +103,7 @@ pub async fn version_create(
async fn version_create_inner( async fn version_create_inner(
req: HttpRequest, req: HttpRequest,
mut payload: Multipart, mut payload: Multipart,
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
file_host: &dyn FileHost, file_host: &dyn FileHost,
uploaded_files: &mut Vec<UploadedFile>, uploaded_files: &mut Vec<UploadedFile>,
) -> Result<HttpResponse, CreateError> { ) -> Result<HttpResponse, CreateError> {
@@ -322,7 +322,7 @@ async fn version_create_inner(
all_game_versions.clone(), all_game_versions.clone(),
version_data.primary_file.is_some(), version_data.primary_file.is_some(),
version_data.primary_file.as_deref() == Some(name), version_data.primary_file.as_deref() == Some(name),
&mut transaction, transaction,
) )
.await?; .await?;
} }
@@ -486,7 +486,7 @@ async fn upload_file_to_version_inner(
req: HttpRequest, req: HttpRequest,
mut payload: Multipart, mut payload: Multipart,
client: Data<PgPool>, client: Data<PgPool>,
mut transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
file_host: &dyn FileHost, file_host: &dyn FileHost,
uploaded_files: &mut Vec<UploadedFile>, uploaded_files: &mut Vec<UploadedFile>,
version_id: models::VersionId, version_id: models::VersionId,
@@ -597,7 +597,7 @@ async fn upload_file_to_version_inner(
all_game_versions.clone(), all_game_versions.clone(),
true, true,
false, false,
&mut transaction, transaction,
) )
.await?; .await?;
} }

View File

@@ -135,9 +135,9 @@ pub async fn delete_file(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::CustomAuthenticationError( ApiError::CustomAuthentication(
"You don't have permission to delete this file!" "You don't have permission to delete this file!"
.to_string(), .to_string(),
) )
@@ -147,7 +147,7 @@ pub async fn delete_file(
.permissions .permissions
.contains(Permissions::DELETE_VERSION) .contains(Permissions::DELETE_VERSION)
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You don't have permission to delete this file!" "You don't have permission to delete this file!"
.to_string(), .to_string(),
)); ));
@@ -169,7 +169,7 @@ pub async fn delete_file(
.await?; .await?;
if files.len() < 2 { if files.len() < 2 {
return Err(ApiError::InvalidInputError( return Err(ApiError::InvalidInput(
"Versions must have at least one file uploaded to them" "Versions must have at least one file uploaded to them"
.to_string(), .to_string(),
)); ));

View File

@@ -28,9 +28,10 @@ pub async fn version_list(
) -> Result<HttpResponse, ApiError> { ) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0; let string = info.into_inner().0;
let result = let result = database::models::Project::get_full_from_slug_or_project_id(
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool) &string, &**pool,
.await?; )
.await?;
let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); let user_option = get_user_from_headers(req.headers(), &**pool).await.ok();
@@ -187,6 +188,7 @@ pub struct EditVersion {
pub loaders: Option<Vec<models::projects::Loader>>, pub loaders: Option<Vec<models::projects::Loader>>,
pub featured: Option<bool>, pub featured: Option<bool>,
pub primary_file: Option<(String, String)>, pub primary_file: Option<(String, String)>,
pub downloads: Option<u32>,
} }
#[patch("{id}")] #[patch("{id}")]
@@ -199,7 +201,7 @@ pub async fn version_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?; let user = get_user_from_headers(req.headers(), &**pool).await?;
new_version.validate().map_err(|err| { new_version.validate().map_err(|err| {
ApiError::ValidationError(validation_errors_to_string(err, None)) ApiError::Validation(validation_errors_to_string(err, None))
})?; })?;
let version_id = info.into_inner().0; let version_id = info.into_inner().0;
@@ -227,7 +229,7 @@ pub async fn version_edit(
if let Some(perms) = permissions { if let Some(perms) = permissions {
if !perms.contains(Permissions::UPLOAD_VERSION) { if !perms.contains(Permissions::UPLOAD_VERSION) {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit this version!" "You do not have the permissions to edit this version!"
.to_string(), .to_string(),
)); ));
@@ -321,7 +323,7 @@ pub async fn version_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"No database entry for game version provided." "No database entry for game version provided."
.to_string(), .to_string(),
) )
@@ -358,7 +360,7 @@ pub async fn version_edit(
) )
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"No database entry for loader provided." "No database entry for loader provided."
.to_string(), .to_string(),
) )
@@ -404,7 +406,7 @@ pub async fn version_edit(
.fetch_optional(&**pool) .fetch_optional(&**pool)
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError(format!( ApiError::InvalidInput(format!(
"Specified file with hash {} does not exist.", "Specified file with hash {} does not exist.",
primary_file.1.clone() primary_file.1.clone()
)) ))
@@ -447,10 +449,38 @@ pub async fn version_edit(
.await?; .await?;
} }
if let Some(downloads) = &new_version.downloads {
sqlx::query!(
"
UPDATE versions
SET downloads = $1
WHERE (id = $2)
",
*downloads as i32,
id as database::models::ids::VersionId,
)
.execute(&mut *transaction)
.await?;
let diff = *downloads - (version_item.downloads as u32);
sqlx::query!(
"
UPDATE mods
SET downloads = downloads + $1
WHERE (id = $2)
",
diff as i32,
version_item.project_id as database::models::ids::ProjectId,
)
.execute(&mut *transaction)
.await?;
}
transaction.commit().await?; transaction.commit().await?;
Ok(HttpResponse::NoContent().body("")) Ok(HttpResponse::NoContent().body(""))
} else { } else {
Err(ApiError::CustomAuthenticationError( Err(ApiError::CustomAuthentication(
"You do not have permission to edit this version!".to_string(), "You do not have permission to edit this version!".to_string(),
)) ))
} }
@@ -503,7 +533,7 @@ pub async fn version_count_patch(
.execute(pool.as_ref()), .execute(pool.as_ref()),
) )
.await .await
.map_err(ApiError::SqlxDatabaseError)?; .map_err(ApiError::SqlxDatabase)?;
Ok(HttpResponse::Ok().body("")) Ok(HttpResponse::Ok().body(""))
} }
@@ -524,9 +554,9 @@ pub async fn version_delete(
&**pool, &**pool,
) )
.await .await
.map_err(ApiError::DatabaseError)? .map_err(ApiError::Database)?
.ok_or_else(|| { .ok_or_else(|| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"You do not have permission to delete versions in this team".to_string(), "You do not have permission to delete versions in this team".to_string(),
) )
})?; })?;
@@ -535,7 +565,7 @@ pub async fn version_delete(
.permissions .permissions
.contains(Permissions::DELETE_VERSION) .contains(Permissions::DELETE_VERSION)
{ {
return Err(ApiError::CustomAuthenticationError( return Err(ApiError::CustomAuthentication(
"You do not have permission to delete versions in this team" "You do not have permission to delete versions in this team"
.to_string(), .to_string(),
)); ));

View File

@@ -8,23 +8,24 @@ use meilisearch_sdk::client::Client;
use meilisearch_sdk::indexes::Index; use meilisearch_sdk::indexes::Index;
use meilisearch_sdk::settings::Settings; use meilisearch_sdk::settings::Settings;
use sqlx::postgres::PgPool; use sqlx::postgres::PgPool;
use std::collections::{HashMap, VecDeque};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum IndexingError { pub enum IndexingError {
#[error("Error while connecting to the MeiliSearch database")] #[error("Error while connecting to the MeiliSearch database")]
IndexDBError(#[from] meilisearch_sdk::errors::Error), Indexing(#[from] meilisearch_sdk::errors::Error),
#[error("Error while serializing or deserializing JSON: {0}")] #[error("Error while serializing or deserializing JSON: {0}")]
SerdeError(#[from] serde_json::Error), Serde(#[from] serde_json::Error),
#[error("Error while parsing a timestamp: {0}")] #[error("Error while parsing a timestamp: {0}")]
ParseDateError(#[from] chrono::format::ParseError), ParseDate(#[from] chrono::format::ParseError),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
SqlxError(#[from] sqlx::error::Error), Sqlx(#[from] sqlx::error::Error),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError), Database(#[from] crate::database::models::DatabaseError),
#[error("Environment Error")] #[error("Environment Error")]
EnvError(#[from] dotenv::Error), Env(#[from] dotenv::Error),
#[error("Error while awaiting index creation task")]
Task,
} }
// The chunk size for adding projects to the indexing database. If the request size // The chunk size for adding projects to the indexing database. If the request size
@@ -57,8 +58,8 @@ pub async fn index_projects(
if settings.index_local { if settings.index_local {
docs_to_add.append(&mut index_local(pool.clone()).await?); docs_to_add.append(&mut index_local(pool.clone()).await?);
} }
// Write Indices
// Write Indices
add_projects(docs_to_add, config).await?; add_projects(docs_to_add, config).await?;
Ok(()) Ok(())
@@ -67,122 +68,91 @@ pub async fn index_projects(
pub async fn reset_indices(config: &SearchConfig) -> Result<(), IndexingError> { pub async fn reset_indices(config: &SearchConfig) -> Result<(), IndexingError> {
let client = config.make_client(); let client = config.make_client();
client.delete_index("relevance_projects").await?; client.delete_index("projects").await?;
client.delete_index("downloads_projects").await?; client.delete_index("projects_filtered").await?;
client.delete_index("follows_projects").await?;
client.delete_index("updated_projects").await?;
client.delete_index("newest_projects").await?;
Ok(())
}
async fn update_index_helper<'a>(
client: &'a Client<'a>,
name: &'static str,
rule: &'static str,
) -> Result<Index<'a>, IndexingError> {
update_index(client, name, {
let mut rules = default_rules();
rules.push_back(rule);
rules.into()
})
.await
}
pub async fn reconfigure_indices(
config: &SearchConfig,
) -> Result<(), IndexingError> {
let client = config.make_client();
// Relevance Index
update_index_helper(&client, "relevance_projects", "desc(downloads)")
.await?;
update_index_helper(&client, "downloads_projects", "desc(downloads)")
.await?;
update_index_helper(&client, "follows_projects", "desc(follows)").await?;
update_index_helper(
&client,
"updated_projects",
"desc(modified_timestamp)",
)
.await?;
update_index_helper(&client, "newest_projects", "desc(created_timestamp)")
.await?;
Ok(()) Ok(())
} }
async fn update_index<'a>( async fn create_index(
client: &'a Client<'a>, client: &Client,
name: &'a str,
rules: Vec<&'static str>,
) -> Result<Index<'a>, IndexingError> {
let index = match client.get_index(name).await {
Ok(index) => index,
Err(meilisearch_sdk::errors::Error::MeiliSearchError {
error_code: meilisearch_sdk::errors::ErrorCode::IndexNotFound,
..
}) => client.create_index(name, Some("project_id")).await?,
Err(e) => {
return Err(IndexingError::IndexDBError(e));
}
};
index
.set_settings(&default_settings().with_ranking_rules(rules))
.await?;
Ok(index)
}
async fn create_index<'a>(
client: &'a Client<'a>,
name: &'static str, name: &'static str,
rules: impl FnOnce() -> Vec<&'static str>, custom_rules: Option<&'static [&'static str]>,
) -> Result<Index<'a>, IndexingError> { ) -> Result<Index, IndexingError> {
client
.delete_index(name)
.await?
.wait_for_completion(client, None, None)
.await?;
match client.get_index(name).await { match client.get_index(name).await {
// TODO: update index settings on startup (or delete old indices on startup) // TODO: update index settings on startup (or delete old indices on startup)
Ok(index) => Ok(index), Ok(index) => {
Err(meilisearch_sdk::errors::Error::MeiliSearchError { index
error_code: meilisearch_sdk::errors::ErrorCode::IndexNotFound, .set_settings(&default_settings())
.. .await?
}) => { .wait_for_completion(client, None, None)
.await?;
Ok(index)
}
Err(meilisearch_sdk::errors::Error::Meilisearch(
meilisearch_sdk::errors::MeilisearchError {
error_code: meilisearch_sdk::errors::ErrorCode::IndexNotFound,
..
},
)) => {
// Only create index and set settings if the index doesn't already exist // Only create index and set settings if the index doesn't already exist
let index = client.create_index(name, Some("project_id")).await?; let task = client.create_index(name, Some("project_id")).await?;
let task = task.wait_for_completion(client, None, None).await?;
let index = task
.try_make_index(client)
.map_err(|_| IndexingError::Task)?;
let mut settings = default_settings();
if let Some(custom_rules) = custom_rules {
settings = settings.with_ranking_rules(custom_rules);
}
index index
.set_settings(&default_settings().with_ranking_rules(rules())) .set_settings(&settings)
.await?
.wait_for_completion(client, None, None)
.await?; .await?;
Ok(index) Ok(index)
} }
Err(e) => { Err(e) => {
log::warn!("Unhandled error while creating index: {}", e); log::warn!("Unhandled error while creating index: {}", e);
Err(IndexingError::IndexDBError(e)) Err(IndexingError::Indexing(e))
} }
} }
} }
async fn add_to_index( async fn add_to_index(
index: Index<'_>, client: &Client,
index: Index,
mods: &[UploadSearchProject], mods: &[UploadSearchProject],
) -> Result<(), IndexingError> { ) -> Result<(), IndexingError> {
for chunk in mods.chunks(MEILISEARCH_CHUNK_SIZE) { for chunk in mods.chunks(MEILISEARCH_CHUNK_SIZE) {
index.add_documents(chunk, Some("project_id")).await?; index
.add_documents(chunk, Some("project_id"))
.await?
.wait_for_completion(client, None, None)
.await?;
} }
Ok(()) Ok(())
} }
async fn create_and_add_to_index<'a>( async fn create_and_add_to_index(
client: &'a Client<'a>, client: &Client,
projects: &'a [UploadSearchProject], projects: &[UploadSearchProject],
name: &'static str, name: &'static str,
rule: &'static str, custom_rules: Option<&'static [&'static str]>,
) -> Result<(), IndexingError> { ) -> Result<(), IndexingError> {
let index = create_index(client, name, || { let index = create_index(client, name, custom_rules).await?;
let mut relevance_rules = default_rules(); add_to_index(client, index, projects).await?;
relevance_rules.push_back(rule);
relevance_rules.into()
})
.await?;
add_to_index(index, projects).await?;
Ok(()) Ok(())
} }
@@ -192,65 +162,32 @@ pub async fn add_projects(
) -> Result<(), IndexingError> { ) -> Result<(), IndexingError> {
let client = config.make_client(); let client = config.make_client();
create_and_add_to_index(&client, &projects, "projects", None).await?;
create_and_add_to_index( create_and_add_to_index(
&client, &client,
&projects, &projects,
"relevance_projects", "projects_filtered",
"desc(downloads)", Some(&[
) "sort",
.await?; "words",
create_and_add_to_index( "typo",
&client, "proximity",
&projects, "attribute",
"downloads_projects", "exactness",
"desc(downloads)", ]),
)
.await?;
create_and_add_to_index(
&client,
&projects,
"follows_projects",
"desc(follows)",
)
.await?;
create_and_add_to_index(
&client,
&projects,
"updated_projects",
"desc(modified_timestamp)",
)
.await?;
create_and_add_to_index(
&client,
&projects,
"newest_projects",
"desc(created_timestamp)",
) )
.await?; .await?;
Ok(()) Ok(())
} }
//region Utils
fn default_rules() -> VecDeque<&'static str> {
vec![
"typo",
"words",
"proximity",
"attribute",
"wordsPosition",
"exactness",
]
.into()
}
fn default_settings() -> Settings { fn default_settings() -> Settings {
Settings::new() Settings::new()
.with_displayed_attributes(DEFAULT_DISPLAYED_ATTRIBUTES) .with_displayed_attributes(DEFAULT_DISPLAYED_ATTRIBUTES)
.with_searchable_attributes(DEFAULT_SEARCHABLE_ATTRIBUTES) .with_searchable_attributes(DEFAULT_SEARCHABLE_ATTRIBUTES)
.with_stop_words(Vec::<String>::new()) .with_sortable_attributes(DEFAULT_SORTABLE_ATTRIBUTES)
.with_synonyms(HashMap::<String, Vec<String>>::new()) .with_filterable_attributes(DEFAULT_ATTRIBUTES_FOR_FACETING)
.with_attributes_for_faceting(DEFAULT_ATTRIBUTES_FOR_FACETING)
} }
const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
@@ -275,72 +212,22 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
]; ];
const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] = const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] =
&["title", "description", "categories", "versions", "author"]; &["title", "description", "author"];
const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
"categories", "categories",
"host",
"versions", "versions",
"license", "license",
"client_side", "client_side",
"server_side", "server_side",
"project_type", "project_type",
"downloads",
"follows",
"author",
"title",
"date_created",
"date_modified",
]; ];
//endregion
// This shouldn't be relied on for proper sorting, but it makes an const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
// attempt at getting proper sorting for Mojang's versions. &["downloads", "follows", "date_created", "date_modified"];
// This isn't currently used, but I wrote it and it works, so I'm
// keeping this mess in case someone needs it in the future.
#[allow(dead_code)]
pub fn sort_projects(a: &str, b: &str) -> std::cmp::Ordering {
use std::cmp::Ordering;
let cmp = a.contains('.').cmp(&b.contains('.'));
if cmp != Ordering::Equal {
return cmp;
}
let mut a = a.split(&['.', '-'] as &[char]);
let mut b = b.split(&['.', '-'] as &[char]);
let a = (a.next(), a.next(), a.next(), a.next());
let b = (b.next(), b.next(), b.next(), b.next());
if a.0 == b.0 {
let cmp =
a.1.map(|s| s.chars().all(|c| c.is_ascii_digit()))
.cmp(&b.1.map(|s| s.chars().all(|c| c.is_ascii_digit())));
if cmp != Ordering::Equal {
return cmp;
}
if a.1 == b.1 {
let cmp =
a.2.map(|s| s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or(true)
.cmp(
&b.2.map(|s| s.chars().all(|c| c.is_ascii_digit()))
.unwrap_or(true),
);
if cmp != Ordering::Equal {
return cmp;
}
if a.2 == b.2 {
match (a.3.is_some(), b.3.is_some()) {
(false, false) => Ordering::Equal,
(false, true) => Ordering::Greater,
(true, false) => Ordering::Less,
(true, true) => a.3.cmp(&b.3),
}
} else {
a.2.cmp(&b.2)
}
} else {
a.1.cmp(&b.1)
}
} else {
match (a.0 == Some("1"), b.0 == Some("1")) {
(false, false) => a.0.cmp(&b.0),
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(true, true) => unreachable!(),
}
}
}

View File

@@ -15,13 +15,13 @@ pub mod indexing;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SearchError { pub enum SearchError {
#[error("MeiliSearch Error: {0}")] #[error("MeiliSearch Error: {0}")]
MeiliSearchError(#[from] meilisearch_sdk::errors::Error), MeiliSearch(#[from] meilisearch_sdk::errors::Error),
#[error("Error while serializing or deserializing JSON: {0}")] #[error("Error while serializing or deserializing JSON: {0}")]
SerdeError(#[from] serde_json::Error), Serde(#[from] serde_json::Error),
#[error("Error while parsing an integer: {0}")] #[error("Error while parsing an integer: {0}")]
IntParsingError(#[from] std::num::ParseIntError), IntParsing(#[from] std::num::ParseIntError),
#[error("Environment Error")] #[error("Environment Error")]
EnvError(#[from] dotenv::Error), Env(#[from] dotenv::Error),
#[error("Invalid index to sort by: {0}")] #[error("Invalid index to sort by: {0}")]
InvalidIndex(String), InvalidIndex(String),
} }
@@ -29,10 +29,10 @@ pub enum SearchError {
impl actix_web::ResponseError for SearchError { impl actix_web::ResponseError for SearchError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
SearchError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR, SearchError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
SearchError::MeiliSearchError(..) => StatusCode::BAD_REQUEST, SearchError::MeiliSearch(..) => StatusCode::BAD_REQUEST,
SearchError::SerdeError(..) => StatusCode::BAD_REQUEST, SearchError::Serde(..) => StatusCode::BAD_REQUEST,
SearchError::IntParsingError(..) => StatusCode::BAD_REQUEST, SearchError::IntParsing(..) => StatusCode::BAD_REQUEST,
SearchError::InvalidIndex(..) => StatusCode::BAD_REQUEST, SearchError::InvalidIndex(..) => StatusCode::BAD_REQUEST,
} }
} }
@@ -40,10 +40,10 @@ impl actix_web::ResponseError for SearchError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).json(ApiError { HttpResponse::build(self.status_code()).json(ApiError {
error: match self { error: match self {
SearchError::EnvError(..) => "environment_error", SearchError::Env(..) => "environment_error",
SearchError::MeiliSearchError(..) => "meilisearch_error", SearchError::MeiliSearch(..) => "meilisearch_error",
SearchError::SerdeError(..) => "invalid_input", SearchError::Serde(..) => "invalid_input",
SearchError::IntParsingError(..) => "invalid_input", SearchError::IntParsing(..) => "invalid_input",
SearchError::InvalidIndex(..) => "invalid_input", SearchError::InvalidIndex(..) => "invalid_input",
}, },
description: &self.to_string(), description: &self.to_string(),
@@ -149,62 +149,85 @@ pub async fn search_for_project(
) -> Result<SearchResults, SearchError> { ) -> Result<SearchResults, SearchError> {
let client = Client::new(&*config.address, &*config.key); let client = Client::new(&*config.address, &*config.key);
let filters: Cow<_> =
match (info.filters.as_deref(), info.version.as_deref()) {
(Some(f), Some(v)) => format!("({}) AND ({})", f, v).into(),
(Some(f), None) => f.into(),
(None, Some(v)) => v.into(),
(None, None) => "".into(),
};
let offset = info.offset.as_deref().unwrap_or("0").parse()?; let offset = info.offset.as_deref().unwrap_or("0").parse()?;
let index = info.index.as_deref().unwrap_or("relevance"); let index = info.index.as_deref().unwrap_or("relevance");
let limit = info.limit.as_deref().unwrap_or("10").parse()?; let limit = info.limit.as_deref().unwrap_or("10").parse()?;
let index = match index { let sort = match index {
"relevance" => "relevance_projects", "relevance" => ("projects", ["downloads:desc"]),
"downloads" => "downloads_projects", "downloads" => ("projects_filtered", ["downloads:desc"]),
"follows" => "follows_projects", "follows" => ("projects_filtered", ["follows:desc"]),
"updated" => "updated_projects", "updated" => ("projects_filtered", ["date_created:desc"]),
"newest" => "newest_projects", "newest" => ("projects_filtered", ["date_modified:desc"]),
i => return Err(SearchError::InvalidIndex(i.to_string())), i => return Err(SearchError::InvalidIndex(i.to_string())),
}; };
let meilisearch_index = client.get_index(index).await?; let meilisearch_index = client.get_index(sort.0).await?;
let mut query = meilisearch_index.search();
query.with_limit(min(100, limit)).with_offset(offset); let mut filter_string = String::new();
if let Some(search) = info.query.as_deref() { let results = {
if !search.is_empty() { let mut query = meilisearch_index.search();
query.with_query(search);
query
.with_limit(min(100, limit))
.with_offset(offset)
.with_query(info.query.as_deref().unwrap_or_default())
.with_sort(&sort.1);
if let Some(new_filters) = info.new_filters.as_deref() {
query.with_filter(new_filters);
} else {
let facets = if let Some(facets) = &info.facets {
Some(serde_json::from_str::<Vec<Vec<&str>>>(facets)?)
} else {
None
};
let filters: Cow<_> =
match (info.filters.as_deref(), info.version.as_deref()) {
(Some(f), Some(v)) => format!("({}) AND ({})", f, v).into(),
(Some(f), None) => f.into(),
(None, Some(v)) => v.into(),
(None, None) => "".into(),
};
if let Some(facets) = facets {
filter_string.push('(');
for (index, facet_list) in facets.iter().enumerate() {
filter_string.push('(');
for (facet_index, facet) in facet_list.iter().enumerate() {
filter_string.push_str(&facet.replace(':', " = "));
if facet_index != (facet_list.len() - 1) {
filter_string.push_str(" OR ")
}
}
filter_string.push(')');
if index != (facets.len() - 1) {
filter_string.push_str(" AND ")
}
}
filter_string.push(')');
if !filters.is_empty() {
filter_string.push_str(&format!(" AND ({})", filter_string))
}
} else {
filter_string.push_str(&*filters);
}
println!("{}", filter_string);
if !filter_string.is_empty() {
query.with_filter(&filter_string);
}
} }
}
if !filters.is_empty() { query.execute::<ResultSearchProject>().await?
query.with_filters(&filters); };
}
// So the meilisearch sdk's lifetimes are... broken, to say the least
// They are overspecified and almost always wrong, and would generally
// just be better if they didn't specify them at all.
// They also decided to have this take a &[&[&str]], which is impossible
// to construct efficiently. Instead it should take impl Iterator<Item=&[&str]>,
// &[impl AsRef<[&str]>], or one of many other proper solutions to that issue.
let why_meilisearch;
let why_must_you_do_this;
if let Some(facets) = &info.facets {
why_meilisearch = serde_json::from_str::<Vec<Vec<&str>>>(facets)?;
why_must_you_do_this = why_meilisearch
.iter()
.map(|v| v as &[_])
.collect::<Vec<&[_]>>();
query.with_facet_filters(&why_must_you_do_this);
}
let results = query.execute::<ResultSearchProject>().await?;
Ok(SearchResults { Ok(SearchResults {
hits: results.hits.into_iter().map(|r| r.result).collect(), hits: results.hits.into_iter().map(|r| r.result).collect(),

View File

@@ -12,15 +12,15 @@ use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AuthenticationError { pub enum AuthenticationError {
#[error("An unknown database error occurred")] #[error("An unknown database error occurred")]
SqlxDatabaseError(#[from] sqlx::Error), Sqlx(#[from] sqlx::Error),
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError), Database(#[from] crate::database::models::DatabaseError),
#[error("Error while parsing JSON: {0}")] #[error("Error while parsing JSON: {0}")]
SerdeError(#[from] serde_json::Error), SerDe(#[from] serde_json::Error),
#[error("Error while communicating to GitHub OAuth2: {0}")] #[error("Error while communicating to GitHub OAuth2: {0}")]
GithubError(#[from] reqwest::Error), Github(#[from] reqwest::Error),
#[error("Invalid Authentication Credentials")] #[error("Invalid Authentication Credentials")]
InvalidCredentialsError, InvalidCredentials,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -73,7 +73,7 @@ where
created: result.created, created: result.created,
role: Role::from_string(&result.role), role: Role::from_string(&result.role),
}), }),
None => Err(AuthenticationError::InvalidCredentialsError), None => Err(AuthenticationError::InvalidCredentials),
} }
} }
pub async fn get_user_from_headers<'a, 'b, E>( pub async fn get_user_from_headers<'a, 'b, E>(
@@ -85,9 +85,9 @@ where
{ {
let token = headers let token = headers
.get("Authorization") .get("Authorization")
.ok_or(AuthenticationError::InvalidCredentialsError)? .ok_or(AuthenticationError::InvalidCredentials)?
.to_str() .to_str()
.map_err(|_| AuthenticationError::InvalidCredentialsError)?; .map_err(|_| AuthenticationError::InvalidCredentials)?;
Ok(get_user_from_token(token, executor).await?) Ok(get_user_from_token(token, executor).await?)
} }
@@ -104,7 +104,7 @@ where
if user.role.is_mod() { if user.role.is_mod() {
Ok(user) Ok(user)
} else { } else {
Err(AuthenticationError::InvalidCredentialsError) Err(AuthenticationError::InvalidCredentials)
} }
} }
@@ -119,7 +119,7 @@ where
match user.role { match user.role {
Role::Admin => Ok(user), Role::Admin => Ok(user),
_ => Err(AuthenticationError::InvalidCredentialsError), _ => Err(AuthenticationError::InvalidCredentials),
} }
} }

View File

@@ -15,10 +15,10 @@ pub async fn read_from_payload(
let mut bytes = BytesMut::new(); let mut bytes = BytesMut::new();
while let Some(item) = payload.next().await { while let Some(item) = payload.next().await {
if bytes.len() >= cap { if bytes.len() >= cap {
return Err(ApiError::InvalidInputError(String::from(err_msg))); return Err(ApiError::InvalidInput(String::from(err_msg)));
} else { } else {
bytes.extend_from_slice(&item.map_err(|_| { bytes.extend_from_slice(&item.map_err(|_| {
ApiError::InvalidInputError( ApiError::InvalidInput(
"Unable to parse bytes in payload sent!".to_string(), "Unable to parse bytes in payload sent!".to_string(),
) )
})?); })?);

View File

@@ -34,7 +34,7 @@ pub fn validation_errors_to_string(
*errors.clone(), *errors.clone(),
Some(format!( Some(format!(
"of list {} with index {}", "of list {} with index {}",
index, field field, index
)), )),
)); ));
} }

View File

@@ -33,7 +33,7 @@ impl super::Validator for FabricValidator {
archive: &mut ZipArchive<Cursor<bytes::Bytes>>, archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> { ) -> Result<ValidationResult, ValidationError> {
archive.by_name("fabric.mod.json").map_err(|_| { archive.by_name("fabric.mod.json").map_err(|_| {
ValidationError::InvalidInputError( ValidationError::InvalidInput(
"No fabric.mod.json present for Fabric file.".into(), "No fabric.mod.json present for Fabric file.".into(),
) )
})?; })?;

View File

@@ -33,7 +33,7 @@ impl super::Validator for ForgeValidator {
archive: &mut ZipArchive<Cursor<bytes::Bytes>>, archive: &mut ZipArchive<Cursor<bytes::Bytes>>,
) -> Result<ValidationResult, ValidationError> { ) -> Result<ValidationResult, ValidationError> {
archive.by_name("META-INF/mods.toml").map_err(|_| { archive.by_name("META-INF/mods.toml").map_err(|_| {
ValidationError::InvalidInputError( ValidationError::InvalidInput(
"No mods.toml present for Forge file.".into(), "No mods.toml present for Forge file.".into(),
) )
})?; })?;

View File

@@ -14,15 +14,15 @@ mod pack;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ValidationError { pub enum ValidationError {
#[error("Unable to read Zip Archive: {0}")] #[error("Unable to read Zip Archive: {0}")]
ZipError(#[from] zip::result::ZipError), Zip(#[from] zip::result::ZipError),
#[error("IO Error: {0}")] #[error("IO Error: {0}")]
IoError(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("Error while validating JSON: {0}")] #[error("Error while validating JSON: {0}")]
SerdeError(#[from] serde_json::Error), SerDe(#[from] serde_json::Error),
#[error("Invalid Input: {0}")] #[error("Invalid Input: {0}")]
InvalidInputError(std::borrow::Cow<'static, str>), InvalidInput(std::borrow::Cow<'static, str>),
#[error("Error while managing threads")] #[error("Error while managing threads")]
BlockingError(#[from] actix_web::error::BlockingError), Blocking(#[from] actix_web::error::BlockingError),
} }
#[derive(Eq, PartialEq)] #[derive(Eq, PartialEq)]
@@ -93,7 +93,7 @@ pub async fn validate_file(
} }
if visited { if visited {
Err(ValidationError::InvalidInputError( Err(ValidationError::InvalidInput(
format!( format!(
"File extension {} is invalid for input file", "File extension {} is invalid for input file",
file_extension file_extension

View File

@@ -138,7 +138,7 @@ impl super::Validator for PackValidator {
) -> Result<ValidationResult, ValidationError> { ) -> Result<ValidationResult, ValidationError> {
let mut file = let mut file =
archive.by_name("modrinth.index.json").map_err(|_| { archive.by_name("modrinth.index.json").map_err(|_| {
ValidationError::InvalidInputError( ValidationError::InvalidInput(
"Pack manifest is missing.".into(), "Pack manifest is missing.".into(),
) )
})?; })?;
@@ -149,20 +149,20 @@ impl super::Validator for PackValidator {
let pack: PackFormat = serde_json::from_str(&contents)?; let pack: PackFormat = serde_json::from_str(&contents)?;
pack.validate().map_err(|err| { pack.validate().map_err(|err| {
ValidationError::InvalidInputError( ValidationError::InvalidInput(
validation_errors_to_string(err, None).into(), validation_errors_to_string(err, None).into(),
) )
})?; })?;
if pack.game != "minecraft" { if pack.game != "minecraft" {
return Err(ValidationError::InvalidInputError( return Err(ValidationError::InvalidInput(
format!("Game {0} does not exist!", pack.game).into(), format!("Game {0} does not exist!", pack.game).into(),
)); ));
} }
for file in pack.files { for file in pack.files {
if file.hashes.get(&FileHash::Sha1).is_none() { if file.hashes.get(&FileHash::Sha1).is_none() {
return Err(ValidationError::InvalidInputError( return Err(ValidationError::InvalidInput(
"All pack files must provide a SHA1 hash!".into(), "All pack files must provide a SHA1 hash!".into(),
)); ));
} }
@@ -171,7 +171,7 @@ impl super::Validator for PackValidator {
.components() .components()
.next() .next()
.ok_or_else(|| { .ok_or_else(|| {
ValidationError::InvalidInputError( ValidationError::InvalidInput(
"Invalid pack file path!".into(), "Invalid pack file path!".into(),
) )
})?; })?;
@@ -179,7 +179,7 @@ impl super::Validator for PackValidator {
match path { match path {
Component::CurDir | Component::Normal(_) => {} Component::CurDir | Component::Normal(_) => {}
_ => { _ => {
return Err(ValidationError::InvalidInputError( return Err(ValidationError::InvalidInput(
"Invalid pack file path!".into(), "Invalid pack file path!".into(),
)) ))
} }