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

View File

@@ -4,6 +4,7 @@ version = "0.2.0"
#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>"]
edition = "2018"
license = "AGPL-3.0"
[[bin]]
name = "labrinth"
@@ -19,7 +20,7 @@ tokio-stream = "0.1.8"
actix-multipart = "0.4.0"
actix-cors = "0.6.0"
meilisearch-sdk = "0.6.0"
meilisearch-sdk = "0.15.0"
reqwest = { version = "0.11.9", features = ["json"] }
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
WORKDIR /usr/src/labrinth

View File

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

View File

@@ -163,6 +163,19 @@
"nullable": []
}
},
"06a92b638c77276f36185788748191e7731a2cce874ecca4af913d0d0412d223": {
"query": "\n UPDATE versions\n SET downloads = $1\n WHERE (id = $2)\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int4",
"Int8"
]
},
"nullable": []
}
},
"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 ",
"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": {
"query": "\n DELETE FROM loaders_versions\n WHERE loaders_versions.version_id = $1\n ",
"describe": {
@@ -4102,6 +4128,18 @@
"nullable": []
}
},
"bd0d1da185dc7d21ccbbfde86fc093ce9eda7dd7e07f7a53882d427010fd58ca": {
"query": "\n DELETE FROM dependencies WHERE dependent_id = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8"
]
},
"nullable": []
}
},
"bdaab7da16d07169c29d96330fcc17ef2fb87fdfbadca23b7289c64420ac3a04": {
"query": "\n SELECT id, user_id, role, permissions, accepted\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2)\n ",
"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": {
"query": "\n SELECT f.id id FROM files f\n WHERE f.version_id = $1\n ",
"describe": {

View File

@@ -28,7 +28,7 @@ macro_rules! generate_ids {
retry_count += 1;
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)]
pub enum DatabaseError {
#[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")]
RandomIdError,
RandomId,
#[error(
"Invalid identifier: Category/version names must contain only ASCII \
alphanumeric characters or '_-'."
)]
InvalidIdentifier(String),
#[error("Invalid permissions bitflag!")]
BitflagError,
Bitflag,
#[error("A database request failed")]
Other(String),
}

View File

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

View File

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

View File

@@ -31,8 +31,6 @@ struct Config {
#[options(no_short, help = "Skip indexing on startup")]
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")]
reset_indices: bool,
@@ -79,12 +77,6 @@ async fn main() -> std::io::Result<()> {
.await
.unwrap();
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
@@ -252,18 +244,18 @@ async fn main() -> std::io::Result<()> {
if let Some(header) =
req.headers().get("CF-Connecting-IP")
{
header.to_str().map_err(|_| {
ARError::IdentificationError
})?
header
.to_str()
.map_err(|_| ARError::Identification)?
} else {
connection_info
.peer_addr()
.ok_or(ARError::IdentificationError)?
.ok_or(ARError::Identification)?
}
} else {
connection_info
.peer_addr()
.ok_or(ARError::IdentificationError)?
.ok_or(ARError::Identification)?
},
);

View File

@@ -475,13 +475,14 @@ pub struct Loader(pub String);
#[derive(Serialize, Deserialize)]
pub struct SearchRequest {
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 index: 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
use crate::models::error::ApiError;
use actix_web::ResponseError;
use log::*;
use thiserror::Error;
@@ -11,14 +12,14 @@ use thiserror::Error;
pub enum ARError {
/// Read/Write error on store
#[error("read/write operatiion failed: {0}")]
ReadWriteError(String),
ReadWrite(String),
/// Identifier error
#[error("client identification failed")]
IdentificationError,
Identification,
/// Limited Error
#[error("You are being ratelimited. Please wait {reset} seconds. {remaining}/{max_requests} remaining.")]
LimitedError {
Limited {
max_requests: usize,
remaining: usize,
reset: u64,
@@ -28,7 +29,7 @@ pub enum ARError {
impl ResponseError for ARError {
fn error_response(&self) -> actix_web::HttpResponse {
match self {
Self::LimitedError {
Self::Limited {
max_requests,
remaining,
reset,
@@ -44,10 +45,17 @@ impl ResponseError for ARError {
));
response
.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())
.body(self.to_string()),
_ => actix_web::HttpResponse::build(self.status_code()).json(
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;
ActorResponse::Update(Box::pin(future::ready(Ok(new_val))))
}
None => {
return ActorResponse::Update(Box::pin(future::ready(Err(
ARError::ReadWriteError(
"memory store: read failed!".to_string(),
),
))))
}
None => ActorResponse::Update(Box::pin(future::ready(Err(
ARError::ReadWrite(
"memory store: read failed!".to_string(),
),
)))),
},
ActorMessage::Get(key) => {
if self.inner.contains_key(&key) {
@@ -121,7 +119,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(c) => c,
None => {
return ActorResponse::Get(Box::pin(future::ready(
Err(ARError::ReadWriteError(
Err(ARError::ReadWrite(
"memory store: read failed!".to_string(),
)),
)))
@@ -138,7 +136,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(d) => d,
None => {
return ActorResponse::Expire(Box::pin(future::ready(
Err(ARError::ReadWriteError(
Err(ARError::ReadWrite(
"memory store: read failed!".to_string(),
)),
)))
@@ -156,7 +154,7 @@ impl Handler<ActorMessage> for MemoryStoreActor {
Some(c) => c,
None => {
return ActorResponse::Remove(Box::pin(future::ready(
Err(ARError::ReadWriteError(
Err(ARError::ReadWrite(
"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::{ActorMessage, ActorResponse};
use actix::dev::*;
@@ -19,28 +18,9 @@ use std::{
time::Duration,
};
/// Type that implements the ratelimit middleware.
///
/// 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);
/// }
/// ```
type RateLimiterIdentifier =
Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>;
pub struct RateLimiter<T>
where
T: Handler<ActorMessage> + Send + Sync + 'static,
@@ -49,7 +29,7 @@ where
interval: Duration,
max_requests: usize,
store: Addr<T>,
identifier: Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError>>>,
identifier: RateLimiterIdentifier,
ignore_ips: Vec<String>,
}
@@ -62,9 +42,8 @@ where
pub fn new(store: Addr<T>) -> Self {
let identifier = |req: &ServiceRequest| {
let connection_info = req.connection_info();
let ip = connection_info
.peer_addr()
.ok_or(ARError::IdentificationError)?;
let ip =
connection_info.peer_addr().ok_or(ARError::Identification)?;
Ok(String::from(ip))
};
RateLimiter {
@@ -144,8 +123,7 @@ where
// Exists here for the sole purpose of knowing the max_requests and interval from RateLimiter
max_requests: usize,
interval: u64,
identifier:
Rc<Box<dyn Fn(&ServiceRequest) -> Result<String, ARError> + 'static>>,
identifier: RateLimiterIdentifier,
ignore_ips: Vec<String>,
}
@@ -187,7 +165,7 @@ where
let remaining: ActorResponse = store
.send(ActorMessage::Get(String::from(&identifier)))
.await
.map_err(|_| ARError::IdentificationError)?;
.map_err(|_| ARError::Identification)?;
match remaining {
ActorResponse::Get(opt) => {
let opt = opt.await?;
@@ -199,7 +177,7 @@ where
)))
.await
.map_err(|_| {
ARError::ReadWriteError(
ARError::ReadWrite(
"Setting timeout".to_string(),
)
})?;
@@ -209,7 +187,7 @@ where
};
if c == 0 {
info!("Limit exceeded for client: {}", &identifier);
Err(ARError::LimitedError {
Err(ARError::Limited {
max_requests,
remaining: c,
reset: reset.as_secs(),
@@ -224,7 +202,7 @@ where
})
.await
.map_err(|_| {
ARError::ReadWriteError(
ARError::ReadWrite(
"Decrementing ratelimit".to_string(),
)
})?;
@@ -270,7 +248,7 @@ where
})
.await
.map_err(|_| {
ARError::ReadWriteError(
ARError::ReadWrite(
"Creating store entry".to_string(),
)
})?;

View File

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

View File

@@ -114,7 +114,7 @@ pub async fn maven_metadata(
Ok(HttpResponse::Ok()
.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>(
@@ -211,9 +211,9 @@ pub async fn version_file(
name: project.inner.title,
description: project.inner.description,
};
return Ok(HttpResponse::Ok().content_type("text/xml").body(
yaserde::ser::to_string(&respdata).map_err(ApiError::XmlError)?,
));
return Ok(HttpResponse::Ok()
.content_type("text/xml")
.body(yaserde::ser::to_string(&respdata).map_err(ApiError::Xml)?));
} else if let Some(selected_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)]
pub enum ApiError {
#[error("Environment Error")]
EnvError(#[from] dotenv::Error),
Env(#[from] dotenv::Error),
#[error("Error while uploading file")]
FileHostingError(#[from] FileHostingError),
FileHosting(#[from] FileHostingError),
#[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError),
Database(#[from] crate::database::models::DatabaseError),
#[error("Database Error: {0}")]
SqlxDatabaseError(#[from] sqlx::Error),
SqlxDatabase(#[from] sqlx::Error),
#[error("Internal server error: {0}")]
XmlError(String),
Xml(String),
#[error("Deserialization error: {0}")]
JsonError(#[from] serde_json::Error),
Json(#[from] serde_json::Error),
#[error("Authentication Error: {0}")]
AuthenticationError(#[from] crate::util::auth::AuthenticationError),
Authentication(#[from] crate::util::auth::AuthenticationError),
#[error("Authentication Error: {0}")]
CustomAuthenticationError(String),
CustomAuthentication(String),
#[error("Invalid Input: {0}")]
InvalidInputError(String),
InvalidInput(String),
#[error("Error while validating input: {0}")]
ValidationError(String),
Validation(String),
#[error("Search Error: {0}")]
SearchError(#[from] meilisearch_sdk::errors::Error),
Search(#[from] meilisearch_sdk::errors::Error),
#[error("Indexing Error: {0}")]
IndexingError(#[from] crate::search::indexing::IndexingError),
Indexing(#[from] crate::search::indexing::IndexingError),
}
impl actix_web::ResponseError for ApiError {
fn status_code(&self) -> actix_web::http::StatusCode {
match self {
ApiError::EnvError(..) => {
ApiError::Env(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::DatabaseError(..) => {
ApiError::Database(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::SqlxDatabaseError(..) => {
ApiError::SqlxDatabase(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::AuthenticationError(..) => {
ApiError::Authentication(..) => {
actix_web::http::StatusCode::UNAUTHORIZED
}
ApiError::CustomAuthenticationError(..) => {
ApiError::CustomAuthentication(..) => {
actix_web::http::StatusCode::UNAUTHORIZED
}
ApiError::XmlError(..) => {
ApiError::Xml(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::JsonError(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::SearchError(..) => {
ApiError::Json(..) => actix_web::http::StatusCode::BAD_REQUEST,
ApiError::Search(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::IndexingError(..) => {
ApiError::Indexing(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::FileHostingError(..) => {
ApiError::FileHosting(..) => {
actix_web::http::StatusCode::INTERNAL_SERVER_ERROR
}
ApiError::InvalidInputError(..) => {
ApiError::InvalidInput(..) => {
actix_web::http::StatusCode::BAD_REQUEST
}
ApiError::ValidationError(..) => {
ApiError::Validation(..) => {
actix_web::http::StatusCode::BAD_REQUEST
}
}
@@ -228,18 +228,18 @@ impl actix_web::ResponseError for ApiError {
actix_web::HttpResponse::build(self.status_code()).json(
crate::models::error::ApiError {
error: match self {
ApiError::EnvError(..) => "environment_error",
ApiError::SqlxDatabaseError(..) => "database_error",
ApiError::DatabaseError(..) => "database_error",
ApiError::AuthenticationError(..) => "unauthorized",
ApiError::CustomAuthenticationError(..) => "unauthorized",
ApiError::XmlError(..) => "xml_error",
ApiError::JsonError(..) => "json_error",
ApiError::SearchError(..) => "search_error",
ApiError::IndexingError(..) => "indexing_error",
ApiError::FileHostingError(..) => "file_hosting_error",
ApiError::InvalidInputError(..) => "invalid_input",
ApiError::ValidationError(..) => "invalid_input",
ApiError::Env(..) => "environment_error",
ApiError::SqlxDatabase(..) => "database_error",
ApiError::Database(..) => "database_error",
ApiError::Authentication(..) => "unauthorized",
ApiError::CustomAuthentication(..) => "unauthorized",
ApiError::Xml(..) => "xml_error",
ApiError::Json(..) => "json_error",
ApiError::Search(..) => "search_error",
ApiError::Indexing(..) => "indexing_error",
ApiError::FileHosting(..) => "file_hosting_error",
ApiError::InvalidInput(..) => "invalid_input",
ApiError::Validation(..) => "invalid_input",
},
description: &self.to_string(),
},

View File

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

View File

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

View File

@@ -102,9 +102,10 @@ pub async fn dependency_list(
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let result =
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool)
.await?;
let result = database::models::Project::get_full_from_slug_or_project_id(
&string, &**pool,
)
.await?;
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(
dependencies
.iter()
.map(|x| if x.0.is_none() {
.filter_map(|x| if x.0.is_none() {
if let Some(mod_dependency_id) = x.2 {
Some(mod_dependency_id)
} else {
@@ -161,12 +162,11 @@ pub async fn dependency_list(
} else {
x.1
})
.flatten()
.collect(),
&**pool,
),
database::Version::get_many_full(
dependencies.iter().map(|x| x.0).flatten().collect(),
dependencies.iter().filter_map(|x| x.0).collect(),
&**pool,
)
);
@@ -282,7 +282,7 @@ pub async fn project_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?;
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;
@@ -315,7 +315,7 @@ pub async fn project_edit(
if let Some(title) = &new_project.title {
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!"
.to_string(),
));
@@ -336,7 +336,7 @@ pub async fn project_edit(
if let Some(description) = &new_project.description {
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!"
.to_string(),
));
@@ -357,7 +357,7 @@ pub async fn project_edit(
if let Some(status) = &new_project.status {
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!"
.to_string(),
));
@@ -367,7 +367,7 @@ pub async fn project_edit(
|| status == &ProjectStatus::Approved)
&& !user.role.is_mod()
{
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"You don't have permission to set this status"
.to_string(),
));
@@ -375,7 +375,7 @@ pub async fn project_edit(
if status == &ProjectStatus::Processing {
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",
)));
}
@@ -420,7 +420,7 @@ pub async fn project_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"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 !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!"
.to_string(),
));
@@ -481,7 +481,7 @@ pub async fn project_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Category {} does not exist.",
category.clone()
))
@@ -502,7 +502,7 @@ pub async fn project_edit(
if let Some(issues_url) = &new_project.issues_url {
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!"
.to_string(),
));
@@ -523,7 +523,7 @@ pub async fn project_edit(
if let Some(source_url) = &new_project.source_url {
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!"
.to_string(),
));
@@ -544,7 +544,7 @@ pub async fn project_edit(
if let Some(wiki_url) = &new_project.wiki_url {
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!"
.to_string(),
));
@@ -565,7 +565,7 @@ pub async fn project_edit(
if let Some(license_url) = &new_project.license_url {
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!"
.to_string(),
));
@@ -586,7 +586,7 @@ pub async fn project_edit(
if let Some(discord_url) = &new_project.discord_url {
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!"
.to_string(),
));
@@ -607,7 +607,7 @@ pub async fn project_edit(
if let Some(slug) = &new_project.slug {
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!"
.to_string(),
));
@@ -629,7 +629,7 @@ pub async fn project_edit(
.await?;
if results.exists.unwrap_or(true) {
return Err(ApiError::InvalidInputError(
return Err(ApiError::InvalidInput(
"Slug collides with other project's id!"
.to_string(),
));
@@ -652,7 +652,7 @@ pub async fn project_edit(
if let Some(new_side) = &new_project.client_side {
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!"
.to_string(),
));
@@ -680,7 +680,7 @@ pub async fn project_edit(
if let Some(new_side) = &new_project.server_side {
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!"
.to_string(),
));
@@ -708,7 +708,7 @@ pub async fn project_edit(
if let Some(license) = &new_project.license_id {
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!"
.to_string(),
));
@@ -736,7 +736,7 @@ pub async fn project_edit(
if let Some(donations) = &new_project.donation_urls {
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!"
.to_string(),
));
@@ -760,7 +760,7 @@ pub async fn project_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Platform {} does not exist.",
donation.id.clone()
))
@@ -784,7 +784,7 @@ pub async fn project_edit(
if !user.role.is_mod()
&& 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!"
.to_string(),
));
@@ -809,7 +809,7 @@ pub async fn project_edit(
if !user.role.is_mod()
&& 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!"
.to_string(),
));
@@ -830,7 +830,7 @@ pub async fn project_edit(
if let Some(body) = &new_project.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!"
.to_string(),
));
@@ -852,7 +852,7 @@ pub async fn project_edit(
transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::CustomAuthenticationError(
Err(ApiError::CustomAuthentication(
"You do not have permission to edit this project!".to_string(),
))
}
@@ -889,7 +889,7 @@ pub async fn project_icon_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -901,15 +901,15 @@ pub async fn project_icon_edit(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
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."
.to_string(),
));
@@ -958,7 +958,7 @@ pub async fn project_icon_edit(
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::InvalidInputError(format!(
Err(ApiError::InvalidInput(format!(
"Invalid format for project icon: {}",
ext.ext
)))
@@ -981,7 +981,7 @@ pub async fn delete_project_icon(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -993,15 +993,15 @@ pub async fn delete_project_icon(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
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."
.to_string(),
));
@@ -1057,7 +1057,7 @@ pub async fn add_gallery_item(
crate::util::ext::get_image_content_type(&*ext.ext)
{
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")?;
@@ -1071,7 +1071,7 @@ pub async fn add_gallery_item(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1083,15 +1083,15 @@ pub async fn add_gallery_item(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
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."
.to_string(),
));
@@ -1143,7 +1143,7 @@ pub async fn add_gallery_item(
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::InvalidInputError(format!(
Err(ApiError::InvalidInput(format!(
"Invalid format for gallery image: {}",
ext.ext
)))
@@ -1182,7 +1182,7 @@ pub async fn edit_gallery_item(
let string = info.into_inner().0;
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(
@@ -1191,7 +1191,7 @@ pub async fn edit_gallery_item(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1203,15 +1203,15 @@ pub async fn edit_gallery_item(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
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."
.to_string(),
));
@@ -1229,7 +1229,7 @@ pub async fn edit_gallery_item(
.fetch_optional(&mut *transaction)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Gallery item at URL {} is not part of the project's gallery.",
item.url
))
@@ -1319,7 +1319,7 @@ pub async fn delete_gallery_item(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1331,15 +1331,15 @@ pub async fn delete_gallery_item(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
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."
.to_string(),
));
@@ -1357,7 +1357,7 @@ pub async fn delete_gallery_item(
.fetch_optional(&mut *transaction)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Gallery item at URL {} is not part of the project's gallery.",
item.url
))
@@ -1403,7 +1403,7 @@ pub async fn project_delete(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1416,9 +1416,9 @@ pub async fn project_delete(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1427,7 +1427,7 @@ pub async fn project_delete(
.permissions
.contains(Permissions::DELETE_PROJECT)
{
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"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)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1512,7 +1512,7 @@ pub async fn project_follow(
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::InvalidInputError(
Err(ApiError::InvalidInput(
"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)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The specified project does not exist!".to_string(),
)
})?;
@@ -1580,7 +1580,7 @@ pub async fn project_unfollow(
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::InvalidInputError(
Err(ApiError::InvalidInput(
"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();
while let Some(item) = body.next().await {
bytes.extend_from_slice(&item.map_err(|_| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"Error while parsing request payload!".to_string(),
)
})?);
@@ -46,7 +46,7 @@ pub async fn report_create(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Invalid report type: {}",
new_report.report_type
))
@@ -91,7 +91,7 @@ pub async fn report_create(
)
}
ItemType::Unknown => {
return Err(ApiError::InvalidInputError(format!(
return Err(ApiError::InvalidInput(format!(
"Invalid report item type: {}",
new_report.item_type.as_str()
)))

View File

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

View File

@@ -38,7 +38,7 @@ pub async fn team_members_get_project(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?;
.map_err(ApiError::Database)?;
if team_member.is_some() {
let team_members: Vec<_> = members_data
@@ -80,7 +80,7 @@ pub async fn team_members_get(
let team_member =
TeamMember::get_from_user_id(id.into(), user.id.into(), &**pool)
.await
.map_err(ApiError::DatabaseError)?;
.map_err(ApiError::Database)?;
if team_member.is_some() {
let team_members: Vec<_> = members_data
@@ -119,7 +119,7 @@ pub async fn join_team(
if let Some(member) = member {
if member.accepted {
return Err(ApiError::InvalidInputError(
return Err(ApiError::InvalidInput(
"You are already a member of this team".to_string(),
));
}
@@ -138,7 +138,7 @@ pub async fn join_team(
transaction.commit().await?;
} else {
return Err(ApiError::InvalidInputError(
return Err(ApiError::InvalidInput(
"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)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
ApiError::CustomAuthentication(
"You don't have permission to edit members of this team"
.to_string(),
)
})?;
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"
.to_string(),
));
}
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(),
));
}
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(),
));
}
@@ -207,11 +207,11 @@ pub async fn add_team_member(
if let Some(req) = request {
if req.accepted {
return Err(ApiError::InvalidInputError(
return Err(ApiError::InvalidInput(
"The user is already a member of that team".to_string(),
));
} else {
return Err(ApiError::InvalidInputError(
return Err(ApiError::InvalidInput(
"There is already a pending member request for this user"
.to_string(),
));
@@ -221,9 +221,7 @@ pub async fn add_team_member(
crate::database::models::User::get(member.user_id, &**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
"An invalid User ID specified".to_string(),
)
ApiError::InvalidInput("An invalid User ID specified".to_string())
})?;
let new_id =
@@ -312,7 +310,7 @@ pub async fn edit_team_member(
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
ApiError::CustomAuthentication(
"You don't have permission to edit members of this team"
.to_string(),
)
@@ -321,7 +319,7 @@ pub async fn edit_team_member(
TeamMember::get_from_user_id_pending(id, user_id, &**pool)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
ApiError::CustomAuthentication(
"You don't have permission to edit members of this team"
.to_string(),
)
@@ -330,13 +328,13 @@ pub async fn edit_team_member(
let mut transaction = pool.begin().await?;
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(),
));
}
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"
.to_string(),
));
@@ -344,7 +342,7 @@ pub async fn edit_team_member(
if let Some(new_permissions) = edit_member.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"
.to_string(),
));
@@ -352,7 +350,7 @@ pub async fn edit_team_member(
}
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(),
));
}
@@ -394,7 +392,7 @@ pub async fn transfer_ownership(
)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
ApiError::CustomAuthentication(
"You don't have permission to edit members of this team"
.to_string(),
)
@@ -406,20 +404,20 @@ pub async fn transfer_ownership(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"The new owner specified does not exist".to_string(),
)
})?;
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"
.to_string(),
));
}
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(),
));
}
@@ -466,7 +464,7 @@ pub async fn remove_team_member(
TeamMember::get_from_user_id(id, current_user.id.into(), &**pool)
.await?
.ok_or_else(|| {
ApiError::CustomAuthenticationError(
ApiError::CustomAuthentication(
"You don't have permission to edit members of this team"
.to_string(),
)
@@ -478,7 +476,7 @@ pub async fn remove_team_member(
if let Some(delete_member) = delete_member {
if delete_member.role == crate::models::teams::OWNER_ROLE {
// 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(),
));
}
@@ -492,7 +490,7 @@ pub async fn remove_team_member(
{
TeamMember::delete(id, user_id, &**pool).await?;
} else {
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"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.
TeamMember::delete(id, user_id, &**pool).await?;
} else {
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"You do not have permission to cancel a team invite"
.to_string(),
));

View File

@@ -24,12 +24,12 @@ pub async fn forge_updates(
&id, &**pool,
)
.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();
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(

View File

@@ -166,7 +166,7 @@ pub async fn user_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?;
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(
@@ -201,7 +201,7 @@ pub async fn user_edit(
.execute(&mut *transaction)
.await?;
} else {
return Err(ApiError::InvalidInputError(format!(
return Err(ApiError::InvalidInput(format!(
"Username {} is taken!",
username
)));
@@ -252,7 +252,7 @@ pub async fn user_edit(
if let Some(role) = &new_user.role {
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!"
.to_string(),
));
@@ -276,7 +276,7 @@ pub async fn user_edit(
transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::CustomAuthenticationError(
Err(ApiError::CustomAuthentication(
"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 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."
.to_string(),
));
@@ -374,7 +374,7 @@ pub async fn user_icon_edit(
Ok(HttpResponse::NotFound().body(""))
}
} else {
Err(ApiError::InvalidInputError(format!(
Err(ApiError::InvalidInput(format!(
"Invalid format for user icon: {}",
ext.ext
)))
@@ -407,24 +407,18 @@ pub async fn user_delete(
if let Some(id) = id_option {
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(),
));
}
let mut transaction = pool.begin().await?;
let result;
if &*removal_type.removal_type == "full" {
result = crate::database::models::User::remove_full(
id,
&mut transaction,
)
.await?;
let result = if &*removal_type.removal_type == "full" {
crate::database::models::User::remove_full(id, &mut transaction)
.await?
} else {
result =
crate::database::models::User::remove(id, &mut transaction)
.await?;
crate::database::models::User::remove(id, &mut transaction).await?
};
transaction.commit().await?;
@@ -454,7 +448,7 @@ pub async fn user_follows(
if let Some(id) = id_option {
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(),
));
}
@@ -504,7 +498,7 @@ pub async fn user_notifications(
if let Some(id) = id_option {
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(),
));
}

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ pub async fn team_members_get(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?;
.map_err(ApiError::Database)?;
if team_member.is_some() {
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 !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(),
));
}

View File

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

View File

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

View File

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

View File

@@ -28,9 +28,10 @@ pub async fn version_list(
) -> Result<HttpResponse, ApiError> {
let string = info.into_inner().0;
let result =
database::models::Project::get_full_from_slug_or_project_id(&string, &**pool)
.await?;
let result = database::models::Project::get_full_from_slug_or_project_id(
&string, &**pool,
)
.await?;
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 featured: Option<bool>,
pub primary_file: Option<(String, String)>,
pub downloads: Option<u32>,
}
#[patch("{id}")]
@@ -199,7 +201,7 @@ pub async fn version_edit(
let user = get_user_from_headers(req.headers(), &**pool).await?;
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;
@@ -227,7 +229,7 @@ pub async fn version_edit(
if let Some(perms) = permissions {
if !perms.contains(Permissions::UPLOAD_VERSION) {
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"You do not have the permissions to edit this version!"
.to_string(),
));
@@ -321,7 +323,7 @@ pub async fn version_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"No database entry for game version provided."
.to_string(),
)
@@ -358,7 +360,7 @@ pub async fn version_edit(
)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"No database entry for loader provided."
.to_string(),
)
@@ -404,7 +406,7 @@ pub async fn version_edit(
.fetch_optional(&**pool)
.await?
.ok_or_else(|| {
ApiError::InvalidInputError(format!(
ApiError::InvalidInput(format!(
"Specified file with hash {} does not exist.",
primary_file.1.clone()
))
@@ -447,10 +449,38 @@ pub async fn version_edit(
.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?;
Ok(HttpResponse::NoContent().body(""))
} else {
Err(ApiError::CustomAuthenticationError(
Err(ApiError::CustomAuthentication(
"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()),
)
.await
.map_err(ApiError::SqlxDatabaseError)?;
.map_err(ApiError::SqlxDatabase)?;
Ok(HttpResponse::Ok().body(""))
}
@@ -524,9 +554,9 @@ pub async fn version_delete(
&**pool,
)
.await
.map_err(ApiError::DatabaseError)?
.map_err(ApiError::Database)?
.ok_or_else(|| {
ApiError::InvalidInputError(
ApiError::InvalidInput(
"You do not have permission to delete versions in this team".to_string(),
)
})?;
@@ -535,7 +565,7 @@ pub async fn version_delete(
.permissions
.contains(Permissions::DELETE_VERSION)
{
return Err(ApiError::CustomAuthenticationError(
return Err(ApiError::CustomAuthentication(
"You do not have permission to delete versions in this team"
.to_string(),
));

View File

@@ -8,23 +8,24 @@ use meilisearch_sdk::client::Client;
use meilisearch_sdk::indexes::Index;
use meilisearch_sdk::settings::Settings;
use sqlx::postgres::PgPool;
use std::collections::{HashMap, VecDeque};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum IndexingError {
#[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}")]
SerdeError(#[from] serde_json::Error),
Serde(#[from] serde_json::Error),
#[error("Error while parsing a timestamp: {0}")]
ParseDateError(#[from] chrono::format::ParseError),
ParseDate(#[from] chrono::format::ParseError),
#[error("Database Error: {0}")]
SqlxError(#[from] sqlx::error::Error),
Sqlx(#[from] sqlx::error::Error),
#[error("Database Error: {0}")]
DatabaseError(#[from] crate::database::models::DatabaseError),
Database(#[from] crate::database::models::DatabaseError),
#[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
@@ -57,8 +58,8 @@ pub async fn index_projects(
if settings.index_local {
docs_to_add.append(&mut index_local(pool.clone()).await?);
}
// Write Indices
// Write Indices
add_projects(docs_to_add, config).await?;
Ok(())
@@ -67,122 +68,91 @@ pub async fn index_projects(
pub async fn reset_indices(config: &SearchConfig) -> Result<(), IndexingError> {
let client = config.make_client();
client.delete_index("relevance_projects").await?;
client.delete_index("downloads_projects").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?;
client.delete_index("projects").await?;
client.delete_index("projects_filtered").await?;
Ok(())
}
async fn update_index<'a>(
client: &'a Client<'a>,
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>,
async fn create_index(
client: &Client,
name: &'static str,
rules: impl FnOnce() -> Vec<&'static str>,
) -> Result<Index<'a>, IndexingError> {
custom_rules: Option<&'static [&'static str]>,
) -> Result<Index, IndexingError> {
client
.delete_index(name)
.await?
.wait_for_completion(client, None, None)
.await?;
match client.get_index(name).await {
// TODO: update index settings on startup (or delete old indices on startup)
Ok(index) => Ok(index),
Err(meilisearch_sdk::errors::Error::MeiliSearchError {
error_code: meilisearch_sdk::errors::ErrorCode::IndexNotFound,
..
}) => {
Ok(index) => {
index
.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
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
.set_settings(&default_settings().with_ranking_rules(rules()))
.set_settings(&settings)
.await?
.wait_for_completion(client, None, None)
.await?;
Ok(index)
}
Err(e) => {
log::warn!("Unhandled error while creating index: {}", e);
Err(IndexingError::IndexDBError(e))
Err(IndexingError::Indexing(e))
}
}
}
async fn add_to_index(
index: Index<'_>,
client: &Client,
index: Index,
mods: &[UploadSearchProject],
) -> Result<(), IndexingError> {
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(())
}
async fn create_and_add_to_index<'a>(
client: &'a Client<'a>,
projects: &'a [UploadSearchProject],
async fn create_and_add_to_index(
client: &Client,
projects: &[UploadSearchProject],
name: &'static str,
rule: &'static str,
custom_rules: Option<&'static [&'static str]>,
) -> Result<(), IndexingError> {
let index = create_index(client, name, || {
let mut relevance_rules = default_rules();
relevance_rules.push_back(rule);
relevance_rules.into()
})
.await?;
add_to_index(index, projects).await?;
let index = create_index(client, name, custom_rules).await?;
add_to_index(client, index, projects).await?;
Ok(())
}
@@ -192,65 +162,32 @@ pub async fn add_projects(
) -> Result<(), IndexingError> {
let client = config.make_client();
create_and_add_to_index(&client, &projects, "projects", None).await?;
create_and_add_to_index(
&client,
&projects,
"relevance_projects",
"desc(downloads)",
)
.await?;
create_and_add_to_index(
&client,
&projects,
"downloads_projects",
"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)",
"projects_filtered",
Some(&[
"sort",
"words",
"typo",
"proximity",
"attribute",
"exactness",
]),
)
.await?;
Ok(())
}
//region Utils
fn default_rules() -> VecDeque<&'static str> {
vec![
"typo",
"words",
"proximity",
"attribute",
"wordsPosition",
"exactness",
]
.into()
}
fn default_settings() -> Settings {
Settings::new()
.with_displayed_attributes(DEFAULT_DISPLAYED_ATTRIBUTES)
.with_searchable_attributes(DEFAULT_SEARCHABLE_ATTRIBUTES)
.with_stop_words(Vec::<String>::new())
.with_synonyms(HashMap::<String, Vec<String>>::new())
.with_attributes_for_faceting(DEFAULT_ATTRIBUTES_FOR_FACETING)
.with_sortable_attributes(DEFAULT_SORTABLE_ATTRIBUTES)
.with_filterable_attributes(DEFAULT_ATTRIBUTES_FOR_FACETING)
}
const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
@@ -275,72 +212,22 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
];
const DEFAULT_SEARCHABLE_ATTRIBUTES: &[&str] =
&["title", "description", "categories", "versions", "author"];
&["title", "description", "author"];
const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
"categories",
"host",
"versions",
"license",
"client_side",
"server_side",
"project_type",
"downloads",
"follows",
"author",
"title",
"date_created",
"date_modified",
];
//endregion
// This shouldn't be relied on for proper sorting, but it makes an
// attempt at getting proper sorting for Mojang's versions.
// 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!(),
}
}
}
const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] =
&["downloads", "follows", "date_created", "date_modified"];

View File

@@ -15,13 +15,13 @@ pub mod indexing;
#[derive(Error, Debug)]
pub enum SearchError {
#[error("MeiliSearch Error: {0}")]
MeiliSearchError(#[from] meilisearch_sdk::errors::Error),
MeiliSearch(#[from] meilisearch_sdk::errors::Error),
#[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}")]
IntParsingError(#[from] std::num::ParseIntError),
IntParsing(#[from] std::num::ParseIntError),
#[error("Environment Error")]
EnvError(#[from] dotenv::Error),
Env(#[from] dotenv::Error),
#[error("Invalid index to sort by: {0}")]
InvalidIndex(String),
}
@@ -29,10 +29,10 @@ pub enum SearchError {
impl actix_web::ResponseError for SearchError {
fn status_code(&self) -> StatusCode {
match self {
SearchError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
SearchError::MeiliSearchError(..) => StatusCode::BAD_REQUEST,
SearchError::SerdeError(..) => StatusCode::BAD_REQUEST,
SearchError::IntParsingError(..) => StatusCode::BAD_REQUEST,
SearchError::Env(..) => StatusCode::INTERNAL_SERVER_ERROR,
SearchError::MeiliSearch(..) => StatusCode::BAD_REQUEST,
SearchError::Serde(..) => StatusCode::BAD_REQUEST,
SearchError::IntParsing(..) => StatusCode::BAD_REQUEST,
SearchError::InvalidIndex(..) => StatusCode::BAD_REQUEST,
}
}
@@ -40,10 +40,10 @@ impl actix_web::ResponseError for SearchError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code()).json(ApiError {
error: match self {
SearchError::EnvError(..) => "environment_error",
SearchError::MeiliSearchError(..) => "meilisearch_error",
SearchError::SerdeError(..) => "invalid_input",
SearchError::IntParsingError(..) => "invalid_input",
SearchError::Env(..) => "environment_error",
SearchError::MeiliSearch(..) => "meilisearch_error",
SearchError::Serde(..) => "invalid_input",
SearchError::IntParsing(..) => "invalid_input",
SearchError::InvalidIndex(..) => "invalid_input",
},
description: &self.to_string(),
@@ -149,62 +149,85 @@ pub async fn search_for_project(
) -> Result<SearchResults, SearchError> {
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 index = info.index.as_deref().unwrap_or("relevance");
let limit = info.limit.as_deref().unwrap_or("10").parse()?;
let index = match index {
"relevance" => "relevance_projects",
"downloads" => "downloads_projects",
"follows" => "follows_projects",
"updated" => "updated_projects",
"newest" => "newest_projects",
let sort = match index {
"relevance" => ("projects", ["downloads:desc"]),
"downloads" => ("projects_filtered", ["downloads:desc"]),
"follows" => ("projects_filtered", ["follows:desc"]),
"updated" => ("projects_filtered", ["date_created:desc"]),
"newest" => ("projects_filtered", ["date_modified:desc"]),
i => return Err(SearchError::InvalidIndex(i.to_string())),
};
let meilisearch_index = client.get_index(index).await?;
let mut query = meilisearch_index.search();
let meilisearch_index = client.get_index(sort.0).await?;
query.with_limit(min(100, limit)).with_offset(offset);
let mut filter_string = String::new();
if let Some(search) = info.query.as_deref() {
if !search.is_empty() {
query.with_query(search);
let results = {
let mut query = meilisearch_index.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.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?;
query.execute::<ResultSearchProject>().await?
};
Ok(SearchResults {
hits: results.hits.into_iter().map(|r| r.result).collect(),

View File

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

View File

@@ -34,7 +34,7 @@ pub fn validation_errors_to_string(
*errors.clone(),
Some(format!(
"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>>,
) -> Result<ValidationResult, ValidationError> {
archive.by_name("fabric.mod.json").map_err(|_| {
ValidationError::InvalidInputError(
ValidationError::InvalidInput(
"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>>,
) -> Result<ValidationResult, ValidationError> {
archive.by_name("META-INF/mods.toml").map_err(|_| {
ValidationError::InvalidInputError(
ValidationError::InvalidInput(
"No mods.toml present for Forge file.".into(),
)
})?;

View File

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

View File

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