mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
feat: better api docs (#6586)
* feat: docs * fix tombi * chore: fix some of the routes rendering with missing / * response schemas * fix: restore labrinth docs routes * Fix path parameter docs in routes * remove utoipa-actix-web * consistency * improve version intros * improve formatting, examples * better hash examples, move openapi stuff to openapi.rs * more utoipa param fixes * request body docs * chore: remove moderation route from v2, remove ingest & webhooks from v3 spec * fixes * chore: tweak sources titles * fix * fix test * improve examples * increase compiler spawned thread stack size * remove unused tests & script * test * bro what * fix --------- Co-authored-by: aecsocket <43144841+aecsocket@users.noreply.github.com>
This commit is contained in:
co-authored by
aecsocket
parent
b26d048a63
commit
4a6fa9fc3d
@@ -79,6 +79,7 @@ jobs:
|
||||
SCCACHE_DIR: ${{ needs.skip-if-clean.outputs.internal == 'true' && '/mnt/sccache' || '' }}
|
||||
SCCACHE_CACHE_SIZE: ${{ needs.skip-if-clean.outputs.internal == 'true' && '10G' || '' }}
|
||||
RUSTC_WRAPPER: ${{ needs.skip-if-clean.outputs.internal == 'true' && 'sccache' || '' }}
|
||||
RUST_MIN_STACK: '16777216'
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
Generated
+147
-64
@@ -59,9 +59,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-http"
|
||||
version = "3.11.2"
|
||||
version = "3.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49"
|
||||
checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-rt",
|
||||
@@ -75,7 +75,7 @@ dependencies = [
|
||||
"derive_more 2.1.1",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"foldhash 0.1.5",
|
||||
"foldhash 0.2.0",
|
||||
"futures-core",
|
||||
"h2 0.3.27",
|
||||
"http 0.2.12",
|
||||
@@ -87,8 +87,8 @@ dependencies = [
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"sha1",
|
||||
"rand 0.10.1",
|
||||
"sha1 0.11.0",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -146,9 +146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-router"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
|
||||
checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7"
|
||||
dependencies = [
|
||||
"bytestring",
|
||||
"cfg-if",
|
||||
@@ -209,9 +209,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-web"
|
||||
version = "4.11.0"
|
||||
version = "4.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea"
|
||||
checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
@@ -228,7 +228,7 @@ dependencies = [
|
||||
"cookie 0.16.2",
|
||||
"derive_more 2.1.1",
|
||||
"encoding_rs",
|
||||
"foldhash 0.1.5",
|
||||
"foldhash 0.2.0",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"impl-more",
|
||||
@@ -244,7 +244,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"smallvec",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.1",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
@@ -1127,7 +1127,7 @@ dependencies = [
|
||||
"http-body-util",
|
||||
"md-5",
|
||||
"pin-project-lite",
|
||||
"sha1",
|
||||
"sha1 0.10.6",
|
||||
"sha2",
|
||||
"tracing",
|
||||
]
|
||||
@@ -1499,7 +1499,7 @@ version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1511,6 +1511,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
@@ -2224,6 +2233,12 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
@@ -2424,7 +2439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"rustversion",
|
||||
"spin 0.10.0",
|
||||
]
|
||||
@@ -2515,6 +2530,15 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.29.6"
|
||||
@@ -2791,7 +2815,7 @@ version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"const-oid 0.9.6",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -2904,12 +2928,23 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"const-oid",
|
||||
"crypto-common",
|
||||
"block-buffer 0.10.4",
|
||||
"const-oid 0.9.6",
|
||||
"crypto-common 0.1.6",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.1",
|
||||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
@@ -3127,7 +3162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||
dependencies = [
|
||||
"der",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
@@ -3151,7 +3186,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"generic-array",
|
||||
"group",
|
||||
@@ -4402,7 +4437,7 @@ version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4532,6 +4567,15 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
@@ -4893,9 +4937,9 @@ checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
|
||||
|
||||
[[package]]
|
||||
name = "impl-more"
|
||||
version = "0.1.9"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
|
||||
checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870"
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
@@ -5390,11 +5434,12 @@ dependencies = [
|
||||
"rust_iso3166",
|
||||
"rustls 0.23.32",
|
||||
"rusty-money",
|
||||
"scalar_api_reference",
|
||||
"sentry",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha1",
|
||||
"sha1 0.10.6",
|
||||
"sha2",
|
||||
"spdx",
|
||||
"sqlx",
|
||||
@@ -5411,8 +5456,6 @@ dependencies = [
|
||||
"url",
|
||||
"urlencoding",
|
||||
"utoipa",
|
||||
"utoipa-actix-web",
|
||||
"utoipa-scalar",
|
||||
"uuid 1.23.3",
|
||||
"validator",
|
||||
"webauthn-rs",
|
||||
@@ -5822,7 +5865,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8494,8 +8537,8 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"digest",
|
||||
"const-oid 0.9.6",
|
||||
"digest 0.10.7",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
@@ -8508,6 +8551,40 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.106",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
|
||||
dependencies = [
|
||||
"sha2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
@@ -8827,6 +8904,18 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scalar_api_reference"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d67c85edd56c41f364603eff688a330f01dee99bdfc84c005d3bc3d0721d6b2"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.4.0"
|
||||
@@ -9252,15 +9341,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9455,7 +9544,18 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9472,7 +9572,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9505,7 +9605,7 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -9782,7 +9882,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"digest",
|
||||
"digest 0.10.7",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"futures-channel",
|
||||
@@ -9803,7 +9903,7 @@ dependencies = [
|
||||
"rsa",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha1 0.10.6",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
@@ -11290,7 +11390,7 @@ dependencies = [
|
||||
"constant_time_eq",
|
||||
"hmac",
|
||||
"rand 0.9.2",
|
||||
"sha1",
|
||||
"sha1 0.10.6",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
@@ -11505,7 +11605,7 @@ dependencies = [
|
||||
"rand 0.9.2",
|
||||
"rustls 0.23.32",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"sha1 0.10.6",
|
||||
"thiserror 2.0.17",
|
||||
"utf-8",
|
||||
]
|
||||
@@ -11524,9 +11624,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
@@ -11757,17 +11857,6 @@ dependencies = [
|
||||
"utoipa-gen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-actix-web"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7eda9c23c05af0fb812f6a177514047331dac4851a2c8e9c4b895d6d826967f"
|
||||
dependencies = [
|
||||
"actix-service",
|
||||
"actix-web",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-gen"
|
||||
version = "5.4.0"
|
||||
@@ -11782,18 +11871,6 @@ dependencies = [
|
||||
"uuid 1.23.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utoipa-scalar"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59559e1509172f6b26c1cdbc7247c4ddd1ac6560fe94b584f81ee489b141f719"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
@@ -13450,6 +13527,12 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f06ae92f42f5e5c42443fd094f245eb656abf56dd7cce9b8b263236565e00f2"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.2"
|
||||
|
||||
+1
-2
@@ -158,6 +158,7 @@ rust-s3 = { version = "0.37.0", default-features = false, features = [
|
||||
] }
|
||||
rustls = "0.23.32"
|
||||
rusty-money = "0.4.1"
|
||||
scalar_api_reference = { version = "0.2.2", default-features = false }
|
||||
secrecy = "0.10.3"
|
||||
sentry = { version = "0.45.0", default-features = false, features = [
|
||||
"backtrace",
|
||||
@@ -217,8 +218,6 @@ typed-path = "0.12.0"
|
||||
url = "2.5.7"
|
||||
urlencoding = "2.1.3"
|
||||
utoipa = { version = "5.4.0", features = ["actix_extras", "chrono", "decimal"] }
|
||||
utoipa-actix-web = { version = "0.1.2" }
|
||||
utoipa-scalar = { version = "0.3.0", default-features = false }
|
||||
uuid = "1.18.1"
|
||||
validator = "0.20.0"
|
||||
webauthn-rs = "0.5.5"
|
||||
|
||||
@@ -100,6 +100,7 @@ rust_decimal = { workspace = true, features = [
|
||||
rust_iso3166 = { workspace = true }
|
||||
rustls.workspace = true
|
||||
rusty-money = { workspace = true }
|
||||
scalar_api_reference = { workspace = true, features = ["actix-web"] }
|
||||
sentry = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -128,8 +129,6 @@ tracing-actix-web = { workspace = true }
|
||||
url = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
utoipa = { workspace = true, features = ["url"] }
|
||||
utoipa-actix-web = { workspace = true }
|
||||
utoipa-scalar = { workspace = true, features = ["actix-web"] }
|
||||
uuid = { workspace = true, features = ["fast-rng", "serde", "v4", "v7"] }
|
||||
validator = { workspace = true, features = ["derive"] }
|
||||
webauthn-rs = { workspace = true, features = [
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Modrinth Internal API.
|
||||
|
||||
> [!caution]
|
||||
> This API is internal and is subject to breaking changes without notice. Please avoid using it in production.
|
||||
|
||||
> [!tip]
|
||||
> You're probably looking for [Modrinth's public API v2](/docs?api=v2)
|
||||
@@ -0,0 +1,101 @@
|
||||
> [!success]
|
||||
> API v2 is Modrinth's current, official API. It is the recommended API for integrations and applications and will receive long-term support.
|
||||
|
||||
## Authentication
|
||||
This API has two options for authentication: personal access tokens and [OAuth2](https://en.wikipedia.org/wiki/OAuth).
|
||||
All tokens are tied to a Modrinth user and use the `Authorization` header of the request.
|
||||
|
||||
Example:
|
||||
```
|
||||
Authorization: mrp_RNtLRSPmGj2pd1v1ubi52nX7TJJM9sznrmwhAuj511oe4t1jAqAQ3D6Wc8Ic
|
||||
```
|
||||
|
||||
> [!tip] You do not need a token for most requests.
|
||||
>
|
||||
> Generally speaking, only the following types of requests require a token:
|
||||
> - those which create data (such as version creation)
|
||||
> - those which modify data (such as editing a project)
|
||||
> - those which access private data (such as draft projects, notifications, emails, and payout data)
|
||||
|
||||
Each request requiring authentication has a certain scope. For example, to view the email of the user being requested, the token must have the `USER_READ_EMAIL` scope.
|
||||
You can find the list of available scopes [on GitHub](https://github.com/modrinth/code/blob/main/apps/labrinth/src/models/v3/pats.rs#L9). Making a request with an invalid scope will return a 401 error.
|
||||
|
||||
Please note that certain scopes and requests cannot be completed with a personal access token or using OAuth.
|
||||
For example, deleting a user account can only be done through Modrinth's frontend.
|
||||
|
||||
### Personal access tokens
|
||||
Personal access tokens (PATs) can be generated from [the user settings](https://modrinth.com/settings/account).
|
||||
|
||||
### GitHub tokens
|
||||
For backwards compatibility purposes, some types of GitHub tokens also work for authenticating a user with Modrinth's API, granting all scopes.
|
||||
**We urge any application still using GitHub tokens to start using personal access tokens for security and reliability purposes.**
|
||||
|
||||
## Cross-Origin Resource Sharing
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with the [W3C spec](https://www.w3.org/TR/cors/).
|
||||
This allows for cross-domain communication from the browser.
|
||||
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
||||
|
||||
## Identifiers
|
||||
The majority of items you can interact with in the API have a unique eight-digit base62 ID.
|
||||
Projects, versions, users, threads, teams, and reports all use this same way of identifying themselves.
|
||||
Version files use the sha1 or sha512 file hashes as identifiers.
|
||||
|
||||
Each project and user has a friendlier way of identifying them; slugs and usernames, respectively.
|
||||
While unique IDs are constant, slugs and usernames can change at any moment.
|
||||
If you want to store something in the long term, it is recommended to use the unique ID.
|
||||
|
||||
## Ratelimits
|
||||
The API has a ratelimit defined per IP. Limits and remaining amounts are given in the response headers.
|
||||
- `X-Ratelimit-Limit`: the maximum number of requests that can be made in a minute
|
||||
- `X-Ratelimit-Remaining`: the number of requests remaining in the current ratelimit window
|
||||
- `X-Ratelimit-Reset`: the time in seconds until the ratelimit window resets
|
||||
|
||||
**Ratelimits are the same no matter whether you use a token or not.**
|
||||
|
||||
The ratelimit is currently 300 requests per minute per IP. If your application is hitting rate limits, try caching responses, using batch requests, or spreading out your requests over time.
|
||||
|
||||
Higher rate limits are granted only in very rare cases. If you believe your use case requires an exception, please [contact us](mailto:support@modrinth.com).
|
||||
|
||||
## User Agents
|
||||
To access the Modrinth API, you **must** use provide a uniquely-identifying `User-Agent` header.
|
||||
Providing a user agent that only identifies your HTTP client library (such as "okhttp/4.9.3") increases the likelihood that we will block your traffic.
|
||||
It is recommended, but not required, to include contact information in your user agent.
|
||||
This allows us to contact you if we would like a change in your application's behavior without having to block your traffic.
|
||||
- Bad: `User-Agent: okhttp/4.9.3`
|
||||
- Good: `User-Agent: project_name`
|
||||
- Better: `User-Agent: github_username/project_name/1.56.0`
|
||||
- Best: `User-Agent: github_username/project_name/1.56.0 (launcher.com)` or `User-Agent: github_username/project_name/1.56.0 (contact@launcher.com)`
|
||||
|
||||
## Versioning
|
||||
Modrinth follows a simple pattern for its API versioning.
|
||||
In the event of a breaking API change, the API version in the URL path is bumped, and migration steps will be published below.
|
||||
|
||||
### Migrations
|
||||
Inside the following spoiler, you will be able to find all changes between versions of the Modrinth API, accompanied by tips and a guide to migrate applications to newer versions.
|
||||
|
||||
<details><summary>API v1 to API v2</summary>
|
||||
|
||||
These bullet points cover most changes in the v2 API, but please note that fields containing `mod` in most contexts have been shifted to `project`. For example, in the search route, the field `mod_id` was renamed to `project_id`.
|
||||
|
||||
- The search route has been moved from `/api/v1/mod` to `/v2/search`
|
||||
- New project fields: `project_type` (may be `mod` or `modpack`), `moderation_message` (which has a `message` and `body`), `gallery`
|
||||
- New search facet: `project_type`
|
||||
- Alphabetical sort removed (it didn't work and is not possible due to limits in MeiliSearch)
|
||||
- New search fields: `project_type`, `gallery`
|
||||
- The gallery field is an array of URLs to images that are part of the project's gallery
|
||||
- The gallery is a new feature which allows the user to upload images showcasing their mod to the CDN which will be displayed on their mod page
|
||||
- Internal change: Any project file uploaded to Modrinth is now validated to make sure it's a valid Minecraft mod, Modpack, etc.
|
||||
- For example, a Forge 1.17 mod with a JAR not containing a mods.toml will not be allowed to be uploaded to Modrinth
|
||||
- In project creation, projects may not upload a mod with no versions to review, however they can be saved as a draft
|
||||
- Similarly, for version creation, a version may not be uploaded without any files
|
||||
- Donation URLs have been enabled
|
||||
- New project status: `archived`. Projects with this status do not appear in search
|
||||
- Tags (such as categories, loaders) now have icons (SVGs) and specific project types attached
|
||||
- Dependencies have been wiped and replaced with a new system
|
||||
- Notifications now have a `type` field, such as `project_update`
|
||||
|
||||
Along with this, project subroutes (such as `/v2/project/{id}/version`) now allow the slug to be used as the ID. This is also the case with user routes.
|
||||
|
||||
<br />
|
||||
|
||||
The above snippet about User Agents was adapted from https://crates.io/policies, copyright (c) 2014 The Rust Project Developers under MIT license.
|
||||
@@ -0,0 +1,8 @@
|
||||
> [!caution]
|
||||
> API v3 is Modrinth's experimental API.
|
||||
>
|
||||
> **Please do not use this API in production. It will be deprecated in the future.**
|
||||
|
||||
|
||||
> [!tip]
|
||||
> You're probably looking for [Modrinth's public API v2](/docs?api=v2)
|
||||
@@ -18,7 +18,7 @@ use crate::models::ids::OAuthClientId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use actix_web::http::header::{CACHE_CONTROL, LOCATION, PRAGMA};
|
||||
use actix_web::web::{Data, Query, ServiceConfig};
|
||||
use actix_web::web::{Data, Query};
|
||||
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||
use chrono::{DateTime, Duration};
|
||||
use rand::distributions::Alphanumeric;
|
||||
@@ -33,7 +33,7 @@ use super::AuthenticationError;
|
||||
pub mod errors;
|
||||
pub mod uris;
|
||||
|
||||
pub fn config(cfg: &mut ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(init_oauth)
|
||||
.service(accept_client_scopes)
|
||||
.service(reject_client_scopes)
|
||||
@@ -57,6 +57,18 @@ pub struct OAuthClientAccessRequest {
|
||||
pub requested_scopes: Scopes,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
path = "/authorize",
|
||||
tag = "oauth",
|
||||
params(
|
||||
("client_id" = OAuthClientId, Query),
|
||||
("redirect_uri" = Option<String>, Query),
|
||||
("scope" = Option<String>, Query),
|
||||
("state" = Option<String>, Query)
|
||||
),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("authorize")]
|
||||
pub async fn init_oauth(
|
||||
req: HttpRequest,
|
||||
@@ -165,11 +177,15 @@ pub async fn init_oauth(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct RespondToOAuthClientScopes {
|
||||
pub flow: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
path = "/accept", tag = "oauth", responses((status = OK))
|
||||
)]
|
||||
#[post("accept")]
|
||||
pub async fn accept_client_scopes(
|
||||
req: HttpRequest,
|
||||
@@ -189,6 +205,10 @@ pub async fn accept_client_scopes(
|
||||
.await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
path = "/reject", tag = "oauth", responses((status = OK))
|
||||
)]
|
||||
#[post("reject")]
|
||||
pub async fn reject_client_scopes(
|
||||
req: HttpRequest,
|
||||
@@ -201,7 +221,7 @@ pub async fn reject_client_scopes(
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct TokenRequest {
|
||||
pub grant_type: String,
|
||||
pub code: String,
|
||||
@@ -216,6 +236,10 @@ pub struct TokenResponse {
|
||||
pub expires_in: i64,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
path = "/token", tag = "oauth", responses((status = OK))
|
||||
)]
|
||||
#[post("token")]
|
||||
/// Params should be in the urlencoded request body
|
||||
/// And client secret should be in the HTTP basic authorization header
|
||||
|
||||
+42
-11
@@ -343,6 +343,14 @@ pub fn app_setup(
|
||||
pub fn app_config(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
app_data_config(cfg, labrinth_config);
|
||||
app_fallback_config(cfg);
|
||||
}
|
||||
|
||||
pub fn app_data_config(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
cfg.app_data(web::FormConfig::default().error_handler(|err, _req| {
|
||||
routes::ApiError::Validation(err.to_string()).into()
|
||||
@@ -379,16 +387,17 @@ pub fn app_config(
|
||||
.app_data(labrinth_config.rate_limiter.clone())
|
||||
.app_data(labrinth_config.kafka_client.clone())
|
||||
.app_data(labrinth_config.search_state.clone())
|
||||
.app_data(labrinth_config.webauthn.clone())
|
||||
.configure(routes::v3::config)
|
||||
.configure(routes::internal::config)
|
||||
.configure(routes::root_config)
|
||||
.default_service(web::get().wrap(default_cors()).to(routes::not_found));
|
||||
.app_data(labrinth_config.webauthn.clone());
|
||||
}
|
||||
|
||||
pub fn utoipa_app_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
_labrinth_config: LabrinthConfig,
|
||||
pub fn app_fallback_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.configure(routes::root_config)
|
||||
.default_service(web::get().to(routes::not_found).wrap(default_cors()));
|
||||
}
|
||||
|
||||
pub fn app_routes_config(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
cfg.configure({
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -400,7 +409,29 @@ pub fn utoipa_app_config(
|
||||
|_cfg| ()
|
||||
}
|
||||
})
|
||||
.configure(routes::v2::utoipa_config)
|
||||
.configure(routes::v3::utoipa_config)
|
||||
.configure(routes::internal::utoipa_config);
|
||||
.configure(|cfg| app_routes_config_v2(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| app_routes_config_v3(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| app_routes_config_internal(cfg, labrinth_config));
|
||||
}
|
||||
|
||||
pub fn app_routes_config_v2(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
_labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
cfg.configure(routes::v2::config);
|
||||
}
|
||||
|
||||
pub fn app_routes_config_v3(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
_labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
cfg.configure(routes::public_config)
|
||||
.configure(routes::v3::config);
|
||||
}
|
||||
|
||||
pub fn app_routes_config_internal(
|
||||
cfg: &mut web::ServiceConfig,
|
||||
_labrinth_config: LabrinthConfig,
|
||||
) {
|
||||
cfg.configure(routes::internal::config);
|
||||
}
|
||||
|
||||
+169
-24
@@ -2,7 +2,7 @@
|
||||
|
||||
use actix_web::dev::Service;
|
||||
use actix_web::middleware::from_fn;
|
||||
use actix_web::{App, HttpServer, web};
|
||||
use actix_web::{App, HttpResponse, HttpServer, web};
|
||||
use actix_web_prom::PrometheusMetricsBuilder;
|
||||
use clap::Parser;
|
||||
|
||||
@@ -15,17 +15,22 @@ use labrinth::search;
|
||||
use labrinth::util::anrok;
|
||||
use labrinth::util::gotenberg::GotenbergClient;
|
||||
use labrinth::util::ratelimit::rate_limit_middleware;
|
||||
use labrinth::utoipa_app_config;
|
||||
use labrinth::{app_config, env};
|
||||
use labrinth::{app_data_config, app_fallback_config, env};
|
||||
use labrinth::{
|
||||
app_routes_config_internal, app_routes_config_v2, app_routes_config_v3,
|
||||
};
|
||||
use labrinth::{clickhouse, database, file_hosting};
|
||||
use scalar_api_reference::actix_web::config as scalar_config;
|
||||
use serde_json::json;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Arc;
|
||||
use tracing::{Instrument, info, info_span};
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
||||
use utoipa_actix_web::AppExt;
|
||||
use utoipa_scalar::Servable;
|
||||
use utoipa::PartialSchema;
|
||||
use utoipa::openapi::Content;
|
||||
use utoipa::openapi::response::Response;
|
||||
use utoipa::openapi::schema::Components;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[global_allocator]
|
||||
@@ -223,7 +228,16 @@ async fn app() -> std::io::Result<()> {
|
||||
info!("Starting Actix HTTP server!");
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
let mut docs_v2 = labrinth::routes::v2::ApiDoc::openapi();
|
||||
let mut docs_v3 = labrinth::routes::v3::ApiDoc::openapi();
|
||||
let mut docs_internal = labrinth::routes::internal::ApiDoc::openapi();
|
||||
#[cfg(target_os = "linux")]
|
||||
docs_v3.merge(labrinth::routes::debug::ApiDoc::openapi());
|
||||
document_error_responses(&mut docs_v2);
|
||||
document_error_responses(&mut docs_v3);
|
||||
document_error_responses(&mut docs_internal);
|
||||
|
||||
let app = App::new()
|
||||
.wrap(TracingLogger::default())
|
||||
.wrap_fn(|req, srv| {
|
||||
// We capture the same fields as `tracing-actix-web`'s `RootSpanBuilder`.
|
||||
@@ -260,31 +274,162 @@ async fn app() -> std::io::Result<()> {
|
||||
// transactions out of HTTP requests. However, we have to use our
|
||||
// own - See `sentry::SentryErrorReporting` for why.
|
||||
.wrap(labrinth::util::sentry::SentryErrorReporting)
|
||||
// Use `utoipa` for OpenAPI generation
|
||||
.into_utoipa_app()
|
||||
.configure(|cfg| utoipa_app_config(cfg, labrinth_config.clone()))
|
||||
.openapi_service(|api| utoipa_scalar::Scalar::with_url("/docs", ApiDoc::openapi().merge_from(api)))
|
||||
.into_app()
|
||||
.configure(|cfg| app_config(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| app_data_config(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| app_routes_config_v2(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| app_routes_config_v3(cfg, labrinth_config.clone()))
|
||||
.configure(|cfg| {
|
||||
app_routes_config_internal(cfg, labrinth_config.clone())
|
||||
});
|
||||
|
||||
let scalar_configuration = json!({
|
||||
"sources": [
|
||||
{
|
||||
"title": "API v2",
|
||||
"slug": "v2",
|
||||
"url": "/openapi/v2.json",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"title": "API v3 (UNSTABLE)",
|
||||
"slug": "v3",
|
||||
"url": "/openapi/v3.json"
|
||||
},
|
||||
{
|
||||
"title": "Internal API (HIGHLY UNSTABLE)",
|
||||
"slug": "internal",
|
||||
"url": "/openapi/internal.json"
|
||||
}
|
||||
],
|
||||
"agent": {
|
||||
"disabled": true
|
||||
},
|
||||
"mcp": {
|
||||
"disabled": true
|
||||
},
|
||||
"telemetry": false,
|
||||
|
||||
"metaData": {
|
||||
"title": "Modrinth API Documentation",
|
||||
"description": "Reference documentation for the Modrinth API.",
|
||||
"ogTitle": "Modrinth API Documentation",
|
||||
"ogDescription": "Reference documentation for the Modrinth API."
|
||||
},
|
||||
|
||||
"modelsSectionLabel": "Schemas",
|
||||
"defaultOpenFirstTag": true,
|
||||
"defaultOpenAllTags": false,
|
||||
"expandAllResponses": false,
|
||||
"expandAllSchemaProperties": false,
|
||||
"expandAllModelSections": false,
|
||||
"orderSchemaPropertiesBy": "preserve",
|
||||
"orderRequiredPropertiesFirst": true,
|
||||
"hideSearch": false,
|
||||
"searchHotKey": "k",
|
||||
"showOperationId": false,
|
||||
|
||||
"defaultHttpClient": {
|
||||
"targetKey": "shell",
|
||||
"clientKey": "curl"
|
||||
},
|
||||
|
||||
"persistAuth": false,
|
||||
"showDeveloperTools": "never",
|
||||
});
|
||||
|
||||
app.service(openapi_json_service("/openapi/v2.json", docs_v2))
|
||||
.service(openapi_json_service("/openapi/v3.json", docs_v3))
|
||||
.service(openapi_json_service(
|
||||
"/openapi/internal.json",
|
||||
docs_internal,
|
||||
))
|
||||
.configure(scalar_config("/docs", &scalar_configuration))
|
||||
.configure(app_fallback_config)
|
||||
})
|
||||
.bind(&ENV.BIND_ADDR)?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(info(title = "Labrinth"), modifiers(&SecurityAddon))]
|
||||
struct ApiDoc;
|
||||
fn openapi_json_service(
|
||||
path: &'static str,
|
||||
openapi: utoipa::openapi::OpenApi,
|
||||
) -> actix_web::Resource {
|
||||
web::resource(path).route(web::get().to(move || {
|
||||
let openapi = openapi.clone();
|
||||
async move { openapi_json(openapi) }
|
||||
}))
|
||||
}
|
||||
|
||||
struct SecurityAddon;
|
||||
fn openapi_json(openapi: utoipa::openapi::OpenApi) -> HttpResponse {
|
||||
match serde_json::to_string_pretty(&openapi) {
|
||||
Ok(body) => HttpResponse::Ok()
|
||||
.content_type("application/json; charset=utf-8")
|
||||
.body(body),
|
||||
Err(error) => {
|
||||
tracing::error!(%error, "Failed to serialize OpenAPI schema");
|
||||
HttpResponse::InternalServerError().finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::Modify for SecurityAddon {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
let components = openapi.components.as_mut().unwrap();
|
||||
components.add_security_scheme(
|
||||
"bearer_auth",
|
||||
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new(
|
||||
"authorization",
|
||||
fn document_error_responses(openapi: &mut utoipa::openapi::OpenApi) {
|
||||
let components = openapi.components.get_or_insert_with(Components::new);
|
||||
components.schemas.insert(
|
||||
"ApiError".to_string(),
|
||||
labrinth::models::error::ApiError::schema(),
|
||||
);
|
||||
|
||||
for path_item in openapi.paths.paths.values_mut() {
|
||||
add_default_error_response(&mut path_item.get);
|
||||
add_default_error_response(&mut path_item.put);
|
||||
add_default_error_response(&mut path_item.post);
|
||||
add_default_error_response(&mut path_item.delete);
|
||||
add_default_error_response(&mut path_item.options);
|
||||
add_default_error_response(&mut path_item.head);
|
||||
add_default_error_response(&mut path_item.patch);
|
||||
add_default_error_response(&mut path_item.trace);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_default_error_response(
|
||||
operation: &mut Option<utoipa::openapi::path::Operation>,
|
||||
) {
|
||||
if let Some(operation) = operation {
|
||||
for (status, response) in &mut operation.responses.responses {
|
||||
if !is_error_response_status(status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let utoipa::openapi::RefOr::T(response) = response {
|
||||
add_error_content(response);
|
||||
}
|
||||
}
|
||||
|
||||
operation
|
||||
.responses
|
||||
.responses
|
||||
.entry("500".to_string())
|
||||
.or_insert_with(|| error_response().into());
|
||||
}
|
||||
}
|
||||
|
||||
fn is_error_response_status(status: &str) -> bool {
|
||||
matches!(status.as_bytes().first(), Some(b'4' | b'5'))
|
||||
|| status == "default"
|
||||
}
|
||||
|
||||
fn error_response() -> Response {
|
||||
let mut response = Response::new("Error response");
|
||||
add_error_content(&mut response);
|
||||
response
|
||||
}
|
||||
|
||||
fn add_error_content(response: &mut Response) {
|
||||
if response.content.is_empty() {
|
||||
response.content.insert(
|
||||
"application/json".to_string(),
|
||||
Content::new(Some(utoipa::openapi::Ref::from_schema_name(
|
||||
"ApiError",
|
||||
))),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An error returned by the API
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct ApiError<'a> {
|
||||
#[schema(value_type = String)]
|
||||
pub error: &'a str,
|
||||
pub description: String,
|
||||
#[schema(value_type = Option<serde_json::Value>)]
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub details: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::large_stack_arrays)]
|
||||
|
||||
use crate::models::ids::{ThreadMessageId, VersionId};
|
||||
use crate::models::v3::billing::PriceDuration;
|
||||
use crate::models::{
|
||||
@@ -13,7 +15,7 @@ use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct LegacyNotification {
|
||||
pub id: NotificationId,
|
||||
pub user_id: UserId,
|
||||
@@ -30,14 +32,14 @@ pub struct LegacyNotification {
|
||||
pub actions: Vec<LegacyNotificationAction>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct LegacyNotificationAction {
|
||||
pub title: String,
|
||||
/// The route to call when this notification action is called. Formatted HTTP Method, route
|
||||
pub action_route: (String, String),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum LegacyNotificationBody {
|
||||
TaxNotification {
|
||||
|
||||
@@ -4,7 +4,7 @@ use ariadne::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct LegacyReport {
|
||||
pub id: ReportId,
|
||||
pub report_type: String,
|
||||
@@ -17,7 +17,7 @@ pub struct LegacyReport {
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LegacyItemType {
|
||||
Project,
|
||||
|
||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{routes::v2_reroute, search::ResultSearchProject};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, utoipa::ToSchema)]
|
||||
pub struct LegacySearchResults {
|
||||
pub hits: Vec<LegacyResultSearchProject>,
|
||||
pub offset: usize,
|
||||
@@ -11,7 +11,7 @@ pub struct LegacySearchResults {
|
||||
pub total_hits: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, utoipa::ToSchema)]
|
||||
pub struct LegacyResultSearchProject {
|
||||
pub project_id: String,
|
||||
pub project_type: String,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::models::{
|
||||
};
|
||||
|
||||
/// A member of a team
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct LegacyTeamMember {
|
||||
pub role: String,
|
||||
// is_owner removed, and role hardcoded to Owner if true,
|
||||
|
||||
@@ -7,7 +7,7 @@ use ariadne::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct LegacyThread {
|
||||
pub id: ThreadId,
|
||||
#[serde(rename = "type")]
|
||||
@@ -18,7 +18,7 @@ pub struct LegacyThread {
|
||||
pub members: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct LegacyThreadMessage {
|
||||
pub id: ThreadMessageId,
|
||||
pub author_id: Option<UserId>,
|
||||
@@ -26,7 +26,7 @@ pub struct LegacyThreadMessage {
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum LegacyMessageBody {
|
||||
Text {
|
||||
@@ -49,7 +49,9 @@ pub enum LegacyMessageBody {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Eq, PartialEq, Copy, Clone, utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum LegacyThreadType {
|
||||
Report,
|
||||
|
||||
@@ -6,7 +6,7 @@ use ariadne::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, utoipa::ToSchema)]
|
||||
pub struct LegacyUser {
|
||||
pub id: UserId,
|
||||
pub username: String,
|
||||
|
||||
@@ -75,7 +75,17 @@ impl Price {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, Eq, PartialEq, Debug, Copy, Clone)]
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum PriceDuration {
|
||||
FiveDays,
|
||||
|
||||
@@ -28,7 +28,7 @@ impl From<crate::database::models::DBModerationNote> for ModerationNote {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct PatchModerationNote {
|
||||
pub notes: Option<String>,
|
||||
pub user_rating: Option<i32>,
|
||||
|
||||
@@ -706,7 +706,7 @@ impl From<DBNotification> for Notification {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct NotificationAction {
|
||||
pub name: String,
|
||||
/// The route to call when this notification action is called. Formatted HTTP Method, route
|
||||
|
||||
@@ -10,14 +10,14 @@ use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub struct OAuthRedirectUri {
|
||||
pub id: OAuthRedirectUriId,
|
||||
pub client_id: OAuthClientId,
|
||||
pub uri: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct OAuthClientCreationResult {
|
||||
#[serde(flatten)]
|
||||
pub client: OAuthClient,
|
||||
@@ -25,7 +25,7 @@ pub struct OAuthClientCreationResult {
|
||||
pub client_secret: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub struct OAuthClient {
|
||||
pub id: OAuthClientId,
|
||||
pub name: String,
|
||||
@@ -48,7 +48,7 @@ pub struct OAuthClient {
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
pub struct OAuthClientAuthorization {
|
||||
pub id: OAuthClientAuthorizationId,
|
||||
pub app_id: OAuthClientId,
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::models::ids::{OrganizationId, TeamId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An organization of users who control a project
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Organization {
|
||||
/// The id of the organization
|
||||
pub id: OrganizationId,
|
||||
|
||||
@@ -234,7 +234,7 @@ impl PayoutStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct PayoutMethod {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
@@ -252,7 +252,7 @@ pub struct PayoutMethod {
|
||||
pub exchange_rate: Option<Decimal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PayoutMethodFee {
|
||||
#[serde(with = "rust_decimal::serde::float")]
|
||||
pub percentage: Decimal,
|
||||
@@ -274,6 +274,17 @@ impl PayoutMethodFee {
|
||||
#[derive(Clone)]
|
||||
pub struct PayoutDecimal(pub Decimal);
|
||||
|
||||
impl utoipa::PartialSchema for PayoutDecimal {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
utoipa::openapi::ObjectBuilder::new()
|
||||
.schema_type(utoipa::openapi::schema::Type::Number)
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for PayoutDecimal {}
|
||||
|
||||
impl Serialize for PayoutDecimal {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
@@ -293,7 +304,7 @@ impl<'de> Deserialize<'de> for PayoutDecimal {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PayoutInterval {
|
||||
Standard {
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::models::exp;
|
||||
use crate::models::ids::{
|
||||
FileId, OrganizationId, ProjectId, TeamId, ThreadId, VersionId,
|
||||
};
|
||||
use crate::routes::{FileHash, HashAlgorithm};
|
||||
use ariadne::ids::UserId;
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
@@ -970,6 +971,7 @@ pub struct VersionFile {
|
||||
pub id: Option<FileId>,
|
||||
/// A map of hashes of the file. The key is the hashing algorithm
|
||||
/// and the value is the string version of the hash.
|
||||
#[schema(value_type = std::collections::HashMap<HashAlgorithm, FileHash>)]
|
||||
pub hashes: std::collections::HashMap<String, String>,
|
||||
/// A direct link to the file for downloading it.
|
||||
pub url: String,
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct Report {
|
||||
pub thread_id: ThreadId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ItemType {
|
||||
Project,
|
||||
|
||||
@@ -38,7 +38,7 @@ impl SharedInstance {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct SharedInstanceVersion {
|
||||
pub id: SharedInstanceVersionId,
|
||||
pub shared_instance: SharedInstanceId,
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub const DEFAULT_ROLE: &str = "Member";
|
||||
|
||||
/// A team of users who control a project
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct Team {
|
||||
/// The id of the team
|
||||
pub id: TeamId,
|
||||
@@ -157,7 +157,7 @@ impl OrganizationPermissions {
|
||||
}
|
||||
|
||||
/// A member of a team
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct TeamMember {
|
||||
/// The ID of the team this team member is a member of
|
||||
pub team_id: TeamId,
|
||||
|
||||
@@ -242,7 +242,7 @@ impl Role {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UserFriend {
|
||||
// The user who accepted the friend request
|
||||
pub id: UserId,
|
||||
|
||||
@@ -46,20 +46,26 @@ pub const FILTERED_HEADERS: &[&str] = &[
|
||||
"x-vercel-ip-country",
|
||||
];
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(page_view_ingest)
|
||||
.service(playtime_ingest)
|
||||
.service(minecraft_server_play_ingest);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct UrlInput {
|
||||
url: String,
|
||||
}
|
||||
|
||||
//this route should be behind the cloudflare WAF to prevent non-browsers from calling it
|
||||
#[post("view")]
|
||||
async fn page_view_ingest(
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
request_body = UrlInput,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/view")]
|
||||
pub async fn page_view_ingest(
|
||||
req: HttpRequest,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -171,7 +177,7 @@ async fn page_view_ingest(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, utoipa::ToSchema)]
|
||||
pub struct PlaytimeInput {
|
||||
seconds: u16,
|
||||
loader: String,
|
||||
@@ -179,8 +185,14 @@ pub struct PlaytimeInput {
|
||||
parent: Option<crate::models::ids::VersionId>,
|
||||
}
|
||||
|
||||
#[post("playtime")]
|
||||
async fn playtime_ingest(
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
request_body = serde_json::Value,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/playtime")]
|
||||
pub async fn playtime_ingest(
|
||||
req: HttpRequest,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
@@ -249,7 +261,7 @@ struct MinecraftProfile {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct MinecraftJavaServerPlayInput {
|
||||
project_id: ProjectId,
|
||||
username: String,
|
||||
@@ -258,8 +270,14 @@ pub struct MinecraftJavaServerPlayInput {
|
||||
|
||||
pub const MINECRAFT_SERVER_PLAYS: &str = "minecraft_server_plays";
|
||||
|
||||
#[post("minecraft-server-play")]
|
||||
async fn minecraft_server_play_ingest(
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
request_body = MinecraftJavaServerPlayInput,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/minecraft-server-play")]
|
||||
pub async fn minecraft_server_play_ingest(
|
||||
req: HttpRequest,
|
||||
analytics_queue: web::Data<Arc<AnalyticsQueue>>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
|
||||
@@ -1,29 +1,46 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use actix_web::web;
|
||||
use eyre::Context;
|
||||
use eyre::eyre;
|
||||
use prometheus::IntGauge;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::SecurityAddon;
|
||||
use crate::util::cors::default_cors;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod pprof;
|
||||
pub(crate) mod pprof;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/debug")
|
||||
.wrap(default_cors())
|
||||
.configure({
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
pprof::config
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
|_cfg| ()
|
||||
}
|
||||
}),
|
||||
);
|
||||
#[cfg(target_os = "linux")]
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
paths(pprof::heap, pprof::flame_graph),
|
||||
modifiers(&DebugPathModifier, &SecurityAddon)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
struct DebugPathModifier;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
impl utoipa::Modify for DebugPathModifier {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
super::prefix_openapi_paths(openapi, "/debug", |_| false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(web::scope("/debug").wrap(default_cors()).configure({
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
pprof::config
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
|_cfg| ()
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn register_and_set_metrics(
|
||||
|
||||
@@ -5,11 +5,19 @@ use eyre::{Context, eyre};
|
||||
use prometheus::{IntGauge, Registry};
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(heap).service(flame_graph);
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get a heap profile.
|
||||
#[utoipa::path(
|
||||
tag = "debug",
|
||||
responses((
|
||||
status = OK,
|
||||
body = Vec<u8>,
|
||||
content_type = "application/octet-stream"
|
||||
))
|
||||
)]
|
||||
#[get("/pprof/heap", guard = "admin_key_guard")]
|
||||
pub async fn heap() -> Result<HttpResponse, ApiError> {
|
||||
let mut prof_ctl = jemalloc_pprof::PROF_CTL.as_ref().unwrap().lock().await;
|
||||
@@ -23,7 +31,11 @@ pub async fn heap() -> Result<HttpResponse, ApiError> {
|
||||
.body(pprof))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get a heap flame graph.
|
||||
#[utoipa::path(
|
||||
tag = "debug",
|
||||
responses((status = OK, body = String, content_type = "image/svg+xml"))
|
||||
)]
|
||||
#[get("/pprof/heap/flamegraph", guard = "admin_key_guard")]
|
||||
pub async fn flame_graph() -> Result<HttpResponse, ApiError> {
|
||||
let mut prof_ctl = jemalloc_pprof::PROF_CTL.as_ref().unwrap().lock().await;
|
||||
|
||||
@@ -23,9 +23,9 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tracing::trace;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/admin")
|
||||
web::scope("/admin")
|
||||
.service(count_download)
|
||||
.service(force_reindex)
|
||||
.service(force_reindex_project),
|
||||
@@ -130,7 +130,10 @@ async fn resolve_download_attribution_version(
|
||||
}
|
||||
|
||||
// This is an internal route, cannot be used without key
|
||||
/// Count a download.
|
||||
#[utoipa::path(
|
||||
context_path = "/admin",
|
||||
tag = "v2 admin",
|
||||
patch,
|
||||
operation_id = "countDownload",
|
||||
responses(
|
||||
@@ -308,7 +311,10 @@ pub async fn count_download(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
/// Reindex all projects.
|
||||
#[utoipa::path(
|
||||
context_path = "/admin",
|
||||
tag = "v2 admin",
|
||||
post,
|
||||
operation_id = "forceReindex",
|
||||
responses(
|
||||
@@ -330,7 +336,10 @@ pub async fn force_reindex(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
/// Reindex a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/admin",
|
||||
tag = "v2 admin",
|
||||
post,
|
||||
operation_id = "forceReindexProject",
|
||||
responses(
|
||||
|
||||
@@ -25,7 +25,7 @@ use url::Url;
|
||||
|
||||
use crate::routes::ApiError;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(ingest_click)
|
||||
.service(get_all)
|
||||
.service(create)
|
||||
@@ -40,9 +40,14 @@ pub struct IngestClick {
|
||||
pub affiliate_code_id: AffiliateCodeId,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Ingest an affiliate click.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/ingest-click")]
|
||||
async fn ingest_click(
|
||||
pub async fn ingest_click(
|
||||
req: HttpRequest,
|
||||
web::Json(ingest_click): web::Json<IngestClick>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -136,11 +141,14 @@ async fn ingest_click(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// List affiliate codes.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = OK, body = inline(Vec<AffiliateCode>)))
|
||||
)]
|
||||
#[get("")]
|
||||
async fn get_all(
|
||||
pub async fn get_all(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -187,11 +195,14 @@ pub struct CreateRequest {
|
||||
pub source_name: String,
|
||||
}
|
||||
|
||||
/// Create an affiliate code.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = OK, body = inline(AffiliateCode)))
|
||||
)]
|
||||
#[put("")]
|
||||
async fn create(
|
||||
pub async fn create(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -263,11 +274,14 @@ async fn create(
|
||||
Ok(web::Json(AffiliateCode::from(code, is_admin)))
|
||||
}
|
||||
|
||||
/// Get an affiliate code.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = OK, body = inline(AffiliateCode)))
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
async fn get(
|
||||
pub async fn get(
|
||||
req: HttpRequest,
|
||||
path: web::Path<(AffiliateCodeId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -302,9 +316,14 @@ async fn get(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Delete an affiliate code.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
async fn delete(
|
||||
pub async fn delete(
|
||||
req: HttpRequest,
|
||||
path: web::Path<(AffiliateCodeId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -350,9 +369,14 @@ pub struct PatchRequest {
|
||||
pub source_name: String,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Update an affiliate code.
|
||||
#[utoipa::path(
|
||||
context_path = "/affiliate",
|
||||
tag = "affiliates",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
async fn patch(
|
||||
pub async fn patch(
|
||||
req: HttpRequest,
|
||||
path: web::Path<(AffiliateCodeId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::error::Context;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(list)
|
||||
.service(update_group)
|
||||
.service(scan)
|
||||
@@ -37,7 +37,7 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
.service(split);
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
struct AttributionGroupResponse {
|
||||
id: crate::models::ids::AttributionGroupId,
|
||||
flame_project: Option<FlameProject>,
|
||||
@@ -48,7 +48,7 @@ struct AttributionGroupResponse {
|
||||
versions: Vec<VersionInfo>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[derive(Clone, Serialize, utoipa::ToSchema)]
|
||||
struct VersionInfo {
|
||||
id: VersionId,
|
||||
name: String,
|
||||
@@ -56,7 +56,7 @@ struct VersionInfo {
|
||||
date_created: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
struct AttributionFileResponse {
|
||||
name: String,
|
||||
sha1: String,
|
||||
@@ -67,7 +67,7 @@ struct AttributionFileResponse {
|
||||
moderation_external_license: Option<ModerationExternalLicenseResponse>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[derive(Clone, Serialize, utoipa::ToSchema)]
|
||||
struct ModerationExternalLicenseResponse {
|
||||
id: i64,
|
||||
title: Option<String>,
|
||||
@@ -92,9 +92,14 @@ struct ScanResponse {
|
||||
queued_files: u64,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Queue an attribution scan.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
responses((status = OK, body = ScanResponse))
|
||||
)]
|
||||
#[post("/scan")]
|
||||
async fn scan(
|
||||
pub async fn scan(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -278,9 +283,17 @@ async fn force_scan_file(
|
||||
Ok(web::Json(scan_summary))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// List project attribution groups.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
params(
|
||||
("project_id" = ProjectId, Path)
|
||||
),
|
||||
responses((status = OK, body = inline(Vec<AttributionGroupResponse>)))
|
||||
)]
|
||||
#[get("/{project_id}")]
|
||||
async fn list(
|
||||
pub async fn list(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -528,9 +541,14 @@ struct UpdateGroupBody {
|
||||
attribution: AttributionResolution,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Update an attribution group.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/group/{group_id}")]
|
||||
async fn update_group(
|
||||
pub async fn update_group(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -609,9 +627,14 @@ struct AssignBody {
|
||||
project_id: ProjectId,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Move a file to an attribution group.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/assign")]
|
||||
async fn assign(
|
||||
pub async fn assign(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -765,9 +788,14 @@ struct SplitBody {
|
||||
project_id: ProjectId,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Split a file into a new attribution group.
|
||||
#[utoipa::path(
|
||||
context_path = "/attribution",
|
||||
tag = "attribution",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/split")]
|
||||
async fn split(
|
||||
pub async fn split(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -42,7 +42,7 @@ use stripe::{
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("/billing")
|
||||
.service(products)
|
||||
@@ -58,11 +58,18 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.service(active_servers)
|
||||
.service(initiate_payment)
|
||||
.service(stripe_webhook)
|
||||
.service(refund_charge),
|
||||
.service(refund_charge)
|
||||
.service(reprocess_charge_tax),
|
||||
);
|
||||
}
|
||||
|
||||
#[get("products")]
|
||||
/// List products.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/products")]
|
||||
pub async fn products(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -99,7 +106,14 @@ struct SubscriptionsQuery {
|
||||
pub user_id: Option<ariadne::ids::UserId>,
|
||||
}
|
||||
|
||||
#[get("subscriptions")]
|
||||
/// List subscriptions.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
params(("user_id" = Option<ariadne::ids::UserId>, Query)),
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/subscriptions")]
|
||||
pub async fn subscriptions(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -141,7 +155,7 @@ pub async fn subscriptions(
|
||||
Ok(HttpResponse::Ok().json(subscriptions))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ChargeRefundAmount {
|
||||
Full,
|
||||
@@ -149,14 +163,21 @@ pub enum ChargeRefundAmount {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ChargeRefund {
|
||||
#[serde(flatten)]
|
||||
pub amount: ChargeRefundAmount,
|
||||
pub unprovision: Option<bool>,
|
||||
}
|
||||
|
||||
#[post("charge/{id}/refund")]
|
||||
/// Refund a charge.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
request_body = ChargeRefund,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/charge/{id}/refund")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn refund_charge(
|
||||
req: HttpRequest,
|
||||
@@ -419,7 +440,13 @@ pub async fn refund_charge(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[post("charge/{id}/tax/reprocess")]
|
||||
/// Reprocess tax for a charge.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/charge/{id}/tax/reprocess")]
|
||||
pub async fn reprocess_charge_tax(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -587,8 +614,9 @@ pub async fn reprocess_charge_tax(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct SubscriptionEdit {
|
||||
#[schema(value_type = String)]
|
||||
pub interval: Option<PriceDuration>,
|
||||
pub payment_method: Option<String>,
|
||||
pub cancelled: Option<bool>,
|
||||
@@ -601,7 +629,17 @@ pub struct SubscriptionEditQuery {
|
||||
pub dry: Option<bool>,
|
||||
}
|
||||
|
||||
#[patch("subscription/{id}")]
|
||||
/// Update a subscription.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
params(("dry" = Option<bool>, Query)),
|
||||
responses(
|
||||
(status = OK, body = serde_json::Value),
|
||||
(status = NO_CONTENT),
|
||||
)
|
||||
)]
|
||||
#[patch("/subscription/{id}")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn edit_subscription(
|
||||
req: HttpRequest,
|
||||
@@ -1091,7 +1129,13 @@ pub async fn edit_subscription(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("customer")]
|
||||
/// Get the current customer.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/customer")]
|
||||
pub async fn user_customer(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1129,7 +1173,14 @@ pub struct ChargesQuery {
|
||||
pub user_id: Option<ariadne::ids::UserId>,
|
||||
}
|
||||
|
||||
#[get("payments")]
|
||||
/// List payments.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
params(("user_id" = Option<ariadne::ids::UserId>, Query)),
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/payments")]
|
||||
pub async fn charges(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1188,7 +1239,13 @@ pub async fn charges(
|
||||
))
|
||||
}
|
||||
|
||||
#[post("payment_method")]
|
||||
/// Start a payment method flow.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[post("/payment_method")]
|
||||
pub async fn add_payment_method_flow(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1241,7 +1298,13 @@ pub struct EditPaymentMethod {
|
||||
pub primary: bool,
|
||||
}
|
||||
|
||||
#[patch("payment_method/{id}")]
|
||||
/// Update a payment method.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/payment_method/{id}")]
|
||||
pub async fn edit_payment_method(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -1305,7 +1368,13 @@ pub async fn edit_payment_method(
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("payment_method/{id}")]
|
||||
/// Remove a payment method.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/payment_method/{id}")]
|
||||
pub async fn remove_payment_method(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -1388,7 +1457,16 @@ pub async fn remove_payment_method(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("payment_methods")]
|
||||
/// List payment methods.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses(
|
||||
(status = OK, body = serde_json::Value),
|
||||
(status = NO_CONTENT),
|
||||
)
|
||||
)]
|
||||
#[get("/payment_methods")]
|
||||
pub async fn payment_methods(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1432,7 +1510,24 @@ pub struct ActiveServersQuery {
|
||||
pub subscription_status: Option<SubscriptionStatus>,
|
||||
}
|
||||
|
||||
#[get("active_servers")]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
struct ActiveServerResponse {
|
||||
pub user_id: ariadne::ids::UserId,
|
||||
pub server_id: String,
|
||||
pub price_id: crate::models::ids::ProductPriceId,
|
||||
#[schema(value_type = String)]
|
||||
pub interval: PriceDuration,
|
||||
pub region: Option<String>,
|
||||
}
|
||||
|
||||
/// List active servers.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
params(("subscription_status" = Option<String>, Query)),
|
||||
responses((status = OK, body = inline(Vec<ActiveServerResponse>)))
|
||||
)]
|
||||
#[get("/active_servers")]
|
||||
pub async fn active_servers(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1457,21 +1552,12 @@ pub async fn active_servers(
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ActiveServer {
|
||||
pub user_id: ariadne::ids::UserId,
|
||||
pub server_id: String,
|
||||
pub price_id: crate::models::ids::ProductPriceId,
|
||||
pub interval: PriceDuration,
|
||||
pub region: Option<String>,
|
||||
}
|
||||
|
||||
let server_ids = servers
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
x.metadata.as_ref().and_then(|metadata| match metadata {
|
||||
SubscriptionMetadata::Pyro { id, region } => {
|
||||
Some(ActiveServer {
|
||||
Some(ActiveServerResponse {
|
||||
user_id: x.user_id.into(),
|
||||
server_id: id.clone(),
|
||||
price_id: x.price_id.into(),
|
||||
@@ -1482,12 +1568,12 @@ pub async fn active_servers(
|
||||
SubscriptionMetadata::Medal { .. } => None,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<ActiveServer>>();
|
||||
.collect::<Vec<ActiveServerResponse>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(server_ids))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum PaymentRequestType {
|
||||
PaymentMethod { id: String },
|
||||
@@ -1507,7 +1593,7 @@ impl PaymentRequestType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ChargeRequestType {
|
||||
Existing {
|
||||
@@ -1515,11 +1601,12 @@ pub enum ChargeRequestType {
|
||||
},
|
||||
New {
|
||||
product_id: crate::models::ids::ProductId,
|
||||
#[schema(value_type = String)]
|
||||
interval: Option<PriceDuration>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PaymentRequestMetadata {
|
||||
#[serde(flatten)]
|
||||
@@ -1527,7 +1614,7 @@ pub struct PaymentRequestMetadata {
|
||||
pub affiliate_code: Option<AffiliateCodeId>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum PaymentRequestMetadataKind {
|
||||
Pyro {
|
||||
@@ -1537,16 +1624,23 @@ pub enum PaymentRequestMetadataKind {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct PaymentRequest {
|
||||
#[serde(flatten)]
|
||||
pub type_: PaymentRequestType,
|
||||
pub charge: ChargeRequestType,
|
||||
#[schema(value_type = String)]
|
||||
pub existing_payment_intent: Option<stripe::PaymentIntentId>,
|
||||
pub metadata: Option<PaymentRequestMetadata>,
|
||||
}
|
||||
|
||||
#[post("payment")]
|
||||
/// Initiate a payment.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[post("/payment")]
|
||||
pub async fn initiate_payment(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1610,7 +1704,14 @@ pub async fn initiate_payment(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("_stripe")]
|
||||
/// Receive a Stripe webhook.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
request_body(content = String, content_type = "text/plain"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/_stripe")]
|
||||
pub async fn stripe_webhook(
|
||||
req: HttpRequest,
|
||||
payload: String,
|
||||
@@ -2503,7 +2604,7 @@ async fn apply_credit_many(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreditRequest {
|
||||
#[serde(flatten)]
|
||||
pub target: CreditTarget,
|
||||
@@ -2512,7 +2613,7 @@ pub struct CreditRequest {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum CreditTarget {
|
||||
Subscriptions {
|
||||
@@ -2526,7 +2627,13 @@ pub enum CreditTarget {
|
||||
},
|
||||
}
|
||||
|
||||
#[post("credit")]
|
||||
/// Credit subscriptions.
|
||||
#[utoipa::path(
|
||||
context_path = "/billing",
|
||||
tag = "billing",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/credit")]
|
||||
pub async fn credit(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::{
|
||||
util::{error::Context, http::HttpClient, tiltify::TiltifyClient},
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(tiltify_webhook).service(pride_26);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,13 @@ impl CampaignDonation {
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Receive a Tiltify webhook.
|
||||
#[utoipa::path(
|
||||
context_path = "/campaign",
|
||||
tag = "campaigns",
|
||||
request_body(content = String, content_type = "text/plain"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/webhook")]
|
||||
pub async fn tiltify_webhook(
|
||||
req: HttpRequest,
|
||||
@@ -301,7 +307,12 @@ fn verify_tiltify_webhook_signature(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get Pride campaign data.
|
||||
#[utoipa::path(
|
||||
context_path = "/campaign",
|
||||
tag = "campaigns",
|
||||
responses((status = OK, body = CampaignInfo))
|
||||
)]
|
||||
#[get("/pride-26")]
|
||||
pub async fn pride_26(
|
||||
http: web::Data<HttpClient>,
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
|
||||
pub mod rescan;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("/delphi")
|
||||
.service(ingest_report)
|
||||
@@ -141,8 +141,14 @@ pub struct DelphiRunParameters {
|
||||
pub file_id: crate::models::ids::FileId,
|
||||
}
|
||||
|
||||
#[post("ingest", guard = "admin_key_guard")]
|
||||
async fn ingest_report(
|
||||
/// Ingest a Delphi report.
|
||||
#[utoipa::path(
|
||||
context_path = "/delphi",
|
||||
tag = "delphi",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/ingest", guard = "admin_key_guard")]
|
||||
pub async fn ingest_report(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
web::Json(report): web::Json<serde_json::Value>,
|
||||
@@ -466,8 +472,15 @@ pub async fn send_tech_review_exit_file_deleted_message_if_exited(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[post("run")]
|
||||
async fn _run(
|
||||
/// Run Delphi.
|
||||
#[utoipa::path(
|
||||
context_path = "/delphi",
|
||||
tag = "delphi",
|
||||
params(("file_id" = crate::models::ids::FileId, Query)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/run")]
|
||||
pub async fn _run(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -487,8 +500,14 @@ async fn _run(
|
||||
run(&**pool, run_parameters.into_inner(), &http).await
|
||||
}
|
||||
|
||||
#[get("version")]
|
||||
async fn version(
|
||||
/// Get the Delphi version.
|
||||
#[utoipa::path(
|
||||
context_path = "/delphi",
|
||||
tag = "delphi",
|
||||
responses((status = OK, body = inline(Option<i32>)))
|
||||
)]
|
||||
#[get("/version")]
|
||||
pub async fn version(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -510,8 +529,14 @@ async fn version(
|
||||
))
|
||||
}
|
||||
|
||||
#[get("issue_type/schema")]
|
||||
async fn issue_type_schema(
|
||||
/// Get the Delphi issue type schema.
|
||||
#[utoipa::path(
|
||||
context_path = "/delphi",
|
||||
tag = "delphi",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/issue_type/schema")]
|
||||
pub async fn issue_type_schema(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -28,14 +28,14 @@ use eyre::eyre;
|
||||
use lettre::message::Mailbox;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(create)
|
||||
.service(create_email_sync)
|
||||
.service(remove)
|
||||
.service(send_custom_email);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, PartialEq, Default)]
|
||||
#[derive(Deserialize, PartialEq, Default, utoipa::ToSchema)]
|
||||
enum EmailStrategy {
|
||||
#[default]
|
||||
Async,
|
||||
@@ -43,8 +43,9 @@ enum EmailStrategy {
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
struct CreateNotification {
|
||||
#[schema(value_type = serde_json::Value)]
|
||||
pub body: NotificationBody,
|
||||
pub user_ids: Vec<UserId>,
|
||||
#[serde(default)]
|
||||
@@ -78,7 +79,12 @@ where
|
||||
error.as_api_error().serialize(serializer)
|
||||
}
|
||||
|
||||
#[post("external_notifications", guard = "external_notification_key_guard")]
|
||||
/// Create external notifications.
|
||||
#[utoipa::path(
|
||||
tag = "external notifications",
|
||||
responses((status = ACCEPTED))
|
||||
)]
|
||||
#[post("/external_notifications", guard = "external_notification_key_guard")]
|
||||
pub async fn create(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -89,6 +95,46 @@ pub async fn create(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Create notifications and send emails.
|
||||
///
|
||||
/// Responds with the user IDs that could not be emailed:
|
||||
/// - `200` if every recipient was emailed (empty list)
|
||||
/// - `207` if some recipients could not be emailed (list of failed IDs)
|
||||
/// Create email sync.
|
||||
#[utoipa::path(
|
||||
tag = "external notifications",
|
||||
responses(
|
||||
(status = OK, body = inline(Vec<UserId>)),
|
||||
(status = 207, body = inline(Vec<UserId>)),
|
||||
)
|
||||
)]
|
||||
#[post(
|
||||
"external_notifications/email-sync",
|
||||
guard = "external_notification_key_guard"
|
||||
)]
|
||||
pub async fn create_email_sync(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
email_queue: web::Data<EmailQueue>,
|
||||
data: web::Json<CreateNotification>,
|
||||
) -> Result<(web::Json<Vec<UserId>>, StatusCode), ApiError> {
|
||||
let data = data.into_inner();
|
||||
create_impl(
|
||||
pool,
|
||||
redis,
|
||||
email_queue,
|
||||
CreateNotification {
|
||||
body: data.body,
|
||||
user_ids: data.user_ids,
|
||||
email: EmailStrategy::Sync,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|(res, code)| {
|
||||
(web::Json(res.into_inner().into_keys().collect()), code)
|
||||
})
|
||||
}
|
||||
|
||||
async fn create_impl(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -224,44 +270,19 @@ async fn create_impl(
|
||||
Ok((web::Json(HashMap::new()), StatusCode::ACCEPTED))
|
||||
}
|
||||
|
||||
/// Inserts notifications for all users and tries to send emails immediately.
|
||||
///
|
||||
/// Responds with the user IDs that could not be emailed and a reason why
|
||||
#[post(
|
||||
"external_notifications/email-sync",
|
||||
guard = "external_notification_key_guard"
|
||||
)]
|
||||
pub async fn create_email_sync(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
email_queue: web::Data<EmailQueue>,
|
||||
data: web::Json<CreateNotification>,
|
||||
) -> Result<(web::Json<Vec<UserId>>, StatusCode), ApiError> {
|
||||
let data = data.into_inner();
|
||||
create_impl(
|
||||
pool,
|
||||
redis,
|
||||
email_queue,
|
||||
CreateNotification {
|
||||
body: data.body,
|
||||
user_ids: data.user_ids,
|
||||
email: EmailStrategy::Sync,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|(res, code)| {
|
||||
(web::Json(res.into_inner().into_keys().collect()), code)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
struct NotificationFilter {
|
||||
pub user_ids: Vec<UserId>,
|
||||
#[serde(flatten)]
|
||||
pub body: serde_json::Map<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[delete("external_notifications", guard = "external_notification_key_guard")]
|
||||
/// Remove external notifications.
|
||||
#[utoipa::path(
|
||||
tag = "external notifications",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/external_notifications", guard = "external_notification_key_guard")]
|
||||
pub async fn remove(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -301,7 +322,7 @@ pub async fn remove(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
struct SendEmail {
|
||||
pub users: Vec<UserId>,
|
||||
pub key: String,
|
||||
@@ -309,7 +330,12 @@ struct SendEmail {
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[post("external_notifications/send_custom_email")]
|
||||
/// Send a custom email.
|
||||
#[utoipa::path(
|
||||
tag = "external notifications",
|
||||
responses((status = ACCEPTED))
|
||||
)]
|
||||
#[post("/external_notifications/send_custom_email")]
|
||||
pub async fn send_custom_email(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -60,9 +60,9 @@ use webauthn_rs::prelude::{
|
||||
};
|
||||
use zxcvbn::Score;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/auth")
|
||||
web::scope("/auth")
|
||||
.service(init)
|
||||
.service(auth_callback)
|
||||
.service(delete_auth_provider)
|
||||
@@ -1068,12 +1068,15 @@ pub struct Authorization {
|
||||
// Init link takes us to GitHub API and calls back to callback endpoint with a code and state
|
||||
// http://localhost:8000/auth/init?url=https://modrinth.com
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "authInit",
|
||||
responses(
|
||||
(status = 307, description = "Redirect to OAuth provider"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
params(
|
||||
("url" = Url, Query),
|
||||
("provider" = Option<AuthProvider>, Query),
|
||||
("token" = Option<String>, Query),
|
||||
("auth_token" = Option<String>, Query)
|
||||
),
|
||||
responses((status = TEMPORARY_REDIRECT), (status = OK))
|
||||
)]
|
||||
#[get("/init")]
|
||||
pub async fn init(
|
||||
@@ -1166,12 +1169,8 @@ pub async fn init(
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
operation_id = "authCallback",
|
||||
responses(
|
||||
(status = 307, description = "Redirect with auth code"),
|
||||
(status = 401, description = "Authentication failed")
|
||||
)
|
||||
context_path = "/auth",
|
||||
tag = "auth", responses((status = OK))
|
||||
)]
|
||||
#[get("/callback")]
|
||||
pub async fn auth_callback(
|
||||
@@ -1441,16 +1440,19 @@ struct NewOAuthAccount {
|
||||
pub sign_up_newsletter: bool,
|
||||
}
|
||||
|
||||
/// Create account with OAuth.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "createOAuthAccount",
|
||||
responses(
|
||||
(status = 200, description = "OAuth account created"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
operation_id = "createOAuthAccount",
|
||||
responses(
|
||||
(status = 200, description = "OAuth account created", body = serde_json::Value),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/create/oauth")]
|
||||
async fn create_oauth_account(
|
||||
pub async fn create_oauth_account(
|
||||
req: HttpRequest,
|
||||
db: Data<PgPool>,
|
||||
file_host: Data<dyn FileHost>,
|
||||
@@ -1525,7 +1527,10 @@ struct DiscordCommunityHandoffPayload {
|
||||
nonce: String,
|
||||
}
|
||||
|
||||
/// Link Discord community.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
operation_id = "discordCommunityLink",
|
||||
responses(
|
||||
(status = 200, description = "Discord community bot handoff URL", body = DiscordCommunityLinkResponse),
|
||||
@@ -1599,7 +1604,10 @@ pub async fn discord_community_link(
|
||||
Ok(web::Json(DiscordCommunityLinkResponse { url }))
|
||||
}
|
||||
|
||||
/// Remove an auth provider.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
delete,
|
||||
operation_id = "deleteAuthProvider",
|
||||
responses(
|
||||
@@ -1939,11 +1947,14 @@ impl ReadyAccountRegisterFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate password account creation.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "validateCreateAccountWithPassword",
|
||||
responses(
|
||||
(status = 200, description = "Account input is valid"),
|
||||
(status = NO_CONTENT, description = "Account input is valid"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
@@ -1964,13 +1975,16 @@ pub async fn validate_create_account_with_password(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create account with a password.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "createAccountPassword",
|
||||
responses(
|
||||
(status = 200, description = "Account created"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
operation_id = "createAccountPassword",
|
||||
responses(
|
||||
(status = 200, description = "Account created", body = serde_json::Value),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/create")]
|
||||
pub async fn create_account_with_password(
|
||||
@@ -2010,13 +2024,16 @@ pub struct Login {
|
||||
pub challenge: String,
|
||||
}
|
||||
|
||||
/// Log in with a password.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "loginPassword",
|
||||
responses(
|
||||
(status = 200, description = "Login successful"),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
operation_id = "loginPassword",
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = serde_json::Value),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
)]
|
||||
#[post("/login")]
|
||||
pub async fn login_password(
|
||||
@@ -2168,13 +2185,16 @@ async fn validate_2fa_code(
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete login with 2FA.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "login2fa",
|
||||
responses(
|
||||
(status = 200, description = "2FA login successful"),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
operation_id = "login2fa",
|
||||
responses(
|
||||
(status = 200, description = "2FA login successful", body = serde_json::Value),
|
||||
(status = 401, description = "Invalid credentials")
|
||||
)
|
||||
)]
|
||||
#[post("/login/2fa")]
|
||||
pub async fn login_2fa(
|
||||
@@ -2225,13 +2245,16 @@ pub async fn login_2fa(
|
||||
}
|
||||
}
|
||||
|
||||
/// Start 2FA setup.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "begin2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA secret generated"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "begin2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA secret generated", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/2fa/get_secret")]
|
||||
@@ -2273,13 +2296,16 @@ pub async fn begin_2fa_flow(
|
||||
}
|
||||
}
|
||||
|
||||
/// Finish 2FA setup.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "finish2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA enabled"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "finish2faFlow",
|
||||
responses(
|
||||
(status = 200, description = "2FA enabled", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[post("/2fa")]
|
||||
@@ -2405,7 +2431,10 @@ pub struct Remove2FA {
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
/// Remove 2FA.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
delete,
|
||||
operation_id = "remove2fa",
|
||||
responses(
|
||||
@@ -2502,7 +2531,10 @@ pub struct ResetPassword {
|
||||
pub challenge: String,
|
||||
}
|
||||
|
||||
/// Start password reset.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "resetPasswordBegin",
|
||||
responses(
|
||||
@@ -2605,7 +2637,10 @@ pub struct ChangePassword {
|
||||
pub new_password: Option<String>,
|
||||
}
|
||||
|
||||
/// Change password.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
patch,
|
||||
operation_id = "changePassword",
|
||||
responses(
|
||||
@@ -2768,7 +2803,10 @@ pub struct SetEmail {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
/// Set email address.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
patch,
|
||||
operation_id = "setEmail",
|
||||
responses(
|
||||
@@ -2887,7 +2925,10 @@ pub async fn set_email(
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Resend verification email.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "resendVerifyEmail",
|
||||
responses(
|
||||
@@ -2959,7 +3000,10 @@ pub struct VerifyEmail {
|
||||
pub flow: String,
|
||||
}
|
||||
|
||||
/// Verify email address.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "verifyEmail",
|
||||
responses(
|
||||
@@ -3022,7 +3066,10 @@ pub async fn verify_email(
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribe to the newsletter.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "subscribeNewsletter",
|
||||
responses(
|
||||
@@ -3068,13 +3115,16 @@ pub async fn subscribe_newsletter(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
/// Get newsletter subscription status.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
get,
|
||||
operation_id = "getNewsletterSubscriptionStatus",
|
||||
responses(
|
||||
(status = 200, description = "Subscription status"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "getNewsletterSubscriptionStatus",
|
||||
responses(
|
||||
(status = 200, description = "Subscription status", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
#[get("/email/subscribe")]
|
||||
@@ -3115,7 +3165,10 @@ pub struct RegisterPasskeyResponse {
|
||||
pub flow: String,
|
||||
}
|
||||
|
||||
/// Start passkey registration.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "registerPasskeyStart",
|
||||
responses(
|
||||
@@ -3212,7 +3265,10 @@ pub struct PasskeyResponse {
|
||||
pub last_used: Option<chrono::DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Finish passkey registration.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "registerPasskeyFinish",
|
||||
responses(
|
||||
@@ -3318,7 +3374,10 @@ pub struct AuthenticatePasskeyResponse {
|
||||
pub flow: String,
|
||||
}
|
||||
|
||||
/// Start passkey authentication.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "authenticatePasskeyStart",
|
||||
responses(
|
||||
@@ -3358,13 +3417,16 @@ pub struct AuthenticatePasskeyFinish {
|
||||
pub credential: PublicKeyCredential,
|
||||
}
|
||||
|
||||
/// Finish passkey authentication.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
post,
|
||||
operation_id = "authenticatePasskeyFinish",
|
||||
responses(
|
||||
(status = 200, description = "Passkey authentication successful"),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
operation_id = "authenticatePasskeyFinish",
|
||||
responses(
|
||||
(status = 200, description = "Passkey authentication successful", body = serde_json::Value),
|
||||
(status = 400, description = "Invalid input")
|
||||
)
|
||||
)]
|
||||
#[post("/passkey/finish")]
|
||||
pub async fn authenticate_passkey_finish(
|
||||
@@ -3472,7 +3534,10 @@ pub async fn authenticate_passkey_finish(
|
||||
}
|
||||
}
|
||||
|
||||
/// List passkeys.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
get,
|
||||
operation_id = "listPasskeys",
|
||||
responses(
|
||||
@@ -3519,7 +3584,10 @@ pub struct RenamePasskey {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Rename a passkey.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
patch,
|
||||
operation_id = "renamePasskey",
|
||||
responses(
|
||||
@@ -3571,7 +3639,10 @@ pub async fn rename_passkey(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
/// Delete a passkey.
|
||||
#[utoipa::path(
|
||||
context_path = "/auth",
|
||||
tag = "auth",
|
||||
delete,
|
||||
operation_id = "deletePasskey",
|
||||
responses(
|
||||
|
||||
@@ -6,10 +6,16 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(web::scope("/gdpr").service(export));
|
||||
}
|
||||
|
||||
/// Export GDPR data.
|
||||
#[utoipa::path(
|
||||
context_path = "/gdpr",
|
||||
tag = "GDPR",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[post("/export")]
|
||||
pub async fn export(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -10,7 +10,7 @@ use chrono::{Datelike, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_globals);
|
||||
}
|
||||
|
||||
@@ -89,8 +89,12 @@ pub fn tax_compliance_payout_threshold_for_year(
|
||||
value
|
||||
}
|
||||
|
||||
/// Gets configured global non-secret variables for this backend instance.
|
||||
#[utoipa::path]
|
||||
/// Get backend globals.
|
||||
#[utoipa::path(
|
||||
context_path = "/globals",
|
||||
tag = "globals",
|
||||
responses((status = OK, body = Globals))
|
||||
)]
|
||||
#[get("")]
|
||||
pub async fn get_globals() -> web::Json<Globals> {
|
||||
web::Json(GLOBALS.clone())
|
||||
|
||||
@@ -28,6 +28,12 @@ pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(success_callback).service(error_callback);
|
||||
}
|
||||
|
||||
/// Receive a Gotenberg success callback.
|
||||
#[utoipa::path(
|
||||
tag = "gotenberg",
|
||||
request_body = Vec<u8>,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/gotenberg/success", guard = "internal_network_guard")]
|
||||
pub async fn success_callback(
|
||||
web::Header(header::ContentDisposition {
|
||||
@@ -82,7 +88,7 @@ pub async fn success_callback(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Error)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Error, utoipa::ToSchema)]
|
||||
pub struct GotenbergError {
|
||||
pub status: Option<String>,
|
||||
pub message: Option<String>,
|
||||
@@ -101,6 +107,11 @@ impl fmt::Display for GotenbergError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Receive a Gotenberg error callback.
|
||||
#[utoipa::path(
|
||||
tag = "gotenberg",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/gotenberg/error", guard = "internal_network_guard")]
|
||||
pub async fn error_callback(
|
||||
web::Header(GotenbergTrace(trace)): web::Header<GotenbergTrace>,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::queue::billing::try_process_user_redeemal;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::guards::medal_key_guard;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(web::scope("/medal").service(verify).service(redeem));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,19 @@ struct MedalQuery {
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[post("verify", guard = "medal_key_guard")]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
struct VerifyResponse {
|
||||
user_id: UserId,
|
||||
redeemed: bool,
|
||||
}
|
||||
|
||||
/// Verify Medal credentials.
|
||||
#[utoipa::path(
|
||||
context_path = "/medal",
|
||||
tag = "medal",
|
||||
responses((status = OK, body = VerifyResponse))
|
||||
)]
|
||||
#[post("/verify", guard = "medal_key_guard")]
|
||||
pub async fn verify(
|
||||
pool: web::Data<PgPool>,
|
||||
web::Query(MedalQuery { username }): web::Query<MedalQuery>,
|
||||
@@ -35,12 +47,6 @@ pub async fn verify(
|
||||
)
|
||||
.await?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct VerifyResponse {
|
||||
user_id: UserId,
|
||||
redeemed: bool,
|
||||
}
|
||||
|
||||
match maybe_fields {
|
||||
None => Err(ApiError::NotFound),
|
||||
Some(fields) => Ok(HttpResponse::Ok().json(VerifyResponse {
|
||||
@@ -50,7 +56,13 @@ pub async fn verify(
|
||||
}
|
||||
}
|
||||
|
||||
#[post("redeem", guard = "medal_key_guard")]
|
||||
/// Redeem Medal credit.
|
||||
#[utoipa::path(
|
||||
context_path = "/medal",
|
||||
tag = "medal",
|
||||
responses((status = ACCEPTED), (status = CREATED))
|
||||
)]
|
||||
#[post("/redeem", guard = "medal_key_guard")]
|
||||
pub async fn redeem(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -19,99 +19,172 @@ pub mod session;
|
||||
pub mod statuses;
|
||||
|
||||
pub use super::ApiError;
|
||||
use super::SecurityAddon;
|
||||
use super::v3::oauth_clients;
|
||||
use crate::util::cors::default_cors;
|
||||
use actix_web::web;
|
||||
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
actix_web::web::scope("/_internal")
|
||||
web::scope("/_internal")
|
||||
.wrap(default_cors())
|
||||
.configure(|cfg| {
|
||||
cfg.service(
|
||||
actix_web::web::scope("/admin")
|
||||
.service(admin::count_download)
|
||||
.service(admin::force_reindex)
|
||||
.service(admin::force_reindex_project),
|
||||
);
|
||||
cfg.service(
|
||||
actix_web::web::scope("/session")
|
||||
.service(session::list)
|
||||
.service(session::delete)
|
||||
.service(session::refresh),
|
||||
);
|
||||
cfg.service(
|
||||
actix_web::web::scope("/auth")
|
||||
.service(flows::init)
|
||||
.service(flows::auth_callback)
|
||||
.service(flows::delete_auth_provider)
|
||||
.service(flows::create_account_with_password)
|
||||
.service(flows::login_password)
|
||||
.service(flows::login_2fa)
|
||||
.service(flows::begin_2fa_flow)
|
||||
.service(flows::finish_2fa_flow)
|
||||
.service(flows::remove_2fa)
|
||||
.service(flows::reset_password_begin)
|
||||
.service(flows::change_password)
|
||||
.service(flows::resend_verify_email)
|
||||
.service(flows::set_email)
|
||||
.service(flows::verify_email)
|
||||
.service(flows::subscribe_newsletter)
|
||||
.service(flows::get_newsletter_subscription_status)
|
||||
.service(flows::discord_community_link),
|
||||
);
|
||||
cfg.service(pats::get_pats);
|
||||
cfg.service(pats::create_pat);
|
||||
cfg.service(pats::edit_pat);
|
||||
cfg.service(pats::delete_pat);
|
||||
})
|
||||
.configure(admin::config)
|
||||
.configure(session::config)
|
||||
.configure(flows::config)
|
||||
.configure(pats::config)
|
||||
.configure(oauth_clients::config)
|
||||
.service(web::scope("/moderation").configure(moderation::config))
|
||||
.service(web::scope("/affiliate").configure(affiliate::config))
|
||||
.service(web::scope("/campaign").configure(campaign::config))
|
||||
.service(web::scope("/search-management").configure(search::config))
|
||||
.service(web::scope("/globals").configure(globals::config))
|
||||
.service(web::scope("/server-ping").configure(server_ping::config))
|
||||
.service(web::scope("/attribution").configure(attribution::config))
|
||||
.configure(billing::config)
|
||||
.configure(delphi::config)
|
||||
.configure(external_notifications::config)
|
||||
.configure(gdpr::config)
|
||||
.configure(gotenberg::config)
|
||||
.configure(statuses::config)
|
||||
.configure(medal::config)
|
||||
.configure(external_notifications::config)
|
||||
.configure(mural::config)
|
||||
.configure(delphi::config),
|
||||
.configure(statuses::config),
|
||||
)
|
||||
.service(
|
||||
web::scope("/v3/analytics-event")
|
||||
.wrap(default_cors())
|
||||
.configure(super::v3::analytics_event::config),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn utoipa_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/_internal/moderation")
|
||||
.wrap(default_cors())
|
||||
.configure(moderation::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/affiliate")
|
||||
.wrap(default_cors())
|
||||
.configure(affiliate::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/campaign")
|
||||
.wrap(default_cors())
|
||||
.configure(campaign::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/search-management")
|
||||
.wrap(default_cors())
|
||||
.configure(search::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/globals")
|
||||
.wrap(default_cors())
|
||||
.configure(globals::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/server-ping")
|
||||
.wrap(default_cors())
|
||||
.configure(server_ping::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/_internal/attribution")
|
||||
.wrap(default_cors())
|
||||
.configure(attribution::config),
|
||||
);
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
info(
|
||||
title = "Internal API (UNSTABLE)",
|
||||
version = "internal",
|
||||
description = include_str!("../../api_internal_description.md"),
|
||||
),
|
||||
paths(
|
||||
admin::count_download,
|
||||
admin::force_reindex,
|
||||
admin::force_reindex_project,
|
||||
session::list,
|
||||
session::delete,
|
||||
session::refresh,
|
||||
flows::init,
|
||||
flows::auth_callback,
|
||||
flows::create_oauth_account,
|
||||
flows::discord_community_link,
|
||||
flows::delete_auth_provider,
|
||||
flows::validate_create_account_with_password,
|
||||
flows::create_account_with_password,
|
||||
flows::login_password,
|
||||
flows::login_2fa,
|
||||
flows::begin_2fa_flow,
|
||||
flows::finish_2fa_flow,
|
||||
flows::remove_2fa,
|
||||
flows::reset_password_begin,
|
||||
flows::change_password,
|
||||
flows::set_email,
|
||||
flows::resend_verify_email,
|
||||
flows::verify_email,
|
||||
flows::subscribe_newsletter,
|
||||
flows::get_newsletter_subscription_status,
|
||||
flows::register_passkey_start,
|
||||
flows::register_passkey_finish,
|
||||
flows::authenticate_passkey_start,
|
||||
flows::authenticate_passkey_finish,
|
||||
flows::list_passkeys,
|
||||
flows::rename_passkey,
|
||||
flows::delete_passkey,
|
||||
pats::get_pats,
|
||||
pats::create_pat,
|
||||
pats::edit_pat,
|
||||
pats::delete_pat,
|
||||
moderation::get_projects,
|
||||
moderation::get_project_meta,
|
||||
moderation::set_project_meta,
|
||||
moderation::acquire_lock,
|
||||
moderation::override_lock,
|
||||
moderation::get_lock_status,
|
||||
moderation::release_lock,
|
||||
moderation::release_lock_beacon,
|
||||
moderation::delete_all_locks,
|
||||
moderation::tech_review::get_issue,
|
||||
moderation::tech_review::get_report,
|
||||
moderation::tech_review::search_projects,
|
||||
moderation::tech_review::get_project_report,
|
||||
moderation::tech_review::submit_report,
|
||||
moderation::tech_review::update_issue_details,
|
||||
moderation::tech_review::add_report,
|
||||
moderation::external_license::search,
|
||||
moderation::external_license::lookup,
|
||||
moderation::external_license::get_by_sha1,
|
||||
moderation::external_license::get_by_sha1_bulk,
|
||||
moderation::external_license::add_file,
|
||||
moderation::external_license::reassign_file,
|
||||
moderation::external_license::update_license,
|
||||
affiliate::ingest_click,
|
||||
affiliate::get_all,
|
||||
affiliate::create,
|
||||
affiliate::get,
|
||||
affiliate::delete,
|
||||
affiliate::patch,
|
||||
campaign::tiltify_webhook,
|
||||
campaign::pride_26,
|
||||
search::tasks,
|
||||
search::tasks_cancel,
|
||||
globals::get_globals,
|
||||
server_ping::ping_minecraft_java,
|
||||
attribution::scan,
|
||||
attribution::list,
|
||||
attribution::update_group,
|
||||
attribution::assign,
|
||||
attribution::split,
|
||||
billing::products,
|
||||
billing::subscriptions,
|
||||
billing::refund_charge,
|
||||
billing::reprocess_charge_tax,
|
||||
billing::edit_subscription,
|
||||
billing::user_customer,
|
||||
billing::charges,
|
||||
billing::add_payment_method_flow,
|
||||
billing::edit_payment_method,
|
||||
billing::remove_payment_method,
|
||||
billing::payment_methods,
|
||||
billing::active_servers,
|
||||
billing::initiate_payment,
|
||||
billing::stripe_webhook,
|
||||
billing::credit,
|
||||
delphi::ingest_report,
|
||||
delphi::_run,
|
||||
delphi::version,
|
||||
delphi::issue_type_schema,
|
||||
external_notifications::create,
|
||||
external_notifications::create_email_sync,
|
||||
external_notifications::remove,
|
||||
external_notifications::send_custom_email,
|
||||
gdpr::export,
|
||||
gotenberg::success_callback,
|
||||
gotenberg::error_callback,
|
||||
medal::verify,
|
||||
medal::redeem,
|
||||
mural::get_bank_details,
|
||||
statuses::ws_init,
|
||||
super::v3::analytics_event::analytics_events_get,
|
||||
super::v3::analytics_event::analytics_event_create,
|
||||
super::v3::analytics_event::analytics_event_edit,
|
||||
super::v3::analytics_event::analytics_event_delete,
|
||||
),
|
||||
modifiers(&InternalPathModifier, &SecurityAddon)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
struct InternalPathModifier;
|
||||
|
||||
impl utoipa::Modify for InternalPathModifier {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
super::prefix_openapi_paths(openapi, "/_internal", |path| {
|
||||
path.starts_with("/v3/")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::queue::moderation::ApprovalType;
|
||||
use crate::routes::ApiError;
|
||||
use crate::{auth::check_is_moderator_from_headers, queue::session::AuthQueue};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(search)
|
||||
.service(get_by_sha1)
|
||||
.service(get_by_sha1_bulk)
|
||||
@@ -329,9 +329,14 @@ async fn fetch_by_flame_ids(
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Search external licenses.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = inline(Vec<ExternalProject>)))
|
||||
)]
|
||||
#[post("/search")]
|
||||
async fn search(
|
||||
pub async fn search(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -393,9 +398,14 @@ async fn search(
|
||||
Ok(web::Json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Look up external license metadata.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalLicenseLookupResponse))
|
||||
)]
|
||||
#[post("/lookup")]
|
||||
async fn lookup(
|
||||
pub async fn lookup(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -422,9 +432,14 @@ async fn lookup(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get external license by SHA-1.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[get("/by-sha1/{sha1}")]
|
||||
async fn get_by_sha1(
|
||||
pub async fn get_by_sha1(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -448,9 +463,14 @@ async fn get_by_sha1(
|
||||
Ok(web::Json(result))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get external licenses by SHA-1.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = inline(HashMap<String, ExternalProject>)))
|
||||
)]
|
||||
#[post("/by-sha1")]
|
||||
async fn get_by_sha1_bulk(
|
||||
pub async fn get_by_sha1_bulk(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -472,9 +492,14 @@ async fn get_by_sha1_bulk(
|
||||
Ok(web::Json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Add an external license file.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[post("/file")]
|
||||
async fn add_file(
|
||||
pub async fn add_file(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -484,9 +509,14 @@ async fn add_file(
|
||||
upsert_file_license(req, pool, redis, session_queue, body).await
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Reassign an external license file.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[post("/file/reassign")]
|
||||
async fn reassign_file(
|
||||
pub async fn reassign_file(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -584,9 +614,14 @@ async fn upsert_file_license(
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Update an external license.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/external-license",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = ExternalProject))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
async fn update_license(
|
||||
pub async fn update_license(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -21,11 +21,11 @@ use ownership::get_projects_ownership;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod external_license;
|
||||
pub mod external_license;
|
||||
mod ownership;
|
||||
mod tech_review;
|
||||
pub mod tech_review;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_projects)
|
||||
.service(get_project_meta)
|
||||
.service(set_project_meta)
|
||||
@@ -35,13 +35,9 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
.service(release_lock)
|
||||
.service(release_lock_beacon)
|
||||
.service(delete_all_locks)
|
||||
.service(web::scope("/tech-review").configure(tech_review::config))
|
||||
.service(
|
||||
utoipa_actix_web::scope("/tech-review")
|
||||
.configure(tech_review::config),
|
||||
)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/external-license")
|
||||
.configure(external_license::config),
|
||||
web::scope("/external-license").configure(external_license::config),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -162,12 +158,19 @@ pub struct DeleteAllLocksResponse {
|
||||
pub deleted_count: u64,
|
||||
}
|
||||
|
||||
/// Fetch all projects which are in the moderation queue.
|
||||
/// List projects in the moderation queue.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
params(
|
||||
("count" = Option<u16>, Query),
|
||||
("offset" = Option<u32>, Query),
|
||||
("has_external_dependencies" = Option<bool>, Query)
|
||||
),
|
||||
responses((status = OK, body = inline(Vec<FetchedProject>)))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
async fn get_projects(
|
||||
pub async fn get_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -291,12 +294,14 @@ pub async fn get_projects_internal(
|
||||
Ok(web::Json(projects))
|
||||
}
|
||||
|
||||
/// Fetch moderation metadata for a specific project.
|
||||
/// Get project moderation metadata.
|
||||
#[utoipa::path(
|
||||
responses((status = OK, body = inline(Vec<Project>)))
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = MissingMetadata))
|
||||
)]
|
||||
#[get("/project/{id}")]
|
||||
async fn get_project_meta(
|
||||
pub async fn get_project_meta(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -447,10 +452,14 @@ pub enum Judgement {
|
||||
},
|
||||
}
|
||||
|
||||
/// Update moderation judgements for projects in the review queue.
|
||||
#[utoipa::path]
|
||||
/// Update project moderation judgements.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/project")]
|
||||
async fn set_project_meta(
|
||||
pub async fn set_project_meta(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -536,16 +545,18 @@ async fn set_project_meta(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Acquire or refresh a moderation lock on a project.
|
||||
/// Acquire a moderation lock.
|
||||
/// Returns success if acquired, or info about who holds the lock if blocked.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockAcquireResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}")]
|
||||
async fn acquire_lock(
|
||||
pub async fn acquire_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -594,15 +605,17 @@ async fn acquire_lock(
|
||||
}
|
||||
}
|
||||
|
||||
/// Force-acquire a moderation lock on a project (moderator override).
|
||||
/// Override a moderation lock.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockAcquireResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}/override")]
|
||||
async fn override_lock(
|
||||
pub async fn override_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -639,15 +652,17 @@ async fn override_lock(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Check the lock status for a project
|
||||
/// Get moderation lock status.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockStatusResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[get("/lock/{project_id}")]
|
||||
async fn get_lock_status(
|
||||
pub async fn get_lock_status(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -699,15 +714,17 @@ async fn get_lock_status(
|
||||
}
|
||||
}
|
||||
|
||||
/// Release a moderation lock on a project
|
||||
/// Release a moderation lock.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = LockReleaseResponse),
|
||||
(status = NOT_FOUND, description = "Project not found")
|
||||
)
|
||||
)]
|
||||
#[delete("/lock/{project_id}")]
|
||||
async fn release_lock(
|
||||
pub async fn release_lock(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -740,12 +757,14 @@ async fn release_lock(
|
||||
Ok(web::Json(LockReleaseResponse { success: released }))
|
||||
}
|
||||
|
||||
/// Release a moderation lock using credentials in the request body.
|
||||
/// Release a moderation lock by beacon.
|
||||
///
|
||||
/// For use with `navigator.sendBeacon`, which cannot set `Authorization` or send `DELETE`.
|
||||
/// The body must be `text/plain` containing the same token value as the `Authorization` header
|
||||
/// (optional `Bearer ` prefix). This avoids a CORS preflight compared to `application/json`.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
request_body(
|
||||
content = String,
|
||||
description = "Token value (same as Authorization header)",
|
||||
@@ -757,7 +776,7 @@ async fn release_lock(
|
||||
)
|
||||
)]
|
||||
#[post("/lock/{project_id}/release")]
|
||||
async fn release_lock_beacon(
|
||||
pub async fn release_lock_beacon(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -811,15 +830,17 @@ async fn release_lock_beacon(
|
||||
Ok(web::Json(LockReleaseResponse { success: released }))
|
||||
}
|
||||
|
||||
/// Delete all moderation locks (admin only)
|
||||
/// Delete all moderation locks.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "moderation",
|
||||
responses(
|
||||
(status = OK, body = DeleteAllLocksResponse),
|
||||
(status = UNAUTHORIZED, description = "Not an admin")
|
||||
)
|
||||
)]
|
||||
#[delete("/locks")]
|
||||
async fn delete_all_locks(
|
||||
pub async fn delete_all_locks(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::{
|
||||
};
|
||||
use eyre::eyre;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(search_projects)
|
||||
.service(get_project_report)
|
||||
.service(get_report)
|
||||
@@ -193,13 +193,15 @@ pub enum FlagReason {
|
||||
Delphi,
|
||||
}
|
||||
|
||||
/// Get info on an issue in a Delphi report.
|
||||
/// Get a Delphi report issue.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(FileIssue)))
|
||||
)]
|
||||
#[get("/issue/{issue_id}")]
|
||||
async fn get_issue(
|
||||
pub async fn get_issue(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -252,13 +254,15 @@ async fn get_issue(
|
||||
Ok(web::Json(row.data.0))
|
||||
}
|
||||
|
||||
/// Get info on a specific report for a project.
|
||||
/// Get a project technical report.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(FileReport)))
|
||||
)]
|
||||
#[get("/report/{id}")]
|
||||
async fn get_report(
|
||||
pub async fn get_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -662,13 +666,15 @@ async fn fetch_project_reports(
|
||||
Ok(project_reports)
|
||||
}
|
||||
|
||||
/// Searches all projects which are awaiting technical review.
|
||||
/// Search projects awaiting technical review.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(Vec<SearchResponse>)))
|
||||
responses((status = OK, body = SearchResponse))
|
||||
)]
|
||||
#[post("/search")]
|
||||
async fn search_projects(
|
||||
pub async fn search_projects(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -872,13 +878,15 @@ async fn search_projects(
|
||||
}))
|
||||
}
|
||||
|
||||
/// Gets the technical review report for a specific project.
|
||||
/// Get a project technical review report.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = OK, body = inline(ProjectReportResponse)))
|
||||
)]
|
||||
#[get("/project/{id}")]
|
||||
async fn get_project_report(
|
||||
pub async fn get_project_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -963,18 +971,20 @@ pub struct SubmitReport {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
/// Submits a verdict for a project based on its technical reports.
|
||||
/// Submit a technical review verdict.
|
||||
///
|
||||
/// Before this is called, all issues for this project's reports must have been
|
||||
/// marked as either safe or unsafe. Otherwise, this will error with
|
||||
/// [`ApiError::TechReviewIssuesWithNoVerdict`], providing the issue IDs which
|
||||
/// are still unmarked.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/submit/{project_id}")]
|
||||
async fn submit_report(
|
||||
pub async fn submit_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -1163,16 +1173,18 @@ pub struct UpdateIssue {
|
||||
pub verdict: DelphiVerdict,
|
||||
}
|
||||
|
||||
/// Updates the state of a technical review issue detail.
|
||||
/// Update technical review issue details.
|
||||
///
|
||||
/// This will not automatically reject the project for malware, but just flag
|
||||
/// this issue with a verdict.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
security(("bearer_auth" = [])),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/issue-detail")]
|
||||
async fn update_issue_details(
|
||||
pub async fn update_issue_details(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -1273,11 +1285,15 @@ pub struct AddReport {
|
||||
pub file_id: FileId,
|
||||
}
|
||||
|
||||
/// Adds a file to the technical review queue by adding an empty report, if one
|
||||
/// Add a technical review report.
|
||||
/// does not already exist for it.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation/tech-review",
|
||||
tag = "moderation",
|
||||
responses((status = OK, body = DelphiReportId))
|
||||
)]
|
||||
#[put("/report")]
|
||||
async fn add_report(
|
||||
pub async fn add_report(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -6,12 +6,17 @@ use crate::{
|
||||
queue::payouts::PayoutsQueue, routes::ApiError, util::error::Context,
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_bank_details);
|
||||
}
|
||||
|
||||
/// Get bank details.
|
||||
#[utoipa::path(
|
||||
tag = "mural",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/mural/bank-details")]
|
||||
async fn get_bank_details(
|
||||
pub async fn get_bank_details(
|
||||
payouts_queue: web::Data<PayoutsQueue>,
|
||||
) -> Result<web::Json<muralpay::BankDetailsResponse>, ApiError> {
|
||||
let mural = payouts_queue.muralpay.load();
|
||||
|
||||
@@ -22,20 +22,22 @@ use crate::util::validate::validation_errors_to_string;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_pats);
|
||||
cfg.service(create_pat);
|
||||
cfg.service(edit_pat);
|
||||
cfg.service(delete_pat);
|
||||
}
|
||||
|
||||
/// List personal access tokens.
|
||||
#[utoipa::path(
|
||||
tag = "personal access tokens",
|
||||
get,
|
||||
operation_id = "getPats",
|
||||
responses(
|
||||
(status = 200, description = "List of PATs"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "getPats",
|
||||
responses(
|
||||
(status = 200, description = "List of PATs", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_READ"]))
|
||||
)]
|
||||
#[get("/pat")]
|
||||
@@ -82,14 +84,16 @@ pub struct NewPersonalAccessToken {
|
||||
pub expires: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Create a personal access token.
|
||||
#[utoipa::path(
|
||||
tag = "personal access tokens",
|
||||
post,
|
||||
operation_id = "createPat",
|
||||
responses(
|
||||
(status = 200, description = "PAT created"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "createPat",
|
||||
responses(
|
||||
(status = 200, description = "PAT created", body = serde_json::Value),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["PAT_CREATE"]))
|
||||
)]
|
||||
#[post("/pat")]
|
||||
@@ -185,10 +189,14 @@ pub struct ModifyPersonalAccessToken {
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Update a personal access token.
|
||||
#[utoipa::path(
|
||||
tag = "personal access tokens",
|
||||
patch,
|
||||
operation_id = "editPat",
|
||||
params(("id" = String, Path, description = "The PAT ID")),
|
||||
params(
|
||||
("id" = String, Path, description = "The PAT ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "PAT updated"),
|
||||
(status = 400, description = "Invalid input"),
|
||||
@@ -293,10 +301,14 @@ pub async fn edit_pat(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
/// Delete a personal access token.
|
||||
#[utoipa::path(
|
||||
tag = "personal access tokens",
|
||||
delete,
|
||||
operation_id = "deletePat",
|
||||
params(("id" = String, Path, description = "The PAT ID")),
|
||||
params(
|
||||
("id" = String, Path, description = "The PAT ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "PAT deleted"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
|
||||
@@ -5,20 +5,30 @@ use crate::{
|
||||
};
|
||||
use actix_web::{delete, get, web};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(tasks).service(tasks_cancel);
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
#[get("tasks", guard = "admin_key_guard")]
|
||||
/// List search tasks.
|
||||
#[utoipa::path(
|
||||
context_path = "/search-management",
|
||||
tag = "search",
|
||||
responses((status = OK, body = serde_json::Value))
|
||||
)]
|
||||
#[get("/tasks", guard = "admin_key_guard")]
|
||||
pub async fn tasks(
|
||||
search: web::Data<dyn SearchBackend>,
|
||||
) -> Result<web::Json<serde_json::Value>, ApiError> {
|
||||
Ok(web::Json(search.tasks().await.map_err(ApiError::Internal)?))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
#[delete("tasks", guard = "admin_key_guard")]
|
||||
/// Cancel search tasks.
|
||||
#[utoipa::path(
|
||||
context_path = "/search-management",
|
||||
tag = "search",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/tasks", guard = "admin_key_guard")]
|
||||
pub async fn tasks_cancel(
|
||||
search: web::Data<dyn SearchBackend>,
|
||||
body: web::Json<TasksCancelFilter>,
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::{
|
||||
util::error::Context,
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(ping_minecraft_java);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ pub struct PingRequest {
|
||||
pub timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Ping Minecraft server.
|
||||
#[utoipa::path(
|
||||
context_path = "/server-ping",
|
||||
tag = "server ping",
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/minecraft-java")]
|
||||
pub async fn ping_minecraft_java(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -19,9 +19,9 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use woothee::parser::Parser;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/session")
|
||||
web::scope("/session")
|
||||
.service(list)
|
||||
.service(delete)
|
||||
.service(refresh),
|
||||
@@ -133,13 +133,16 @@ pub async fn issue_session(
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
/// List sessions.
|
||||
#[utoipa::path(
|
||||
context_path = "/session",
|
||||
tag = "sessions",
|
||||
get,
|
||||
operation_id = "listSessions",
|
||||
responses(
|
||||
(status = 200, description = "List of active sessions"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
operation_id = "listSessions",
|
||||
responses(
|
||||
(status = 200, description = "List of active sessions", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
),
|
||||
security(("bearer_auth" = ["SESSION_READ"]))
|
||||
)]
|
||||
#[get("/list")]
|
||||
@@ -178,10 +181,15 @@ pub async fn list(
|
||||
Ok(HttpResponse::Ok().json(sessions))
|
||||
}
|
||||
|
||||
/// Delete a session.
|
||||
#[utoipa::path(
|
||||
context_path = "/session",
|
||||
tag = "sessions",
|
||||
delete,
|
||||
operation_id = "deleteSession",
|
||||
params(("id" = String, Path, description = "The session ID")),
|
||||
params(
|
||||
("id" = String, Path, description = "The session ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Session deleted"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
@@ -228,13 +236,16 @@ pub async fn delete(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
/// Refresh a session.
|
||||
#[utoipa::path(
|
||||
context_path = "/session",
|
||||
tag = "sessions",
|
||||
post,
|
||||
operation_id = "refreshSession",
|
||||
responses(
|
||||
(status = 200, description = "Session refreshed"),
|
||||
(status = 401, description = "Unauthorized")
|
||||
)
|
||||
operation_id = "refreshSession",
|
||||
responses(
|
||||
(status = 200, description = "Session refreshed", body = serde_json::Value),
|
||||
(status = 401, description = "Unauthorized")
|
||||
)
|
||||
)]
|
||||
#[post("/refresh")]
|
||||
pub async fn refresh(
|
||||
|
||||
@@ -35,7 +35,7 @@ use std::sync::atomic::Ordering;
|
||||
use tokio::sync::oneshot::error::TryRecvError;
|
||||
use tokio::time::{Duration, sleep};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(ws_init);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,12 @@ struct LauncherHeartbeatInit {
|
||||
}
|
||||
|
||||
// TODO: Move launcher-specific tunnel traffic to a proper launcher websocket endpoint.
|
||||
#[get("launcher_socket")]
|
||||
/// Start launcher socket.
|
||||
#[utoipa::path(
|
||||
tag = "statuses",
|
||||
responses((status = 101))
|
||||
)]
|
||||
#[get("/launcher_socket")]
|
||||
pub async fn ws_init(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
|
||||
@@ -18,7 +18,7 @@ use quick_xml::escape::escape;
|
||||
use std::collections::HashSet;
|
||||
use yaserde::YaSerialize;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(maven_metadata);
|
||||
cfg.service(version_file_sha512);
|
||||
cfg.service(version_file_sha1);
|
||||
@@ -70,7 +70,12 @@ pub struct MavenPom {
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[get("maven/modrinth/{id}/maven-metadata.xml")]
|
||||
#[utoipa::path(
|
||||
tag = "maven",
|
||||
params(("id" = String, Path)),
|
||||
responses((status = OK, body = String, content_type = "text/xml"))
|
||||
)]
|
||||
#[get("/maven/modrinth/{id}/maven-metadata.xml")]
|
||||
pub async fn maven_metadata(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String,)>,
|
||||
@@ -279,8 +284,20 @@ fn find_file<'a>(
|
||||
None
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "maven",
|
||||
params(
|
||||
("id" = String, Path),
|
||||
("versionnum" = String, Path),
|
||||
("file" = String, Path)
|
||||
),
|
||||
responses(
|
||||
(status = OK, body = String, content_type = "text/xml"),
|
||||
(status = TEMPORARY_REDIRECT)
|
||||
)
|
||||
)]
|
||||
#[route(
|
||||
"maven/modrinth/{id}/{versionnum}/{file}",
|
||||
"/maven/modrinth/{id}/{versionnum}/{file}",
|
||||
method = "GET",
|
||||
method = "HEAD"
|
||||
)]
|
||||
@@ -349,7 +366,16 @@ pub async fn version_file(
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
|
||||
#[get("maven/modrinth/{id}/{versionnum}/{file}.sha1")]
|
||||
#[utoipa::path(
|
||||
tag = "maven",
|
||||
params(
|
||||
("id" = String, Path),
|
||||
("versionnum" = String, Path),
|
||||
("file" = String, Path)
|
||||
),
|
||||
responses((status = OK, body = String))
|
||||
)]
|
||||
#[get("/maven/modrinth/{id}/{versionnum}/{file}.sha1")]
|
||||
pub async fn version_file_sha1(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String, String, String)>,
|
||||
@@ -396,7 +422,16 @@ pub async fn version_file_sha1(
|
||||
))
|
||||
}
|
||||
|
||||
#[get("maven/modrinth/{id}/{versionnum}/{file}.sha512")]
|
||||
#[utoipa::path(
|
||||
tag = "maven",
|
||||
params(
|
||||
("id" = String, Path),
|
||||
("versionnum" = String, Path),
|
||||
("file" = String, Path)
|
||||
),
|
||||
responses((status = OK, body = String))
|
||||
)]
|
||||
#[get("/maven/modrinth/{id}/{versionnum}/{file}.sha512")]
|
||||
pub async fn version_file_sha512(
|
||||
req: HttpRequest,
|
||||
params: web::Path<(String, String, String)>,
|
||||
|
||||
+119
-19
@@ -8,6 +8,8 @@ use actix_web::http::StatusCode;
|
||||
use actix_web::{HttpResponse, web};
|
||||
use futures::FutureExt;
|
||||
use serde_json::json;
|
||||
use utoipa::openapi::extensions::ExtensionsBuilder;
|
||||
use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme};
|
||||
|
||||
pub mod debug;
|
||||
pub mod internal;
|
||||
@@ -23,7 +25,124 @@ mod updates;
|
||||
|
||||
pub use self::not_found::not_found;
|
||||
|
||||
// utoipa-specific struct to use a value_type for docs.
|
||||
/// A sha1 or sha512 hash.
|
||||
pub struct FileHash;
|
||||
|
||||
impl utoipa::PartialSchema for FileHash {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
utoipa::openapi::ObjectBuilder::new()
|
||||
.schema_type(utoipa::openapi::schema::Type::String)
|
||||
.min_length(Some(40))
|
||||
.max_length(Some(128))
|
||||
.pattern(Some("^([A-Fa-f0-9]{40}|[A-Fa-f0-9]{128})$"))
|
||||
.examples([serde_json::json!(
|
||||
"2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"
|
||||
)])
|
||||
.description(Some("A sha1 or sha512 hash."))
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for FileHash {}
|
||||
|
||||
// utoipa-specific struct to use a value_type for docs.
|
||||
/// A hashing algorithm (sha1 or sha256)
|
||||
pub struct HashAlgorithm;
|
||||
|
||||
impl utoipa::PartialSchema for HashAlgorithm {
|
||||
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
|
||||
utoipa::openapi::ObjectBuilder::new()
|
||||
.enum_values(Some([
|
||||
serde_json::json!("sha1"),
|
||||
serde_json::json!("sha512"),
|
||||
]))
|
||||
.examples([serde_json::json!("sha1")])
|
||||
.description(Some("A supported hashing algorithm."))
|
||||
.build()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl utoipa::ToSchema for HashAlgorithm {}
|
||||
|
||||
pub(crate) fn prefix_openapi_paths(
|
||||
openapi: &mut utoipa::openapi::OpenApi,
|
||||
prefix: &str,
|
||||
should_skip: impl Fn(&str) -> bool,
|
||||
) {
|
||||
let paths = std::mem::take(&mut openapi.paths.paths);
|
||||
openapi.paths.paths = paths
|
||||
.into_iter()
|
||||
.map(|(path, item)| {
|
||||
if should_skip(&path) || path.starts_with(prefix) {
|
||||
(path, item)
|
||||
} else {
|
||||
(format!("{prefix}{}", normalize_openapi_path(&path)), item)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn normalize_openapi_path(path: &str) -> String {
|
||||
if path.starts_with('/') {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("/{path}")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SecurityAddon;
|
||||
|
||||
impl utoipa::Modify for SecurityAddon {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
let components = openapi.components.as_mut().unwrap();
|
||||
let mut bearer_auth = HttpBuilder::new()
|
||||
.scheme(HttpAuthScheme::Bearer)
|
||||
.description(Some(
|
||||
"Use a personal access token. Example: `mrp_RNtLRSPmGj2pd1v1ubi52nX7TJJM9sznrmwhAuj511oe4t1jAqAQ3D6Wc8Ic`.",
|
||||
))
|
||||
.build();
|
||||
bearer_auth.extensions = Some(
|
||||
ExtensionsBuilder::new()
|
||||
.add(
|
||||
"x-example",
|
||||
"mrp_RNtLRSPmGj2pd1v1ubi52nX7TJJM9sznrmwhAuj511oe4t1jAqAQ3D6Wc8Ic",
|
||||
)
|
||||
.build(),
|
||||
);
|
||||
|
||||
components.add_security_scheme(
|
||||
"bearer_auth",
|
||||
SecurityScheme::Http(bearer_auth),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("/api/v1")
|
||||
.wrap(default_cors())
|
||||
.wrap_fn(|req, _srv| {
|
||||
async {
|
||||
Ok(req.into_response(
|
||||
HttpResponse::Gone()
|
||||
.content_type("application/json")
|
||||
.body(r#"{"error":"api_deprecated","description":"You are using an application that uses an outdated version of Modrinth's API. Please either update it or switch to another application. For developers: https://docs.modrinth.com/api/#versioning"}"#)
|
||||
))
|
||||
}.boxed_local()
|
||||
})
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("")
|
||||
.wrap(default_cors())
|
||||
.service(index::index_get)
|
||||
.service(Files::new("/", "assets/")),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn public_config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("/maven")
|
||||
.wrap(default_cors())
|
||||
@@ -58,25 +177,6 @@ pub fn root_config(cfg: &mut web::ServiceConfig) {
|
||||
)
|
||||
.configure(analytics::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("/api/v1")
|
||||
.wrap(default_cors())
|
||||
.wrap_fn(|req, _srv| {
|
||||
async {
|
||||
Ok(req.into_response(
|
||||
HttpResponse::Gone()
|
||||
.content_type("application/json")
|
||||
.body(r#"{"error":"api_deprecated","description":"You are using an application that uses an outdated version of Modrinth's API. Please either update it or switch to another application. For developers: https://docs.modrinth.com/api/#versioning"}"#)
|
||||
))
|
||||
}.boxed_local()
|
||||
})
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("")
|
||||
.wrap(default_cors())
|
||||
.service(index::index_get)
|
||||
.service(Files::new("/", "assets/")),
|
||||
);
|
||||
}
|
||||
|
||||
/// Error when calling an HTTP endpoint.
|
||||
|
||||
@@ -17,11 +17,11 @@ use crate::queue::session::AuthQueue;
|
||||
|
||||
use super::ApiError;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(forge_updates);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct NeoForge {
|
||||
#[serde(default = "default_neoforge")]
|
||||
pub neoforge: String,
|
||||
@@ -31,7 +31,16 @@ fn default_neoforge() -> String {
|
||||
"none".into()
|
||||
}
|
||||
|
||||
#[get("{id}/forge_updates.json")]
|
||||
#[utoipa::path(
|
||||
context_path = "/updates",
|
||||
tag = "updates",
|
||||
params(
|
||||
("id" = String, Path),
|
||||
("neoforge" = Option<String>, Query)
|
||||
),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/{id}/forge_updates.json")]
|
||||
pub async fn forge_updates(
|
||||
req: HttpRequest,
|
||||
web::Query(neo): web::Query<NeoForge>,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod moderation;
|
||||
pub(crate) mod moderation;
|
||||
mod notifications;
|
||||
mod openapi;
|
||||
pub(crate) mod project_creation;
|
||||
mod projects;
|
||||
mod reports;
|
||||
@@ -14,17 +15,17 @@ mod versions;
|
||||
|
||||
pub use super::ApiError;
|
||||
use crate::util::cors::default_cors;
|
||||
use actix_web::web;
|
||||
pub use openapi::ApiDoc;
|
||||
|
||||
pub fn utoipa_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
) {
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/v2")
|
||||
web::scope("/v2")
|
||||
.wrap(default_cors())
|
||||
.configure(super::internal::admin::config)
|
||||
.configure(super::internal::session::config)
|
||||
.configure(super::internal::flows::config)
|
||||
.configure(super::internal::pats::config)
|
||||
.configure(super::internal::admin::config)
|
||||
.configure(moderation::config)
|
||||
.configure(notifications::config)
|
||||
.configure(project_creation::config)
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::{database::redis::RedisPool, routes::v2_reroute};
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(utoipa_actix_web::scope("/moderation").service(get_projects));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(web::scope("/moderation").service(get_projects));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -22,19 +22,17 @@ fn default_count() -> u16 {
|
||||
100
|
||||
}
|
||||
|
||||
/// Get projects in the moderation queue.
|
||||
/// List projects in the moderation queue.
|
||||
#[utoipa::path(
|
||||
context_path = "/moderation",
|
||||
tag = "v2 moderation",
|
||||
get,
|
||||
operation_id = "getModerationProjects",
|
||||
params(
|
||||
(
|
||||
"count" = Option<u16>,
|
||||
Query,
|
||||
description = "Maximum number of projects to return"
|
||||
)
|
||||
("count" = Option<u16>, Query, description = "Maximum number of projects to return")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
|
||||
@@ -10,12 +10,12 @@ use crate::routes::v3;
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(notifications_get);
|
||||
cfg.service(notifications_delete);
|
||||
cfg.service(notifications_read);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/notification")
|
||||
web::scope("/notification")
|
||||
.service(notification_get)
|
||||
.service(notification_read)
|
||||
.service(notification_delete),
|
||||
@@ -27,19 +27,16 @@ pub struct NotificationIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
/// Get multiple notifications by ID.
|
||||
/// Get multiple notifications by ID.
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
get,
|
||||
operation_id = "getNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of notification IDs")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyNotification>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -80,13 +77,17 @@ pub async fn notifications_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a notification by ID.
|
||||
/// Get a notification by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/notification",
|
||||
tag = "notifications",
|
||||
get,
|
||||
operation_id = "getNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
params(
|
||||
("id" = NotificationId, Path, description = "The ID of the notification")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyNotification),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -124,11 +125,15 @@ pub async fn notification_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a notification as read.
|
||||
/// Mark a notification as read.
|
||||
#[utoipa::path(
|
||||
context_path = "/notification",
|
||||
tag = "notifications",
|
||||
patch,
|
||||
operation_id = "readNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
params(
|
||||
("id" = NotificationId, Path, description = "The ID of the notification")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
@@ -156,11 +161,15 @@ pub async fn notification_read(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a notification by ID.
|
||||
/// Delete a notification by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/notification",
|
||||
tag = "notifications",
|
||||
delete,
|
||||
operation_id = "deleteNotification",
|
||||
params(("id" = NotificationId, Path, description = "The ID of the notification")),
|
||||
params(
|
||||
("id" = NotificationId, Path, description = "The ID of the notification")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
@@ -194,16 +203,13 @@ pub async fn notification_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Mark multiple notifications as read.
|
||||
/// Mark multiple notifications as read.
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
patch,
|
||||
operation_id = "readNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of notification IDs")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -238,16 +244,13 @@ pub async fn notifications_read(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete multiple notifications by ID.
|
||||
/// Delete multiple notifications by ID.
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
delete,
|
||||
operation_id = "deleteNotifications",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of notification IDs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of notification IDs")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
use crate::routes::SecurityAddon;
|
||||
use crate::routes::prefix_openapi_paths;
|
||||
use crate::routes::v2::{
|
||||
notifications, project_creation, projects, reports, statistics, tags,
|
||||
teams, threads, users, version_creation, version_file, versions,
|
||||
};
|
||||
|
||||
use utoipa::openapi::extensions::{Extensions, ExtensionsBuilder};
|
||||
use utoipa::openapi::path::{Operation, PathItem};
|
||||
use utoipa::openapi::schema::{AdditionalProperties, AllOf, Schema};
|
||||
use utoipa::openapi::{RefOr, Response};
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
info(
|
||||
title = "Modrinth API v2",
|
||||
version = "2.0.0",
|
||||
description = include_str!("../../api_v2_description.md"),
|
||||
),
|
||||
paths(
|
||||
notifications::notifications_get,
|
||||
notifications::notifications_read,
|
||||
notifications::notifications_delete,
|
||||
notifications::notification_get,
|
||||
notifications::notification_read,
|
||||
notifications::notification_delete,
|
||||
project_creation::project_create,
|
||||
projects::project_search,
|
||||
projects::random_projects_get,
|
||||
projects::projects_get,
|
||||
projects::projects_edit,
|
||||
projects::project_get,
|
||||
projects::project_get_check,
|
||||
projects::project_edit,
|
||||
projects::project_icon_edit,
|
||||
projects::delete_project_icon,
|
||||
projects::add_gallery_item,
|
||||
projects::edit_gallery_item,
|
||||
projects::delete_gallery_item,
|
||||
projects::project_delete,
|
||||
projects::project_follow,
|
||||
projects::project_unfollow,
|
||||
projects::dependency_list,
|
||||
reports::report_create,
|
||||
reports::reports,
|
||||
reports::reports_get,
|
||||
reports::report_get,
|
||||
reports::report_edit,
|
||||
reports::report_delete,
|
||||
statistics::get_stats,
|
||||
tags::category_list,
|
||||
tags::loader_list,
|
||||
tags::game_version_list,
|
||||
tags::license_list,
|
||||
tags::license_text,
|
||||
tags::donation_platform_list,
|
||||
tags::report_type_list,
|
||||
tags::project_type_list,
|
||||
tags::side_type_list,
|
||||
teams::teams_get,
|
||||
teams::team_members_get_project,
|
||||
teams::team_members_get,
|
||||
teams::join_team,
|
||||
teams::add_team_member,
|
||||
teams::edit_team_member,
|
||||
teams::transfer_ownership,
|
||||
teams::remove_team_member,
|
||||
threads::threads_get,
|
||||
threads::thread_get,
|
||||
threads::thread_send_message,
|
||||
threads::message_delete,
|
||||
users::users_get,
|
||||
users::user_auth_get,
|
||||
users::user_get,
|
||||
users::projects_list,
|
||||
users::user_edit,
|
||||
users::user_icon_edit,
|
||||
users::user_icon_delete,
|
||||
users::user_delete,
|
||||
users::user_follows,
|
||||
users::user_notifications,
|
||||
version_file::get_version_from_hash,
|
||||
version_file::download_version,
|
||||
version_file::delete_file,
|
||||
version_file::get_update_from_hash,
|
||||
version_file::get_projects_from_hashes,
|
||||
version_file::get_versions_from_hashes,
|
||||
version_file::update_files,
|
||||
version_file::update_files_many,
|
||||
version_file::update_individual_files,
|
||||
versions::versions_get,
|
||||
version_creation::version_create,
|
||||
versions::version_get,
|
||||
versions::version_edit,
|
||||
versions::version_delete,
|
||||
versions::version_list,
|
||||
versions::version_project_get,
|
||||
version_creation::upload_file_to_version,
|
||||
),
|
||||
modifiers(&V2PathModifier, &SecurityAddon, &V2Modifier)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
const FILE_HASH_EXAMPLE: &str = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed";
|
||||
const FILE_HASH_MAP_RESPONSE_OPERATION_IDS: [&str; 5] = [
|
||||
"versionsFromHashes",
|
||||
"projectsFromHashes",
|
||||
"getLatestVersionsFromHashes",
|
||||
"getLatestVersionsFromHashesMany",
|
||||
"getLatestVersionsFromHashesIndividual",
|
||||
];
|
||||
|
||||
struct V2PathModifier;
|
||||
|
||||
impl utoipa::Modify for V2PathModifier {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
prefix_openapi_paths(openapi, "/v2", |_| false);
|
||||
}
|
||||
}
|
||||
|
||||
struct V2Modifier;
|
||||
|
||||
impl utoipa::Modify for V2Modifier {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
// We need to add an extension field to some routes to give a clearer example of a file hash in responses. Else,
|
||||
// it'll render as "additionalProperty"
|
||||
for path_item in openapi.paths.paths.values_mut() {
|
||||
add_file_hash_map_response_extension_to_path_item(path_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_file_hash_map_response_extension_to_path_item(path_item: &mut PathItem) {
|
||||
add_file_hash_map_response_extension(&mut path_item.get);
|
||||
add_file_hash_map_response_extension(&mut path_item.put);
|
||||
add_file_hash_map_response_extension(&mut path_item.post);
|
||||
add_file_hash_map_response_extension(&mut path_item.delete);
|
||||
add_file_hash_map_response_extension(&mut path_item.options);
|
||||
add_file_hash_map_response_extension(&mut path_item.head);
|
||||
add_file_hash_map_response_extension(&mut path_item.patch);
|
||||
add_file_hash_map_response_extension(&mut path_item.trace);
|
||||
}
|
||||
|
||||
fn add_file_hash_map_response_extension(operation: &mut Option<Operation>) {
|
||||
let Some(operation) = operation else {
|
||||
return;
|
||||
};
|
||||
let Some(operation_id) = operation.operation_id.as_deref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !FILE_HASH_MAP_RESPONSE_OPERATION_IDS.contains(&operation_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(RefOr::T(response)) = operation.responses.responses.get_mut("200")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
add_file_hash_map_response_extension_to_response(response);
|
||||
}
|
||||
|
||||
fn add_file_hash_map_response_extension_to_response(response: &mut Response) {
|
||||
let Some(content) = response.content.get_mut("application/json") else {
|
||||
return;
|
||||
};
|
||||
let Some(RefOr::T(Schema::Object(schema))) = content.schema.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let Some(additional_properties) = schema.additional_properties.as_mut()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
match additional_properties.as_mut() {
|
||||
AdditionalProperties::RefOr(RefOr::T(schema)) => {
|
||||
add_file_hash_map_response_extension_to_schema(schema);
|
||||
}
|
||||
AdditionalProperties::RefOr(RefOr::Ref(reference)) => {
|
||||
let mut all_of = AllOf::new();
|
||||
all_of.items.push(RefOr::Ref(reference.clone()));
|
||||
let mut schema = Schema::AllOf(all_of);
|
||||
add_file_hash_map_response_extension_to_schema(&mut schema);
|
||||
**additional_properties =
|
||||
AdditionalProperties::RefOr(RefOr::T(schema));
|
||||
}
|
||||
AdditionalProperties::FreeForm(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_file_hash_map_response_extension_to_schema(schema: &mut Schema) {
|
||||
match schema {
|
||||
Schema::Array(schema) => {
|
||||
add_file_hash_map_response_extension_to_extensions(
|
||||
&mut schema.extensions,
|
||||
);
|
||||
}
|
||||
Schema::Object(schema) => {
|
||||
add_file_hash_map_response_extension_to_extensions(
|
||||
&mut schema.extensions,
|
||||
);
|
||||
}
|
||||
Schema::OneOf(schema) => {
|
||||
add_file_hash_map_response_extension_to_extensions(
|
||||
&mut schema.extensions,
|
||||
);
|
||||
}
|
||||
Schema::AllOf(schema) => {
|
||||
add_file_hash_map_response_extension_to_extensions(
|
||||
&mut schema.extensions,
|
||||
);
|
||||
}
|
||||
Schema::AnyOf(schema) => {
|
||||
add_file_hash_map_response_extension_to_extensions(
|
||||
&mut schema.extensions,
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_file_hash_map_response_extension_to_extensions(
|
||||
extensions: &mut Option<Extensions>,
|
||||
) {
|
||||
let file_hash_extension = ExtensionsBuilder::new()
|
||||
.add("x-additionalPropertiesName", FILE_HASH_EXAMPLE)
|
||||
.build();
|
||||
|
||||
if let Some(extensions) = extensions {
|
||||
extensions.merge(file_hash_extension);
|
||||
} else {
|
||||
*extensions = Some(file_hash_extension);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ use validator::Validate;
|
||||
|
||||
use super::version_creation::InitialVersionData;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(project_create);
|
||||
}
|
||||
|
||||
@@ -134,8 +134,9 @@ struct ProjectCreateData {
|
||||
pub organization_id: Option<models::ids::OrganizationId>,
|
||||
}
|
||||
|
||||
/// Create a new project with initial versions.
|
||||
/// Create a new project with initial versions.
|
||||
#[utoipa::path(
|
||||
tag = "project creation",
|
||||
post,
|
||||
operation_id = "createProject",
|
||||
request_body(
|
||||
@@ -143,7 +144,7 @@ struct ProjectCreateData {
|
||||
description = "Multipart payload containing `data` and uploaded files"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyProject),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
|
||||
@@ -19,13 +19,13 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(project_search);
|
||||
cfg.service(projects_get);
|
||||
cfg.service(projects_edit);
|
||||
cfg.service(random_projects_get);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/project")
|
||||
web::scope("/project")
|
||||
.service(project_get)
|
||||
.service(project_get_check)
|
||||
.service(project_delete)
|
||||
@@ -39,7 +39,7 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
.service(project_unfollow)
|
||||
.service(super::teams::team_members_get_project)
|
||||
.service(
|
||||
utoipa_actix_web::scope("/{project_id}")
|
||||
web::scope("/{project_id}")
|
||||
.service(super::versions::version_list)
|
||||
.service(super::versions::version_project_get)
|
||||
.service(dependency_list),
|
||||
@@ -47,39 +47,25 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Search projects.
|
||||
/// Search projects.
|
||||
#[utoipa::path(
|
||||
tag = "search",
|
||||
get,
|
||||
operation_id = "searchProjects",
|
||||
params(
|
||||
(
|
||||
"query" = Option<String>,
|
||||
Query,
|
||||
description = "The query to search for"
|
||||
),
|
||||
(
|
||||
"facets" = Option<String>,
|
||||
Query,
|
||||
description = "Search facets JSON"
|
||||
),
|
||||
(
|
||||
"index" = Option<String>,
|
||||
Query,
|
||||
description = "Search index to use"
|
||||
),
|
||||
(
|
||||
"offset" = Option<String>,
|
||||
Query,
|
||||
description = "Search result offset"
|
||||
),
|
||||
(
|
||||
"limit" = Option<String>,
|
||||
Query,
|
||||
description = "Maximum number of search results"
|
||||
)
|
||||
("query" = Option<String>, Query, description = "The query to search for"),
|
||||
("facets" = Option<String>, Query, description = "Search facets JSON"),
|
||||
("index" = Option<String>, Query, description = "Search index to use"),
|
||||
("offset" = Option<String>, Query, description = "Search result offset"),
|
||||
("limit" = Option<String>, Query, description = "Maximum number of search results"),
|
||||
("show_metadata" = Option<bool>, Query, description = "Whether to include search metadata"),
|
||||
("typesense_config" = Option<String>, Query, description = "Typesense request configuration"),
|
||||
("new_filters" = Option<String>, Query, description = "Search filters"),
|
||||
("filters" = Option<String>, Query, description = "Legacy search filters"),
|
||||
("version" = Option<String>, Query, description = "Legacy search version")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacySearchResults),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -180,19 +166,16 @@ pub struct RandomProjects {
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
/// Get random projects.
|
||||
/// Get random projects.
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
get,
|
||||
operation_id = "randomProjects",
|
||||
params(
|
||||
(
|
||||
"count" = u32,
|
||||
Query,
|
||||
description = "Number of projects to return"
|
||||
)
|
||||
("count" = u32, Query, description = "Number of projects to return")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -223,18 +206,15 @@ pub async fn random_projects_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get multiple projects by ID or slug.
|
||||
/// Get multiple projects by ID or slug.
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
get,
|
||||
operation_id = "getProjects",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of project IDs or slugs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of project IDs or slugs")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
pub async fn projects_get(
|
||||
@@ -267,13 +247,17 @@ pub async fn projects_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a project by ID or slug.
|
||||
/// Get a project by ID or slug.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
get,
|
||||
operation_id = "getProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyProject),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -316,13 +300,17 @@ pub async fn project_get(
|
||||
}
|
||||
|
||||
//checks the validity of a project id or slug
|
||||
/// Check that a project ID or slug exists.
|
||||
/// Check that a project ID or slug exists.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
get,
|
||||
operation_id = "checkProjectValidity",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = v3::projects::ProjectCheckResponse),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -347,13 +335,17 @@ struct DependencyInfo {
|
||||
pub versions: Vec<LegacyVersion>,
|
||||
}
|
||||
|
||||
/// Get dependency projects and versions for a project.
|
||||
/// Get dependency projects and versions for a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project/{project_id}",
|
||||
tag = "projects",
|
||||
get,
|
||||
operation_id = "getDependencies",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = DependencyInfo),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -507,14 +499,18 @@ pub struct EditProject {
|
||||
pub monetization_status: Option<MonetizationStatus>,
|
||||
}
|
||||
|
||||
/// Modify a project.
|
||||
/// Update a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
patch,
|
||||
operation_id = "modifyProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
request_body = EditProject,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -763,20 +759,17 @@ pub struct BulkEditProject {
|
||||
pub discord_url: Option<Option<String>>,
|
||||
}
|
||||
|
||||
/// Bulk-edit multiple projects.
|
||||
/// Bulk-edit multiple projects.
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
patch,
|
||||
operation_id = "patchProjects",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of project IDs or slugs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of project IDs or slugs")
|
||||
),
|
||||
request_body = BulkEditProject,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -889,17 +882,15 @@ pub struct Extension {
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
/// Change a project's icon.
|
||||
/// Change a project's icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
patch,
|
||||
operation_id = "changeProjectIcon",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
)
|
||||
("ext" = String, Query, description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)")
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
@@ -912,7 +903,7 @@ pub struct Extension {
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
),
|
||||
security(("bearer_auth" = ["PROJECT_WRITE"]))
|
||||
@@ -946,13 +937,17 @@ pub async fn project_icon_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a project's icon.
|
||||
/// Delete a project's icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
delete,
|
||||
operation_id = "deleteProjectIcon",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -995,37 +990,19 @@ pub struct GalleryCreateQuery {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
/// Add a gallery image to a project.
|
||||
/// Add a gallery image to a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
post,
|
||||
operation_id = "addGalleryImage",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
),
|
||||
(
|
||||
"featured" = bool,
|
||||
Query,
|
||||
description = "Whether this image is featured"
|
||||
),
|
||||
(
|
||||
"title" = Option<String>,
|
||||
Query,
|
||||
description = "Image title"
|
||||
),
|
||||
(
|
||||
"description" = Option<String>,
|
||||
Query,
|
||||
description = "Image description"
|
||||
),
|
||||
(
|
||||
"ordering" = Option<i64>,
|
||||
Query,
|
||||
description = "Image ordering"
|
||||
)
|
||||
("ext" = String, Query, description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"),
|
||||
("featured" = bool, Query, description = "Whether this image is featured"),
|
||||
("title" = Option<String>, Query, description = "Image title"),
|
||||
("description" = Option<String>, Query, description = "Image description"),
|
||||
("ordering" = Option<i64>, Query, description = "Image ordering")
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
@@ -1038,7 +1015,7 @@ pub struct GalleryCreateQuery {
|
||||
)
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -1109,36 +1086,22 @@ pub struct GalleryEditQuery {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
/// Modify a gallery image.
|
||||
/// Update a gallery image.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
patch,
|
||||
operation_id = "modifyGalleryImage",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project"),
|
||||
("url" = String, Query, description = "URL of the image to edit"),
|
||||
(
|
||||
"featured" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether this image is featured"
|
||||
),
|
||||
(
|
||||
"title" = Option<Option<String>>,
|
||||
Query,
|
||||
description = "Image title"
|
||||
),
|
||||
(
|
||||
"description" = Option<Option<String>>,
|
||||
Query,
|
||||
description = "Image description"
|
||||
),
|
||||
(
|
||||
"ordering" = Option<i64>,
|
||||
Query,
|
||||
description = "Image ordering"
|
||||
)
|
||||
("featured" = Option<bool>, Query, description = "Whether this image is featured"),
|
||||
("title" = Option<Option<String>>, Query, description = "Image title"),
|
||||
("description" = Option<Option<String>>, Query, description = "Image description"),
|
||||
("ordering" = Option<i64>, Query, description = "Image ordering")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -1183,8 +1146,10 @@ pub struct GalleryDeleteQuery {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
/// Delete a gallery image.
|
||||
/// Delete a gallery image.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
delete,
|
||||
operation_id = "deleteGalleryImage",
|
||||
params(
|
||||
@@ -1192,7 +1157,7 @@ pub struct GalleryDeleteQuery {
|
||||
("url" = String, Query, description = "URL of the image to delete")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -1225,13 +1190,17 @@ pub async fn delete_gallery_item(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a project by ID or slug.
|
||||
/// Delete a project by ID or slug.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
delete,
|
||||
operation_id = "deleteProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -1263,13 +1232,17 @@ pub async fn project_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Follow a project.
|
||||
/// Follow a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
post,
|
||||
operation_id = "followProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -1292,13 +1265,17 @@ pub async fn project_follow(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Unfollow a project.
|
||||
/// Unfollow a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
delete,
|
||||
operation_id = "unfollowProject",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
|
||||
@@ -8,7 +8,7 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(reports_get);
|
||||
cfg.service(reports);
|
||||
cfg.service(report_create);
|
||||
@@ -17,12 +17,14 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(report_get);
|
||||
}
|
||||
|
||||
/// Create a report for a project, version, or user.
|
||||
/// Create a report for a project, version, or user.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
post,
|
||||
operation_id = "submitReport",
|
||||
request_body = v3::reports::CreateReport,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyReport),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -69,19 +71,16 @@ fn default_all() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Get open reports for the current user.
|
||||
/// Get open reports for the current user.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
get,
|
||||
operation_id = "getOpenReports",
|
||||
params(
|
||||
(
|
||||
"count" = Option<u16>,
|
||||
Query,
|
||||
description = "Maximum number of reports to return"
|
||||
)
|
||||
("count" = Option<u16>, Query, description = "Maximum number of reports to return")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyReport>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -131,19 +130,16 @@ pub struct ReportIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
/// Get multiple reports by ID.
|
||||
/// Get multiple reports by ID.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
get,
|
||||
operation_id = "getReports",
|
||||
params(
|
||||
(
|
||||
"ids" = String,
|
||||
Query,
|
||||
description = "The JSON array of report IDs"
|
||||
)
|
||||
("ids" = String, Query, description = "The JSON array of report IDs")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyReport>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -184,13 +180,16 @@ pub async fn reports_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a report by ID.
|
||||
/// Get a report by ID.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
get,
|
||||
operation_id = "getReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
params(
|
||||
("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyReport),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -232,11 +231,14 @@ pub struct EditReport {
|
||||
pub closed: Option<bool>,
|
||||
}
|
||||
|
||||
/// Modify a report.
|
||||
/// Update a report.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
patch,
|
||||
operation_id = "modifyReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
params(
|
||||
("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")
|
||||
),
|
||||
request_body = EditReport,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -278,11 +280,14 @@ pub async fn report_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a report by ID.
|
||||
/// Delete a report by ID.
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
delete,
|
||||
operation_id = "deleteReport",
|
||||
params(("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")),
|
||||
params(
|
||||
("id" = crate::models::ids::ReportId, Path, description = "The ID of the report")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::routes::{
|
||||
};
|
||||
use actix_web::{HttpResponse, get, web};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_stats);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ pub struct V2Stats {
|
||||
pub files: Option<i64>,
|
||||
}
|
||||
|
||||
/// Get aggregate instance statistics.
|
||||
/// Get aggregate instance statistics.
|
||||
#[utoipa::path(
|
||||
tag = "statistics",
|
||||
get,
|
||||
operation_id = "statistics",
|
||||
responses(
|
||||
|
||||
@@ -12,9 +12,9 @@ use actix_web::{HttpResponse, get, web};
|
||||
use chrono::{DateTime, Utc};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/tag")
|
||||
web::scope("/tag")
|
||||
.service(category_list)
|
||||
.service(loader_list)
|
||||
.service(game_version_list)
|
||||
@@ -35,8 +35,10 @@ pub struct CategoryData {
|
||||
pub header: String,
|
||||
}
|
||||
|
||||
/// Get the list of project categories.
|
||||
/// List project categories.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "categoryList",
|
||||
responses(
|
||||
@@ -81,8 +83,10 @@ pub struct LoaderData {
|
||||
pub supported_project_types: Vec<String>,
|
||||
}
|
||||
|
||||
/// Get the list of loaders.
|
||||
/// List loaders.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "loaderList",
|
||||
responses(
|
||||
@@ -155,21 +159,15 @@ pub struct GameVersionQuery {
|
||||
major: Option<bool>,
|
||||
}
|
||||
|
||||
/// Get the list of game versions.
|
||||
/// List game versions.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "versionList",
|
||||
params(
|
||||
(
|
||||
"type" = Option<String>,
|
||||
Query,
|
||||
description = "Optional game version type filter"
|
||||
),
|
||||
(
|
||||
"major" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether to return only major versions"
|
||||
)
|
||||
("type" = Option<String>, Query, description = "Optional game version type filter"),
|
||||
("major" = Option<bool>, Query, description = "Whether to return only major versions")
|
||||
),
|
||||
responses(
|
||||
(
|
||||
@@ -239,8 +237,10 @@ pub struct License {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Get SPDX license identifiers and names.
|
||||
/// List SPDX license identifiers and names.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "licenseList",
|
||||
responses(
|
||||
@@ -278,11 +278,15 @@ pub struct LicenseText {
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
/// Get full license text by SPDX ID.
|
||||
/// Get full license text by SPDX ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "licenseText",
|
||||
params(("id" = String, Path, description = "The license ID to get the text for")),
|
||||
params(
|
||||
("id" = String, Path, description = "The license ID to get the text for")
|
||||
),
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
@@ -325,8 +329,10 @@ pub struct DonationPlatformQueryData {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Get available donation platforms.
|
||||
/// List donation platforms.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "donationPlatformList",
|
||||
responses(
|
||||
@@ -383,8 +389,10 @@ pub async fn donation_platform_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Get valid report types.
|
||||
/// List valid report types.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "reportTypeList",
|
||||
responses(
|
||||
@@ -406,8 +414,10 @@ pub async fn report_type_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Get valid project types.
|
||||
/// List valid project types.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "projectTypeList",
|
||||
responses(
|
||||
@@ -429,8 +439,10 @@ pub async fn project_type_list(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Get valid side-type values.
|
||||
/// List valid side-type values.
|
||||
#[utoipa::path(
|
||||
context_path = "/tag",
|
||||
tag = "tags",
|
||||
get,
|
||||
operation_id = "sideTypeList",
|
||||
responses(
|
||||
|
||||
@@ -12,10 +12,10 @@ use ariadne::ids::UserId;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(teams_get);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/team")
|
||||
web::scope("/team")
|
||||
.service(team_members_get)
|
||||
.service(edit_team_member)
|
||||
.service(transfer_ownership)
|
||||
@@ -30,13 +30,17 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
// also the members of the organization's team if the project is associated with an organization
|
||||
// (Unlike team_members_get_project, which only returns the members of the project's team)
|
||||
// They can be differentiated by the "organization_permissions" field being null or not
|
||||
/// Get a project's team members.
|
||||
/// Get a project's team members.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "teams",
|
||||
get,
|
||||
operation_id = "getProjectTeamMembers",
|
||||
params(("id" = String, Path, description = "The ID or slug of the project")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or slug of the project")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyTeamMember>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -74,12 +78,16 @@ pub async fn team_members_get_project(
|
||||
}
|
||||
|
||||
// Returns all members of a team, but not necessarily those of a project-team's organization (unlike team_members_get_project)
|
||||
/// Get a team's members.
|
||||
/// Get a team's members.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
get,
|
||||
operation_id = "getTeamMembers",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
responses((status = 200, description = "Expected response to a valid request")),
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<LegacyTeamMember>)),
|
||||
security(("bearer_auth" = ["PROJECT_READ"]))
|
||||
)]
|
||||
#[get("/{id}/members")]
|
||||
@@ -112,12 +120,15 @@ pub struct TeamIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
/// Get the members of multiple teams.
|
||||
/// Get the members of multiple teams.
|
||||
#[utoipa::path(
|
||||
tag = "teams",
|
||||
get,
|
||||
operation_id = "getTeams",
|
||||
params(("ids" = String, Query, description = "The JSON array of team IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
params(
|
||||
("ids" = String, Query, description = "The JSON array of team IDs")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<Vec<LegacyTeamMember>>))
|
||||
)]
|
||||
#[get("/teams")]
|
||||
pub async fn teams_get(
|
||||
@@ -154,11 +165,15 @@ pub async fn teams_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Join a team with a pending invite.
|
||||
/// Join a team with a pending invite.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
post,
|
||||
operation_id = "joinTeam",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
@@ -210,11 +225,15 @@ pub struct NewTeamMember {
|
||||
pub ordering: i64,
|
||||
}
|
||||
|
||||
/// Add a member to a team.
|
||||
/// Add a member to a team.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
post,
|
||||
operation_id = "addTeamMember",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team")
|
||||
),
|
||||
request_body = NewTeamMember,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -267,17 +286,15 @@ pub struct EditTeamMember {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
/// Modify a team member.
|
||||
/// Update a team member.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
patch,
|
||||
operation_id = "modifyTeamMember",
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team"),
|
||||
(
|
||||
"user_id" = UserId,
|
||||
Path,
|
||||
description = "The ID of the user to modify"
|
||||
)
|
||||
("user_id" = UserId, Path, description = "The ID of the user to modify")
|
||||
),
|
||||
request_body = EditTeamMember,
|
||||
responses(
|
||||
@@ -326,11 +343,15 @@ pub struct TransferOwnership {
|
||||
pub user_id: UserId,
|
||||
}
|
||||
|
||||
/// Transfer team ownership.
|
||||
/// Transfer team ownership.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
patch,
|
||||
operation_id = "transferTeamOwnership",
|
||||
params(("id" = TeamId, Path, description = "The ID of the team")),
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team")
|
||||
),
|
||||
request_body = TransferOwnership,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -369,17 +390,15 @@ pub async fn transfer_ownership(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Remove a member from a team.
|
||||
/// Remove a member from a team.
|
||||
#[utoipa::path(
|
||||
context_path = "/team",
|
||||
tag = "teams",
|
||||
delete,
|
||||
operation_id = "deleteTeamMember",
|
||||
params(
|
||||
("id" = TeamId, Path, description = "The ID of the team"),
|
||||
(
|
||||
"user_id" = UserId,
|
||||
Path,
|
||||
description = "The ID of the user to remove"
|
||||
)
|
||||
("user_id" = UserId, Path, description = "The ID of the user to remove")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
|
||||
@@ -9,23 +9,27 @@ use crate::routes::{ApiError, v2_reroute, v3};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/thread")
|
||||
web::scope("/thread")
|
||||
.service(thread_get)
|
||||
.service(thread_send_message),
|
||||
);
|
||||
cfg.service(utoipa_actix_web::scope("/message").service(message_delete));
|
||||
cfg.service(web::scope("/message").service(message_delete));
|
||||
cfg.service(threads_get);
|
||||
}
|
||||
|
||||
/// Get a thread by ID.
|
||||
/// Get a thread by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/thread",
|
||||
tag = "threads",
|
||||
get,
|
||||
operation_id = "getThread",
|
||||
params(("id" = ThreadId, Path, description = "The ID of the thread")),
|
||||
params(
|
||||
("id" = ThreadId, Path, description = "The ID of the thread")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Thread),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -51,13 +55,16 @@ pub struct ThreadIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
/// Get multiple threads by ID.
|
||||
/// Get multiple threads by ID.
|
||||
#[utoipa::path(
|
||||
tag = "threads",
|
||||
get,
|
||||
operation_id = "getThreads",
|
||||
params(("ids" = String, Query, description = "The JSON array of thread IDs")),
|
||||
params(
|
||||
("ids" = String, Query, description = "The JSON array of thread IDs")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyThread>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -101,11 +108,15 @@ pub struct NewThreadMessage {
|
||||
pub body: MessageBody,
|
||||
}
|
||||
|
||||
/// Send a message to a thread.
|
||||
/// Send a message to a thread.
|
||||
#[utoipa::path(
|
||||
context_path = "/thread",
|
||||
tag = "threads",
|
||||
post,
|
||||
operation_id = "sendThreadMessage",
|
||||
params(("id" = ThreadId, Path, description = "The ID of the thread")),
|
||||
params(
|
||||
("id" = ThreadId, Path, description = "The ID of the thread")
|
||||
),
|
||||
request_body = NewThreadMessage,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -142,11 +153,15 @@ pub async fn thread_send_message(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a thread message by ID.
|
||||
/// Delete a thread message by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/message",
|
||||
tag = "threads",
|
||||
delete,
|
||||
operation_id = "deleteThreadMessage",
|
||||
params(("id" = ThreadMessageId, Path, description = "The ID of the message")),
|
||||
params(
|
||||
("id" = ThreadMessageId, Path, description = "The ID of the message")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
|
||||
@@ -13,11 +13,11 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(user_auth_get);
|
||||
cfg.service(users_get);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/user")
|
||||
web::scope("/user")
|
||||
.service(user_get)
|
||||
.service(projects_list)
|
||||
.service(user_delete)
|
||||
@@ -29,12 +29,14 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the current user from the authorization header.
|
||||
/// Get the current user.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getUserFromAuth",
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyUser),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -68,12 +70,15 @@ pub struct UserIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
/// Get multiple users by ID.
|
||||
/// Get multiple users by ID.
|
||||
#[utoipa::path(
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getUsers",
|
||||
params(("ids" = String, Query, description = "The JSON array of user IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
params(
|
||||
("ids" = String, Query, description = "The JSON array of user IDs")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<LegacyUser>))
|
||||
)]
|
||||
#[get("/users")]
|
||||
pub async fn users_get(
|
||||
@@ -104,13 +109,17 @@ pub async fn users_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a user by ID or username.
|
||||
/// Get a user by ID or username.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyUser),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -139,13 +148,17 @@ pub async fn user_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a user's projects.
|
||||
/// Get a user's projects.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getUserProjects",
|
||||
params(("user_id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("user_id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -204,11 +217,15 @@ pub struct EditUser {
|
||||
pub allow_friend_requests: Option<bool>,
|
||||
}
|
||||
|
||||
/// Modify a user.
|
||||
/// Update a user.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
patch,
|
||||
operation_id = "modifyUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
request_body = EditUser,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
@@ -258,17 +275,15 @@ pub struct Extension {
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
/// Change a user's avatar.
|
||||
/// Change a user's avatar.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
patch,
|
||||
operation_id = "changeUserIcon",
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user"),
|
||||
(
|
||||
"ext" = String,
|
||||
Query,
|
||||
description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)"
|
||||
)
|
||||
("ext" = String, Query, description = "Image extension (png, jpg, jpeg, bmp, gif, webp, svg, svgz, rgb)")
|
||||
),
|
||||
request_body(
|
||||
content(
|
||||
@@ -317,11 +332,15 @@ pub async fn user_icon_edit(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Remove a user's avatar.
|
||||
/// Remove a user's avatar.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
delete,
|
||||
operation_id = "deleteUserIcon",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
@@ -354,11 +373,15 @@ pub async fn user_icon_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Delete a user by ID or username.
|
||||
/// Delete a user by ID or username.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
delete,
|
||||
operation_id = "deleteUser",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(
|
||||
@@ -387,13 +410,17 @@ pub async fn user_delete(
|
||||
.or_else(v2_reroute::flatten_404_error)
|
||||
}
|
||||
|
||||
/// Get projects followed by a user.
|
||||
/// Get projects followed by a user.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getFollowedProjects",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyProject>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -434,13 +461,17 @@ pub async fn user_follows(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get notifications for a user.
|
||||
/// Get notifications for a user.
|
||||
#[utoipa::path(
|
||||
context_path = "/user",
|
||||
tag = "users",
|
||||
get,
|
||||
operation_id = "getUserNotifications",
|
||||
params(("id" = String, Path, description = "The ID or username of the user")),
|
||||
params(
|
||||
("id" = String, Path, description = "The ID or username of the user")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyNotification>),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
|
||||
@@ -75,8 +75,9 @@ pub struct InitialVersionData {
|
||||
}
|
||||
|
||||
// under `/api/v1/version`
|
||||
/// Create a version on an existing project.
|
||||
/// Create a version on an existing project.
|
||||
#[utoipa::path(
|
||||
tag = "version creation",
|
||||
post,
|
||||
operation_id = "createVersion",
|
||||
request_body(
|
||||
@@ -84,7 +85,7 @@ pub struct InitialVersionData {
|
||||
description = "Multipart payload containing `data` and uploaded files"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyVersion),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
@@ -302,17 +303,20 @@ async fn get_example_version_fields(
|
||||
}
|
||||
|
||||
// under /api/v1/version/{version_id}
|
||||
/// Add files to an existing version.
|
||||
/// Add files to an existing version.
|
||||
#[utoipa::path(
|
||||
tag = "version creation",
|
||||
post,
|
||||
operation_id = "addFilesToVersion",
|
||||
params(("version_id" = VersionId, Path, description = "The ID of the version")),
|
||||
params(
|
||||
("version_id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing files to upload"
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
|
||||
@@ -5,15 +5,16 @@ use crate::database::redis::RedisPool;
|
||||
use crate::models::projects::{Project, Version, VersionType};
|
||||
use crate::models::v2::projects::{LegacyProject, LegacyVersion};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::v3::version_file::HashQuery;
|
||||
use crate::routes::{v2_reroute, v3};
|
||||
use crate::routes::HashAlgorithm;
|
||||
use crate::routes::v3::version_file::{DownloadRedirect, HashQuery};
|
||||
use crate::routes::{FileHash, v2_reroute, v3};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/version_file")
|
||||
web::scope("/version_file")
|
||||
.service(delete_file)
|
||||
.service(get_version_from_hash)
|
||||
.service(download_version)
|
||||
@@ -22,7 +23,7 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
);
|
||||
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/version_files")
|
||||
web::scope("/version_files")
|
||||
.service(get_versions_from_hashes)
|
||||
.service(update_files)
|
||||
.service(update_files_many)
|
||||
@@ -33,27 +34,17 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
// under /api/v1/version_file/{hash}
|
||||
/// Get version metadata by file hash.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_file",
|
||||
tag = "version files",
|
||||
get,
|
||||
operation_id = "versionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<HashAlgorithm>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<crate::models::ids::VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyVersion),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -93,27 +84,17 @@ pub async fn get_version_from_hash(
|
||||
// under /api/v1/version_file/{hash}/download
|
||||
/// Download a file by hash.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_file",
|
||||
tag = "version files",
|
||||
get,
|
||||
operation_id = "downloadVersionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<HashAlgorithm>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<crate::models::ids::VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
responses(
|
||||
(status = 302, description = "Temporary redirect to file URL"),
|
||||
(status = 302, description = "Temporary redirect to file URL", body = DownloadRedirect),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -145,27 +126,17 @@ pub async fn download_version(
|
||||
// under /api/v1/version_file/{hash}
|
||||
/// Delete a file by hash.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_file",
|
||||
tag = "version files",
|
||||
delete,
|
||||
operation_id = "deleteFileFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID to delete from"
|
||||
)
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<HashAlgorithm>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<crate::models::ids::VersionId>, Query, description = "Optional version ID to delete from")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -208,28 +179,18 @@ pub struct UpdateData {
|
||||
|
||||
/// Get the latest compatible version from a file hash.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_file",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "getLatestVersionFromHash",
|
||||
params(
|
||||
(
|
||||
"version_id" = String,
|
||||
Path,
|
||||
description = "The hexadecimal file hash"
|
||||
),
|
||||
(
|
||||
"algorithm" = Option<String>,
|
||||
Query,
|
||||
description = "Hash algorithm to use (sha1 or sha512)"
|
||||
),
|
||||
(
|
||||
"version_id" = Option<crate::models::ids::VersionId>,
|
||||
Query,
|
||||
description = "Optional version ID when hash maps to multiple files"
|
||||
)
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<HashAlgorithm>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<crate::models::ids::VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
request_body = UpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyVersion),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 404,
|
||||
@@ -287,18 +248,23 @@ pub async fn get_update_from_hash(
|
||||
// Requests above with multiple versions below
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct FileHashes {
|
||||
/// Hash algorithm to use (sha1 or sha512)
|
||||
#[schema(value_type = Option<HashAlgorithm>)]
|
||||
pub algorithm: Option<String>,
|
||||
#[schema(value_type = Vec<FileHash>)]
|
||||
pub hashes: Vec<String>,
|
||||
}
|
||||
|
||||
// under /api/v2/version_files
|
||||
/// Get versions from file hashes.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_files",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "versionsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<FileHash, LegacyVersion>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -345,11 +311,13 @@ pub async fn get_versions_from_hashes(
|
||||
|
||||
/// Get projects from file hashes.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_file",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "projectsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<FileHash, LegacyProject>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -413,7 +381,10 @@ pub async fn get_projects_from_hashes(
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyUpdateData {
|
||||
/// Hash algorithm to use (sha1 or sha512)
|
||||
#[schema(value_type = Option<HashAlgorithm>)]
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
#[schema(value_type = Vec<FileHash>)]
|
||||
pub hashes: Vec<String>,
|
||||
pub loaders: Option<Vec<String>>,
|
||||
pub game_versions: Option<Vec<String>>,
|
||||
@@ -422,11 +393,13 @@ pub struct ManyUpdateData {
|
||||
|
||||
/// Get latest compatible versions for multiple hashes.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_files",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashes",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<FileHash, LegacyVersion>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -471,11 +444,13 @@ pub async fn update_files(
|
||||
|
||||
/// Get all latest compatible versions for multiple hashes.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_files",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashesMany",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<FileHash, Vec<LegacyVersion>>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
@@ -531,17 +506,20 @@ pub struct FileUpdateData {
|
||||
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyFileUpdateData {
|
||||
#[schema(value_type = Option<HashAlgorithm>)]
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub hashes: Vec<FileUpdateData>,
|
||||
}
|
||||
|
||||
/// Get latest versions with per-hash filters.
|
||||
#[utoipa::path(
|
||||
context_path = "/version_files",
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "getLatestVersionsFromHashesIndividual",
|
||||
request_body = ManyFileUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<FileHash, LegacyVersion>),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
|
||||
@@ -16,11 +16,11 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(versions_get);
|
||||
cfg.service(super::version_creation::version_create);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/version")
|
||||
web::scope("/version")
|
||||
.service(version_get)
|
||||
.service(version_delete)
|
||||
.service(version_edit)
|
||||
@@ -44,39 +44,24 @@ fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// List versions for a project.
|
||||
/// List versions for a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project/{project_id}",
|
||||
tag = "versions",
|
||||
get,
|
||||
operation_id = "getProjectVersions",
|
||||
params(
|
||||
(
|
||||
"project_id" = String,
|
||||
Path,
|
||||
description = "The ID or slug of the project"
|
||||
),
|
||||
(
|
||||
"loaders" = Option<String>,
|
||||
Query,
|
||||
description = "JSON array of loaders to filter by"
|
||||
),
|
||||
(
|
||||
"game_versions" = Option<String>,
|
||||
Query,
|
||||
description = "JSON array of game versions to filter by"
|
||||
),
|
||||
(
|
||||
"featured" = Option<bool>,
|
||||
Query,
|
||||
description = "Filter by featured status"
|
||||
),
|
||||
(
|
||||
"include_changelog" = Option<bool>,
|
||||
Query,
|
||||
description = "Whether to include changelog fields"
|
||||
)
|
||||
("project_id" = String, Path, description = "The ID or slug of the project"),
|
||||
("loaders" = Option<String>, Query, description = "JSON array of loaders to filter by"),
|
||||
("game_versions" = Option<String>, Query, description = "JSON array of game versions to filter by"),
|
||||
("featured" = Option<bool>, Query, description = "Filter by featured status"),
|
||||
("version_type" = Option<VersionType>, Query, description = "Filter by version type"),
|
||||
("limit" = Option<usize>, Query, description = "Maximum number of versions"),
|
||||
("offset" = Option<usize>, Query, description = "Version offset"),
|
||||
("include_changelog" = Option<bool>, Query, description = "Whether to include changelog fields")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<LegacyVersion>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -169,24 +154,18 @@ pub async fn version_list(
|
||||
}
|
||||
|
||||
// Given a project ID/slug and a version slug
|
||||
/// Get a project version by ID or version number.
|
||||
/// Get a project version by ID or version number.
|
||||
#[utoipa::path(
|
||||
context_path = "/project/{project_id}",
|
||||
tag = "versions",
|
||||
get,
|
||||
operation_id = "getVersionFromIdOrNumber",
|
||||
params(
|
||||
(
|
||||
"project_id" = String,
|
||||
Path,
|
||||
description = "The ID or slug of the project"
|
||||
),
|
||||
(
|
||||
"slug" = String,
|
||||
Path,
|
||||
description = "The version ID or version number"
|
||||
)
|
||||
("project_id" = String, Path, description = "The ID or slug of the project"),
|
||||
("slug" = String, Path, description = "The version ID or version number")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyVersion),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -230,12 +209,16 @@ pub struct VersionIds {
|
||||
pub include_changelog: bool,
|
||||
}
|
||||
|
||||
/// Get multiple versions by ID.
|
||||
/// Get multiple versions by ID.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
get,
|
||||
operation_id = "getVersions",
|
||||
params(("ids" = String, Query, description = "The JSON array of version IDs")),
|
||||
responses((status = 200, description = "Expected response to a valid request"))
|
||||
params(
|
||||
("ids" = String, Query, description = "The JSON array of version IDs"),
|
||||
("include_changelog" = Option<bool>, Query, description = "Whether to include changelog fields")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<LegacyVersion>))
|
||||
)]
|
||||
#[get("/versions")]
|
||||
pub async fn versions_get(
|
||||
@@ -274,13 +257,17 @@ pub async fn versions_get(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a version by ID.
|
||||
/// Get a version by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/version",
|
||||
tag = "versions",
|
||||
get,
|
||||
operation_id = "getVersion",
|
||||
params(("version_id" = models::ids::VersionId, Path, description = "The ID of the version")),
|
||||
params(
|
||||
("version_id" = models::ids::VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request"),
|
||||
(status = 200, description = "Expected response to a valid request", body = LegacyVersion),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
@@ -353,14 +340,18 @@ pub struct EditVersionFileType {
|
||||
pub file_type: Option<FileType>,
|
||||
}
|
||||
|
||||
/// Modify an existing version.
|
||||
/// Update an existing version.
|
||||
#[utoipa::path(
|
||||
context_path = "/version",
|
||||
tag = "versions",
|
||||
patch,
|
||||
operation_id = "modifyVersion",
|
||||
params(("id" = VersionId, Path, description = "The ID of the version")),
|
||||
params(
|
||||
("id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
request_body = EditVersion,
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
@@ -468,13 +459,17 @@ pub async fn version_edit(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Delete a version by ID.
|
||||
/// Delete a version by ID.
|
||||
#[utoipa::path(
|
||||
context_path = "/version",
|
||||
tag = "versions",
|
||||
delete,
|
||||
operation_id = "deleteVersion",
|
||||
params(("version_id" = VersionId, Path, description = "The ID of the version")),
|
||||
params(
|
||||
("version_id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Expected response to a valid request"),
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::{
|
||||
util::error::Context,
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(analytics_events_get)
|
||||
.service(analytics_event_create)
|
||||
.service(analytics_event_edit)
|
||||
@@ -37,8 +37,11 @@ pub struct AnalyticsEventUpsert {
|
||||
pub ends: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Fetches all analytics events.
|
||||
#[utoipa::path(responses((status = OK, body = Vec<AnalyticsEvent>)))]
|
||||
/// List analytics events.
|
||||
#[utoipa::path(
|
||||
context_path = "/v3/analytics-event",
|
||||
tag = "v3 analytics", responses((status = OK, body = Vec<AnalyticsEvent>))
|
||||
)]
|
||||
#[get("")]
|
||||
pub async fn analytics_events_get(
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -54,8 +57,11 @@ pub async fn analytics_events_get(
|
||||
Ok(web::Json(events))
|
||||
}
|
||||
|
||||
/// Creates an analytics event.
|
||||
#[utoipa::path(responses((status = OK, body = AnalyticsEvent)))]
|
||||
/// Create an analytics event.
|
||||
#[utoipa::path(
|
||||
context_path = "/v3/analytics-event",
|
||||
tag = "v3 analytics", responses((status = OK, body = AnalyticsEvent))
|
||||
)]
|
||||
#[post("")]
|
||||
pub async fn analytics_event_create(
|
||||
req: HttpRequest,
|
||||
@@ -110,8 +116,11 @@ pub async fn analytics_event_create(
|
||||
Ok(web::Json(event.into()))
|
||||
}
|
||||
|
||||
/// Edits an analytics event.
|
||||
#[utoipa::path(responses((status = OK, body = AnalyticsEvent)))]
|
||||
/// Update an analytics event.
|
||||
#[utoipa::path(
|
||||
context_path = "/v3/analytics-event",
|
||||
tag = "v3 analytics", responses((status = OK, body = AnalyticsEvent))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
pub async fn analytics_event_edit(
|
||||
req: HttpRequest,
|
||||
@@ -158,8 +167,11 @@ pub async fn analytics_event_edit(
|
||||
Ok(web::Json(event.into()))
|
||||
}
|
||||
|
||||
/// Deletes an analytics event.
|
||||
#[utoipa::path(responses((status = NO_CONTENT)))]
|
||||
/// Delete an analytics event.
|
||||
#[utoipa::path(
|
||||
context_path = "/v3/analytics-event",
|
||||
tag = "v3 analytics", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn analytics_event_delete(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
routes::ApiError,
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(fetch_facets);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ pub struct ProjectPlaytimeFacets {
|
||||
pub country: Vec<String>,
|
||||
}
|
||||
|
||||
/// Get analytics facets.
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
responses((status = OK, body = inline(FacetsResponse))),
|
||||
)]
|
||||
#[post("/facets")]
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
//! requests, you have to zip together M arrays of N elements
|
||||
//! - this makes it inconvenient to have separate endpoints
|
||||
|
||||
mod facets;
|
||||
pub mod facets;
|
||||
mod metrics;
|
||||
mod old;
|
||||
pub mod old;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
@@ -54,7 +54,7 @@ use crate::{
|
||||
pub(crate) use metrics::normalize_download_source;
|
||||
pub use metrics::*;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(fetch_analytics);
|
||||
cfg.configure(facets::config);
|
||||
cfg.configure(old::config);
|
||||
@@ -172,8 +172,10 @@ pub enum ProjectAnalyticsEventKind {
|
||||
|
||||
// logic
|
||||
|
||||
/// Fetches analytics data for the authorized user's projects.
|
||||
/// Fetch analytics data.
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
responses((status = OK, body = inline(GetResponse))),
|
||||
)]
|
||||
#[post("")]
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(playtimes_get)
|
||||
.service(views_get)
|
||||
.service(downloads_get)
|
||||
@@ -48,7 +48,18 @@ pub struct GetData {
|
||||
pub resolution_minutes: Option<NonZeroU32>, // defaults to 1 day. Ignored in routes that do not aggregate over a resolution (eg: /countries)
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get playtime data.
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<u32, u64>>)),
|
||||
)]
|
||||
#[get("/playtime")]
|
||||
pub async fn playtimes_get(
|
||||
req: HttpRequest,
|
||||
@@ -110,7 +121,7 @@ pub async fn playtimes_get(
|
||||
Ok(HttpResponse::Ok().json(hm))
|
||||
}
|
||||
|
||||
/// Get view data for a set of projects or versions.
|
||||
/// Get view data.
|
||||
///
|
||||
/// Data is returned as a hashmap of project/version ids to a hashmap of days to views
|
||||
/// eg:
|
||||
@@ -120,7 +131,17 @@ pub async fn playtimes_get(
|
||||
/// }
|
||||
///}
|
||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<u32, u64>>)),
|
||||
)]
|
||||
#[get("/views")]
|
||||
pub async fn views_get(
|
||||
req: HttpRequest,
|
||||
@@ -182,7 +203,7 @@ pub async fn views_get(
|
||||
Ok(HttpResponse::Ok().json(hm))
|
||||
}
|
||||
|
||||
/// Get download data for a set of projects or versions.
|
||||
/// Get download data.
|
||||
///
|
||||
/// Data is returned as a hashmap of project/version ids to a hashmap of days to downloads
|
||||
/// eg:
|
||||
@@ -192,7 +213,17 @@ pub async fn views_get(
|
||||
/// }
|
||||
///}
|
||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<u32, u64>>)),
|
||||
)]
|
||||
#[get("/downloads")]
|
||||
pub async fn downloads_get(
|
||||
req: HttpRequest,
|
||||
@@ -255,7 +286,7 @@ pub async fn downloads_get(
|
||||
Ok(HttpResponse::Ok().json(hm))
|
||||
}
|
||||
|
||||
/// Get payout data for a set of projects.
|
||||
/// Get payout data.
|
||||
///
|
||||
/// Data is returned as a hashmap of project ids to a hashmap of days to amount earned per day
|
||||
/// eg:
|
||||
@@ -265,7 +296,17 @@ pub async fn downloads_get(
|
||||
/// }
|
||||
///}
|
||||
/// ONLY project IDs can be used. Unauthorized projects will be filtered out.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<i64, rust_decimal::Decimal>>)),
|
||||
)]
|
||||
#[get("/revenue")]
|
||||
pub async fn revenue_get(
|
||||
req: HttpRequest,
|
||||
@@ -394,7 +435,7 @@ pub async fn revenue_get(
|
||||
Ok(HttpResponse::Ok().json(hm))
|
||||
}
|
||||
|
||||
/// Get country data for a set of projects or versions.
|
||||
/// Get download country data.
|
||||
///
|
||||
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to downloads.
|
||||
/// Unknown countries are labeled "".
|
||||
@@ -407,7 +448,17 @@ pub async fn revenue_get(
|
||||
///}
|
||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<String, u64>>)),
|
||||
)]
|
||||
#[get("/countries/downloads")]
|
||||
pub async fn countries_downloads_get(
|
||||
req: HttpRequest,
|
||||
@@ -470,7 +521,7 @@ pub async fn countries_downloads_get(
|
||||
Ok(HttpResponse::Ok().json(hm))
|
||||
}
|
||||
|
||||
/// Get country data for a set of projects or versions.
|
||||
/// Get view country data.
|
||||
///
|
||||
/// Data is returned as a hashmap of project/version ids to a hashmap of coutnry to views.
|
||||
/// Unknown countries are labeled "".
|
||||
@@ -483,7 +534,17 @@ pub async fn countries_downloads_get(
|
||||
///}
|
||||
/// Either a list of project_ids or version_ids can be used, but not both. Unauthorized projects/versions will be filtered out.
|
||||
/// For this endpoint, provided dates are a range to aggregate over, not specific days to fetch
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/analytics",
|
||||
tag = "analytics",
|
||||
params(
|
||||
("project_ids" = Option<String>, Query),
|
||||
("start_date" = Option<String>, Query),
|
||||
("end_date" = Option<String>, Query),
|
||||
("resolution_minutes" = Option<u32>, Query)
|
||||
),
|
||||
responses((status = OK, body = HashMap<String, HashMap<String, u64>>)),
|
||||
)]
|
||||
#[get("/countries/views")]
|
||||
pub async fn countries_views_get(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::util::routes::read_limited_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use crate::{database, models};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use chrono::Utc;
|
||||
use eyre::eyre;
|
||||
@@ -27,21 +27,17 @@ use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("collections", web::get().to(collections_get));
|
||||
cfg.route("collection", web::post().to(collection_create));
|
||||
|
||||
cfg.service(
|
||||
web::scope("collection")
|
||||
.route("{id}", web::get().to(collection_get))
|
||||
.route("{id}", web::delete().to(collection_delete))
|
||||
.route("{id}", web::patch().to(collection_edit))
|
||||
.route("{id}/icon", web::patch().to(collection_icon_edit))
|
||||
.route("{id}/icon", web::delete().to(delete_collection_icon)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(collections_get)
|
||||
.service(collection_create)
|
||||
.service(collection_get)
|
||||
.service(collection_delete)
|
||||
.service(collection_edit)
|
||||
.service(collection_icon_edit)
|
||||
.service(delete_collection_icon);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||
#[derive(Serialize, Deserialize, Validate, Clone, utoipa::ToSchema)]
|
||||
pub struct CollectionCreateData {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -58,6 +54,8 @@ pub struct CollectionCreateData {
|
||||
pub projects: Vec<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "collections", responses((status = OK)))]
|
||||
#[post("/collection")]
|
||||
pub async fn collection_create(
|
||||
req: HttpRequest,
|
||||
collection_create_data: web::Json<CollectionCreateData>,
|
||||
@@ -142,6 +140,12 @@ pub async fn collection_create(
|
||||
pub struct CollectionIds {
|
||||
pub ids: String,
|
||||
}
|
||||
#[utoipa::path(
|
||||
tag = "collections",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/collections")]
|
||||
pub async fn collections_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<CollectionIds>,
|
||||
@@ -178,6 +182,8 @@ pub async fn collections_get(
|
||||
Ok(HttpResponse::Ok().json(collections))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "collections", responses((status = OK)))]
|
||||
#[get("/collection/{id}")]
|
||||
pub async fn collection_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -209,7 +215,7 @@ pub async fn collection_get(
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditCollection {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -223,11 +229,14 @@ pub struct EditCollection {
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
pub description: Option<Option<String>>,
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub status: Option<CollectionStatus>,
|
||||
#[validate(length(max = 1024))]
|
||||
pub new_projects: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "collections", responses((status = NO_CONTENT)))]
|
||||
#[patch("/collection/{id}")]
|
||||
pub async fn collection_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -383,6 +392,13 @@ pub struct Extension {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path(
|
||||
tag = "collections",
|
||||
params(("ext" = String, Query)),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/collection/{id}/icon")]
|
||||
pub async fn collection_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
@@ -468,6 +484,8 @@ pub async fn collection_icon_edit(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "collections", responses((status = NO_CONTENT)))]
|
||||
#[delete("/collection/{id}/icon")]
|
||||
pub async fn delete_collection_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -527,6 +545,8 @@ pub async fn delete_collection_icon(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "collections", responses((status = NO_CONTENT)))]
|
||||
#[delete("/collection/{id}")]
|
||||
pub async fn collection_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -27,12 +27,18 @@ const CONTENT_RESOLVE_CACHE_HEAT_NAMESPACE: &str = "content_resolve_heat";
|
||||
const CONTENT_RESOLVE_CACHE_SCHEMA_VERSION: &str = "v1";
|
||||
const CONTENT_RESOLVE_CACHE_HEAT_WINDOW_SECONDS: i64 = 60 * 60 * 24;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(resolve_content);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(web::scope("/v3").service(resolve_content));
|
||||
}
|
||||
|
||||
#[post("content/resolve")]
|
||||
async fn resolve_content(
|
||||
/// Resolve content.
|
||||
#[utoipa::path(
|
||||
tag = "content",
|
||||
request_body = serde_json::Value,
|
||||
responses((status = OK, body = serde_json::Value)),
|
||||
)]
|
||||
#[post("/content/resolve")]
|
||||
pub async fn resolve_content(
|
||||
req: HttpRequest,
|
||||
request: web::Json<ResolveContentRequest>,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -17,13 +17,15 @@ use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use ariadne::networking::message::ServerToClientMessage;
|
||||
use chrono::Utc;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(add_friend);
|
||||
cfg.service(remove_friend);
|
||||
cfg.service(friends);
|
||||
}
|
||||
|
||||
#[post("friend/{id}")]
|
||||
/// Add a friend.
|
||||
#[utoipa::path(tag = "friends", responses((status = NO_CONTENT)))]
|
||||
#[post("/friend/{id}")]
|
||||
pub async fn add_friend(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -133,7 +135,9 @@ pub async fn add_friend(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[delete("friend/{id}")]
|
||||
/// Remove a friend.
|
||||
#[utoipa::path(tag = "friends", responses((status = NO_CONTENT)))]
|
||||
#[delete("/friend/{id}")]
|
||||
pub async fn remove_friend(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -175,7 +179,9 @@ pub async fn remove_friend(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("friends")]
|
||||
/// List friends.
|
||||
#[utoipa::path(tag = "friends", responses((status = OK, body = Vec<UserFriend>)))]
|
||||
#[get("/friends")]
|
||||
pub async fn friends(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -14,11 +14,11 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::img::upload_image_optimized;
|
||||
use crate::util::routes::read_limited_from_payload;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("image", web::post().to(images_add));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(images_add);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -36,6 +36,20 @@ pub struct ImageUpload {
|
||||
pub report_id: Option<ReportId>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "images",
|
||||
params(
|
||||
("ext" = String, Query),
|
||||
("context" = String, Query),
|
||||
("project_id" = Option<String>, Query),
|
||||
("version_id" = Option<VersionId>, Query),
|
||||
("thread_message_id" = Option<ThreadMessageId>, Query),
|
||||
("report_id" = Option<ReportId>, Query)
|
||||
),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[post("/image")]
|
||||
pub async fn images_add(
|
||||
req: HttpRequest,
|
||||
web::Query(data): web::Query<ImageUpload>,
|
||||
|
||||
@@ -6,18 +6,17 @@ use crate::{
|
||||
queue::session::AuthQueue,
|
||||
routes::ApiError,
|
||||
};
|
||||
use actix_web::{HttpRequest, web};
|
||||
use actix_web::{HttpRequest, get, web};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("limits")
|
||||
.route("projects", web::get().to(get_project_limits))
|
||||
.route("organizations", web::get().to(get_organization_limits))
|
||||
.route("collections", web::get().to(get_collection_limits)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_project_limits)
|
||||
.service(get_organization_limits)
|
||||
.service(get_collection_limits);
|
||||
}
|
||||
|
||||
async fn get_project_limits(
|
||||
#[utoipa::path(tag = "limits", responses((status = OK)))]
|
||||
#[get("/limits/projects")]
|
||||
pub async fn get_project_limits(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -36,7 +35,9 @@ async fn get_project_limits(
|
||||
Ok(web::Json(limits))
|
||||
}
|
||||
|
||||
async fn get_organization_limits(
|
||||
#[utoipa::path(tag = "limits", responses((status = OK)))]
|
||||
#[get("/limits/organizations")]
|
||||
pub async fn get_organization_limits(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -55,7 +56,9 @@ async fn get_organization_limits(
|
||||
Ok(web::Json(limits))
|
||||
}
|
||||
|
||||
async fn get_collection_limits(
|
||||
#[utoipa::path(tag = "limits", responses((status = OK)))]
|
||||
#[get("/limits/collections")]
|
||||
pub async fn get_collection_limits(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub use super::ApiError;
|
||||
use super::SecurityAddon;
|
||||
use crate::util::cors::default_cors;
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde_json::json;
|
||||
@@ -31,14 +32,32 @@ pub mod oauth_clients;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("v3")
|
||||
web::scope("/v3/analytics")
|
||||
.wrap(default_cors())
|
||||
.configure(analytics_get::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("/v3/payout")
|
||||
.wrap(default_cors())
|
||||
.configure(payouts::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("/v3/project")
|
||||
.wrap(default_cors())
|
||||
.configure(projects::project_config)
|
||||
.configure(project_creation::config),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("/v3")
|
||||
.wrap(default_cors())
|
||||
.configure(limits::config)
|
||||
.configure(collections::config)
|
||||
.configure(content::config)
|
||||
.configure(images::config)
|
||||
.configure(notifications::config)
|
||||
.configure(oauth_clients::config)
|
||||
.configure(organizations::config)
|
||||
.service(payouts::paypal_webhook)
|
||||
.service(payouts::tremendous_webhook)
|
||||
.configure(projects::config)
|
||||
.configure(reports::config)
|
||||
.configure(shared_instance_version_creation::config)
|
||||
@@ -52,32 +71,193 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.configure(versions::config)
|
||||
.configure(friends::config),
|
||||
);
|
||||
cfg.configure(content::config);
|
||||
}
|
||||
|
||||
pub fn utoipa_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
) {
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/v3/analytics")
|
||||
.wrap(default_cors())
|
||||
.configure(analytics_get::config),
|
||||
);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/v3/analytics-event")
|
||||
.wrap(default_cors())
|
||||
.configure(analytics_event::config),
|
||||
);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/v3/payout")
|
||||
.wrap(default_cors())
|
||||
.configure(payouts::config),
|
||||
);
|
||||
cfg.service(
|
||||
utoipa_actix_web::scope("/v3/project")
|
||||
.wrap(default_cors())
|
||||
.configure(projects::utoipa_config)
|
||||
.configure(project_creation::config),
|
||||
);
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
info(
|
||||
title = "API v3 (UNSTABLE)",
|
||||
version = "3.0.0",
|
||||
description = include_str!("../../api_v3_description.md"),
|
||||
),
|
||||
paths(
|
||||
analytics_get::fetch_analytics,
|
||||
analytics_get::facets::fetch_facets,
|
||||
analytics_get::old::playtimes_get,
|
||||
analytics_get::old::views_get,
|
||||
analytics_get::old::downloads_get,
|
||||
analytics_get::old::revenue_get,
|
||||
analytics_get::old::countries_downloads_get,
|
||||
analytics_get::old::countries_views_get,
|
||||
payouts::post_compliance_form,
|
||||
payouts::calculate_fees,
|
||||
payouts::create_payout,
|
||||
payouts::transaction_history,
|
||||
payouts::cancel_payout,
|
||||
payouts::payment_methods,
|
||||
payouts::get_balance,
|
||||
payouts::platform_revenue,
|
||||
projects::project_get,
|
||||
projects::project_get_check,
|
||||
projects::project_delete,
|
||||
projects::project_edit,
|
||||
projects::project_icon_edit,
|
||||
projects::delete_project_icon,
|
||||
projects::add_gallery_item,
|
||||
projects::edit_gallery_item,
|
||||
projects::delete_gallery_item,
|
||||
projects::project_follow,
|
||||
projects::project_unfollow,
|
||||
projects::project_get_organization,
|
||||
projects::dependency_list,
|
||||
project_creation::project_create,
|
||||
project_creation::project_create_with_id,
|
||||
project_creation::new::create,
|
||||
teams::team_members_get_project,
|
||||
versions::version_project_get,
|
||||
versions::version_list,
|
||||
limits::get_project_limits,
|
||||
limits::get_organization_limits,
|
||||
limits::get_collection_limits,
|
||||
collections::collection_create,
|
||||
collections::collections_get,
|
||||
collections::collection_get,
|
||||
collections::collection_edit,
|
||||
collections::collection_icon_edit,
|
||||
collections::delete_collection_icon,
|
||||
collections::collection_delete,
|
||||
images::images_add,
|
||||
notifications::notifications_get_route,
|
||||
notifications::notification_get_route,
|
||||
notifications::notification_read_route,
|
||||
notifications::notification_delete_route,
|
||||
notifications::notifications_read_route,
|
||||
notifications::notifications_delete_route,
|
||||
oauth_clients::get_user_clients,
|
||||
oauth_clients::get_client,
|
||||
oauth_clients::get_clients,
|
||||
oauth_clients::oauth_client_create,
|
||||
oauth_clients::oauth_client_delete,
|
||||
oauth_clients::oauth_client_edit,
|
||||
oauth_clients::oauth_client_icon_edit,
|
||||
oauth_clients::oauth_client_icon_delete,
|
||||
oauth_clients::get_user_oauth_authorizations,
|
||||
oauth_clients::revoke_oauth_authorization,
|
||||
super::super::auth::oauth::init_oauth,
|
||||
super::super::auth::oauth::accept_client_scopes,
|
||||
super::super::auth::oauth::reject_client_scopes,
|
||||
super::super::auth::oauth::request_token,
|
||||
organizations::organization_projects_get,
|
||||
organizations::organization_create,
|
||||
organizations::organization_get,
|
||||
organizations::organization_notes_edit,
|
||||
organizations::organizations_get,
|
||||
organizations::organizations_edit,
|
||||
organizations::organization_delete,
|
||||
organizations::organization_projects_add,
|
||||
organizations::organization_projects_remove,
|
||||
organizations::organization_icon_edit,
|
||||
organizations::delete_organization_icon,
|
||||
super::maven::maven_metadata,
|
||||
super::maven::version_file,
|
||||
super::maven::version_file_sha1,
|
||||
super::maven::version_file_sha512,
|
||||
super::updates::forge_updates,
|
||||
projects::project_search,
|
||||
projects::project_search_post,
|
||||
projects::projects_get_route,
|
||||
projects::projects_edit_route,
|
||||
projects::random_projects_get_route,
|
||||
reports::report_create_route,
|
||||
reports::reports_route,
|
||||
reports::reports_get_route,
|
||||
reports::report_get_route,
|
||||
reports::report_edit_route,
|
||||
reports::report_delete_route,
|
||||
shared_instance_version_creation::shared_instance_version_create,
|
||||
shared_instances::shared_instance_create,
|
||||
shared_instances::shared_instance_list,
|
||||
shared_instances::shared_instance_get,
|
||||
shared_instances::shared_instance_edit,
|
||||
shared_instances::shared_instance_delete,
|
||||
shared_instances::shared_instance_version_list,
|
||||
shared_instances::shared_instance_version_get,
|
||||
shared_instances::shared_instance_version_delete,
|
||||
shared_instances::shared_instance_version_download,
|
||||
statistics::get_stats_route,
|
||||
tags::games_list_route,
|
||||
tags::category_list_route,
|
||||
tags::loader_list_route,
|
||||
tags::loader_fields_list_route,
|
||||
tags::license_list_route,
|
||||
tags::license_text_route,
|
||||
tags::link_platform_list_route,
|
||||
tags::report_type_list_route,
|
||||
tags::project_type_list_route,
|
||||
teams::team_members_get_organization,
|
||||
teams::team_members_get_route,
|
||||
teams::teams_get_route,
|
||||
teams::join_team_route,
|
||||
teams::add_team_member_route,
|
||||
teams::edit_team_member_route,
|
||||
teams::transfer_ownership_route,
|
||||
teams::remove_team_member_route,
|
||||
threads::thread_get_route,
|
||||
threads::threads_get_route,
|
||||
threads::thread_send_message_route,
|
||||
threads::message_delete_route,
|
||||
users::all_projects,
|
||||
users::admin_user_email,
|
||||
users::projects_list_route,
|
||||
users::user_auth_get_route,
|
||||
users::users_search,
|
||||
users::users_get_route,
|
||||
users::user_get_route,
|
||||
users::user_notes_edit,
|
||||
users::collections_list,
|
||||
users::orgs_list,
|
||||
users::user_edit_route,
|
||||
users::user_icon_edit_route,
|
||||
users::user_icon_delete_route,
|
||||
users::user_delete_route,
|
||||
users::user_follows_route,
|
||||
users::user_notifications_route,
|
||||
version_creation::version_create_route,
|
||||
version_creation::upload_file_to_version_route,
|
||||
version_file::get_version_from_hash_route,
|
||||
version_file::get_update_from_hash_route,
|
||||
version_file::get_versions_from_hashes_route,
|
||||
version_file::get_projects_from_hashes_route,
|
||||
version_file::update_files_many_route,
|
||||
version_file::update_files_route,
|
||||
version_file::update_individual_files_route,
|
||||
version_file::delete_file_route,
|
||||
version_file::download_version_route,
|
||||
versions::versions_get_route,
|
||||
versions::version_get_route,
|
||||
versions::version_edit_route,
|
||||
versions::version_delete_route,
|
||||
friends::add_friend,
|
||||
friends::remove_friend,
|
||||
friends::friends,
|
||||
content::resolve_content,
|
||||
),
|
||||
modifiers(&V3PathModifier, &SecurityAddon)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
struct V3PathModifier;
|
||||
|
||||
impl utoipa::Modify for V3PathModifier {
|
||||
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||
super::prefix_openapi_paths(openapi, "/v3", |path| {
|
||||
path.starts_with("/analytics/")
|
||||
|| path.starts_with("/maven/")
|
||||
|| path.starts_with("/updates/")
|
||||
|| path.starts_with("/debug/")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn hello_world() -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -7,20 +7,16 @@ use crate::models::notifications::Notification;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("notifications", web::get().to(notifications_get));
|
||||
cfg.route("notifications", web::patch().to(notifications_read));
|
||||
cfg.route("notifications", web::delete().to(notifications_delete));
|
||||
|
||||
cfg.service(
|
||||
web::scope("notification")
|
||||
.route("{id}", web::get().to(notification_get))
|
||||
.route("{id}", web::patch().to(notification_read))
|
||||
.route("{id}", web::delete().to(notification_delete)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(notifications_get_route)
|
||||
.service(notifications_read_route)
|
||||
.service(notifications_delete_route)
|
||||
.service(notification_get_route)
|
||||
.service(notification_read_route)
|
||||
.service(notification_delete_route);
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -28,6 +24,22 @@ pub struct NotificationIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/notifications")]
|
||||
pub async fn notifications_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notifications_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notifications_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
@@ -70,6 +82,18 @@ pub async fn notifications_get(
|
||||
Ok(HttpResponse::Ok().json(notifications))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "notifications", responses((status = OK)))]
|
||||
#[get("/notification/{id}")]
|
||||
pub async fn notification_get_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notification_get(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notification_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -107,6 +131,18 @@ pub async fn notification_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "notifications", responses((status = NO_CONTENT)))]
|
||||
#[patch("/notification/{id}")]
|
||||
pub async fn notification_read_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notification_read(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notification_read(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -157,6 +193,18 @@ pub async fn notification_read(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "notifications", responses((status = NO_CONTENT)))]
|
||||
#[delete("/notification/{id}")]
|
||||
pub async fn notification_delete_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notification_delete(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notification_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(NotificationId,)>,
|
||||
@@ -208,6 +256,22 @@ pub async fn notification_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/notifications")]
|
||||
pub async fn notifications_read_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notifications_read(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notifications_read(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
@@ -261,6 +325,22 @@ pub async fn notifications_read(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "notifications",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/notifications")]
|
||||
pub async fn notifications_delete_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<NotificationIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
notifications_delete(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn notifications_delete(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<NotificationIds>,
|
||||
|
||||
@@ -29,10 +29,7 @@ use crate::{
|
||||
file_hosting::FileHost, models::oauth_clients::DeleteOAuthClientQueryParam,
|
||||
util::routes::read_limited_from_payload,
|
||||
};
|
||||
use actix_web::{
|
||||
HttpRequest, HttpResponse, delete, get, patch, post,
|
||||
web::{self, scope},
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use chrono::Utc;
|
||||
use itertools::Itertools;
|
||||
@@ -41,9 +38,9 @@ use rand_chacha::ChaCha20Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(
|
||||
scope("oauth")
|
||||
web::scope("/oauth")
|
||||
.configure(crate::auth::oauth::config)
|
||||
.service(revoke_oauth_authorization)
|
||||
.service(oauth_client_create)
|
||||
@@ -57,6 +54,11 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients", responses((status = OK))
|
||||
)]
|
||||
#[get("/user/{id}/oauth_apps")]
|
||||
pub async fn get_user_clients(
|
||||
req: HttpRequest,
|
||||
info: web::Path<String>,
|
||||
@@ -100,7 +102,14 @@ pub async fn get_user_clients(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("app/{id}")]
|
||||
/// Get an OAuth client.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("id" = OAuthClientId, Path)),
|
||||
responses((status = OK, body = models::oauth_clients::OAuthClient)),
|
||||
)]
|
||||
#[get("/app/{id}")]
|
||||
pub async fn get_client(
|
||||
id: web::Path<OAuthClientId>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -113,7 +122,14 @@ pub async fn get_client(
|
||||
}
|
||||
}
|
||||
|
||||
#[get("apps")]
|
||||
/// List OAuth clients.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("ids" = Vec<String>, Query)),
|
||||
responses((status = OK, body = Vec<models::oauth_clients::OAuthClient>)),
|
||||
)]
|
||||
#[get("/apps")]
|
||||
pub async fn get_clients(
|
||||
info: web::Query<GetOAuthClientsRequest>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -129,7 +145,7 @@ pub async fn get_clients(
|
||||
Ok(HttpResponse::Ok().json(clients))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct NewOAuthApp {
|
||||
#[validate(
|
||||
custom(function = "crate::util::validate::validate_name"),
|
||||
@@ -154,7 +170,13 @@ pub struct NewOAuthApp {
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[post("app")]
|
||||
/// Create an OAuth client.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
responses((status = OK, body = OAuthClientCreationResult)),
|
||||
)]
|
||||
#[post("/app")]
|
||||
pub async fn oauth_client_create(
|
||||
req: HttpRequest,
|
||||
new_oauth_app: web::Json<NewOAuthApp>,
|
||||
@@ -215,7 +237,14 @@ pub async fn oauth_client_create(
|
||||
}))
|
||||
}
|
||||
|
||||
#[delete("app/{id}")]
|
||||
/// Delete an OAuth client.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("id" = OAuthClientId, Path)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/app/{id}")]
|
||||
pub async fn oauth_client_delete(
|
||||
req: HttpRequest,
|
||||
client_id: web::Path<OAuthClientId>,
|
||||
@@ -245,7 +274,7 @@ pub async fn oauth_client_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct OAuthClientEdit {
|
||||
#[validate(
|
||||
custom(function = "crate::util::validate::validate_name"),
|
||||
@@ -271,7 +300,14 @@ pub struct OAuthClientEdit {
|
||||
pub description: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[patch("app/{id}")]
|
||||
/// Update an OAuth client.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("id" = OAuthClientId, Path)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/app/{id}")]
|
||||
pub async fn oauth_client_edit(
|
||||
req: HttpRequest,
|
||||
client_id: web::Path<OAuthClientId>,
|
||||
@@ -346,7 +382,18 @@ pub struct Extension {
|
||||
pub ext: String,
|
||||
}
|
||||
|
||||
#[patch("app/{id}/icon")]
|
||||
/// Update an OAuth client icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(
|
||||
("id" = OAuthClientId, Path),
|
||||
("ext" = String, Query)
|
||||
),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/app/{id}/icon")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn oauth_client_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
@@ -418,7 +465,14 @@ pub async fn oauth_client_icon_edit(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[delete("app/{id}/icon")]
|
||||
/// Delete an OAuth client icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("id" = OAuthClientId, Path)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/app/{id}/icon")]
|
||||
pub async fn oauth_client_icon_delete(
|
||||
req: HttpRequest,
|
||||
client_id: web::Path<OAuthClientId>,
|
||||
@@ -468,7 +522,13 @@ pub async fn oauth_client_icon_delete(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[get("authorizations")]
|
||||
/// List OAuth authorizations.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
responses((status = OK, body = Vec<models::oauth_clients::OAuthClientAuthorization>)),
|
||||
)]
|
||||
#[get("/authorizations")]
|
||||
pub async fn get_user_oauth_authorizations(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -497,7 +557,14 @@ pub async fn get_user_oauth_authorizations(
|
||||
Ok(HttpResponse::Ok().json(mapped))
|
||||
}
|
||||
|
||||
#[delete("authorizations")]
|
||||
/// Revoke OAuth authorization.
|
||||
#[utoipa::path(
|
||||
context_path = "/oauth",
|
||||
tag = "oauth clients",
|
||||
params(("client_id" = OAuthClientId, Query)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/authorizations")]
|
||||
pub async fn revoke_oauth_authorization(
|
||||
req: HttpRequest,
|
||||
info: web::Query<DeleteOAuthClientQueryParam>,
|
||||
|
||||
@@ -21,37 +21,29 @@ use crate::util::img::delete_old_images;
|
||||
use crate::util::routes::read_limited_from_payload;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use crate::{database, models};
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use ariadne::ids::UserId;
|
||||
use futures::TryStreamExt;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("organizations", web::get().to(organizations_get));
|
||||
cfg.service(
|
||||
web::scope("organization")
|
||||
.route("", web::post().to(organization_create))
|
||||
.route("{id}/projects", web::get().to(organization_projects_get))
|
||||
.route("{id}/notes", web::patch().to(organization_notes_edit))
|
||||
.route("{id}", web::get().to(organization_get))
|
||||
.route("{id}", web::patch().to(organizations_edit))
|
||||
.route("{id}", web::delete().to(organization_delete))
|
||||
.route("{id}/projects", web::post().to(organization_projects_add))
|
||||
.route(
|
||||
"{id}/projects/{project_id}",
|
||||
web::delete().to(organization_projects_remove),
|
||||
)
|
||||
.route("{id}/icon", web::patch().to(organization_icon_edit))
|
||||
.route("{id}/icon", web::delete().to(delete_organization_icon))
|
||||
.route(
|
||||
"{id}/members",
|
||||
web::get().to(super::teams::team_members_get_organization),
|
||||
),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(organizations_get)
|
||||
.service(organization_create)
|
||||
.service(organization_projects_get)
|
||||
.service(organization_notes_edit)
|
||||
.service(organization_get)
|
||||
.service(organizations_edit)
|
||||
.service(organization_delete)
|
||||
.service(organization_projects_add)
|
||||
.service(organization_projects_remove)
|
||||
.service(organization_icon_edit)
|
||||
.service(delete_organization_icon);
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = OK)))]
|
||||
#[get("/organization/{id}/projects")]
|
||||
pub async fn organization_projects_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -106,7 +98,7 @@ pub async fn organization_projects_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct NewOrganization {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -120,6 +112,8 @@ pub struct NewOrganization {
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = OK)))]
|
||||
#[post("/organization")]
|
||||
pub async fn organization_create(
|
||||
req: HttpRequest,
|
||||
new_organization: web::Json<NewOrganization>,
|
||||
@@ -222,6 +216,8 @@ pub async fn organization_create(
|
||||
Ok(HttpResponse::Ok().json(organization))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = OK)))]
|
||||
#[get("/organization/{id}")]
|
||||
pub async fn organization_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -300,6 +296,8 @@ pub async fn organization_get(
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[patch("/organization/{id}/notes")]
|
||||
pub async fn organization_notes_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -380,6 +378,12 @@ pub struct OrganizationIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "organizations",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/organizations")]
|
||||
pub async fn organizations_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<OrganizationIds>,
|
||||
@@ -484,7 +488,7 @@ pub async fn organizations_get(
|
||||
Ok(HttpResponse::Ok().json(organizations))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct OrganizationEdit {
|
||||
#[validate(length(min = 3, max = 256))]
|
||||
pub description: Option<String>,
|
||||
@@ -497,6 +501,8 @@ pub struct OrganizationEdit {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[patch("/organization/{id}")]
|
||||
pub async fn organizations_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -658,6 +664,8 @@ pub async fn organizations_edit(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[delete("/organization/{id}")]
|
||||
pub async fn organization_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -823,10 +831,12 @@ pub async fn organization_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct OrganizationProjectAdd {
|
||||
pub project_id: String, // Also allow name/slug
|
||||
}
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[post("/organization/{id}/projects")]
|
||||
pub async fn organization_projects_add(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -985,13 +995,15 @@ pub async fn organization_projects_add(
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct OrganizationProjectRemoval {
|
||||
// A new owner must be supplied for the project.
|
||||
// That user must be a member of the organization, but not necessarily a member of the project.
|
||||
pub new_owner: UserId,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[delete("/organization/{id}/projects/{project_id}")]
|
||||
pub async fn organization_projects_remove(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String, String)>,
|
||||
@@ -1181,6 +1193,13 @@ pub struct Extension {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path(
|
||||
tag = "organizations",
|
||||
params(("ext" = String, Query)),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/organization/{id}/icon")]
|
||||
pub async fn organization_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
@@ -1288,6 +1307,8 @@ pub async fn organization_icon_edit(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "organizations", responses((status = NO_CONTENT)))]
|
||||
#[delete("/organization/{id}/icon")]
|
||||
pub async fn delete_organization_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::database::redis::RedisPool;
|
||||
use crate::env::ENV;
|
||||
use crate::models::ids::PayoutId;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::payouts::{PayoutMethodType, PayoutStatus, Withdrawal};
|
||||
use crate::models::payouts::{
|
||||
PayoutMethod, PayoutMethodType, PayoutStatus, Withdrawal,
|
||||
};
|
||||
use crate::queue::payouts::PayoutsQueue;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
@@ -31,10 +33,8 @@ use tracing::error;
|
||||
const COMPLIANCE_CHECK_DEBOUNCE: chrono::Duration =
|
||||
chrono::Duration::seconds(15);
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
cfg.service(paypal_webhook)
|
||||
.service(tremendous_webhook)
|
||||
.service(transaction_history)
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(transaction_history)
|
||||
.service(calculate_fees)
|
||||
.service(create_payout)
|
||||
.service(cancel_payout)
|
||||
@@ -49,7 +49,12 @@ pub struct RequestForm {
|
||||
form_type: users_compliance::FormType,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Submit a compliance form.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts",
|
||||
responses((status = OK, body = serde_json::Value)),
|
||||
)]
|
||||
#[post("/compliance")]
|
||||
pub async fn post_compliance_form(
|
||||
req: HttpRequest,
|
||||
@@ -148,7 +153,12 @@ pub async fn post_compliance_form(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Receive PayPal webhook.
|
||||
#[utoipa::path(
|
||||
tag = "payouts",
|
||||
request_body(content = String, content_type = "text/plain"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/_paypal")]
|
||||
pub async fn paypal_webhook(
|
||||
req: HttpRequest,
|
||||
@@ -306,7 +316,12 @@ pub async fn paypal_webhook(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Receive Tremendous webhook.
|
||||
#[utoipa::path(
|
||||
tag = "payouts",
|
||||
request_body(content = String, content_type = "text/plain"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/_tremendous")]
|
||||
pub async fn tremendous_webhook(
|
||||
req: HttpRequest,
|
||||
@@ -417,14 +432,19 @@ pub async fn tremendous_webhook(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct WithdrawalFees {
|
||||
pub net_usd: Decimal2dp,
|
||||
pub fee: Decimal2dp,
|
||||
pub exchange_rate: Option<Decimal>,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Calculate payout fees.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts",
|
||||
responses((status = OK, body = WithdrawalFees)),
|
||||
)]
|
||||
#[post("/fees")]
|
||||
pub async fn calculate_fees(
|
||||
req: HttpRequest,
|
||||
@@ -457,7 +477,11 @@ pub async fn calculate_fees(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Create a payout.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("")]
|
||||
pub async fn create_payout(
|
||||
req: HttpRequest,
|
||||
@@ -670,9 +694,12 @@ pub enum PayoutSource {
|
||||
Affilites,
|
||||
}
|
||||
|
||||
/// Get the history of when the authorized user got payouts available, and when
|
||||
/// Get transaction history.
|
||||
/// the user withdrew their payouts.
|
||||
#[utoipa::path(responses((status = OK, body = Vec<TransactionItem>)))]
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts", responses((status = OK, body = Vec<TransactionItem>))
|
||||
)]
|
||||
#[get("/history")]
|
||||
pub async fn transaction_history(
|
||||
req: HttpRequest,
|
||||
@@ -754,7 +781,11 @@ pub async fn transaction_history(
|
||||
Ok(web::Json(txn_items))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Cancel a payout.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
pub async fn cancel_payout(
|
||||
info: web::Path<(PayoutId,)>,
|
||||
@@ -863,7 +894,7 @@ pub struct MethodFilter {
|
||||
pub country: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum FormCompletionStatus {
|
||||
Unknown,
|
||||
@@ -873,7 +904,15 @@ pub enum FormCompletionStatus {
|
||||
Complete,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// List payment methods.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts",
|
||||
params(
|
||||
("country" = Option<String>, Query)
|
||||
),
|
||||
responses((status = OK, body = Vec<PayoutMethod>)),
|
||||
)]
|
||||
#[get("/methods")]
|
||||
pub async fn payment_methods(
|
||||
payouts_queue: web::Data<PayoutsQueue>,
|
||||
@@ -897,7 +936,7 @@ pub async fn payment_methods(
|
||||
Ok(HttpResponse::Ok().json(methods))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct UserBalance {
|
||||
pub available: Decimal,
|
||||
pub withdrawn_lifetime: Decimal,
|
||||
@@ -906,7 +945,20 @@ pub struct UserBalance {
|
||||
pub dates: HashMap<DateTime<Utc>, Decimal>,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct BalanceResponse {
|
||||
#[serde(flatten)]
|
||||
balance: UserBalance,
|
||||
requested_form_type: Option<users_compliance::FormType>,
|
||||
form_completion_status: Option<FormCompletionStatus>,
|
||||
}
|
||||
|
||||
/// Get account balance.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts",
|
||||
responses((status = OK, body = BalanceResponse)),
|
||||
)]
|
||||
#[get("/balance")]
|
||||
pub async fn get_balance(
|
||||
req: HttpRequest,
|
||||
@@ -924,14 +976,6 @@ pub async fn get_balance(
|
||||
.await?
|
||||
.1;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Response {
|
||||
#[serde(flatten)]
|
||||
balance: UserBalance,
|
||||
requested_form_type: Option<users_compliance::FormType>,
|
||||
form_completion_status: Option<FormCompletionStatus>,
|
||||
}
|
||||
|
||||
let balance = get_user_balance(user.id.into(), &pool).await?;
|
||||
|
||||
let mut requested_form_type = None;
|
||||
@@ -965,7 +1009,7 @@ pub async fn get_balance(
|
||||
);
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(Response {
|
||||
Ok(HttpResponse::Ok().json(BalanceResponse {
|
||||
balance,
|
||||
requested_form_type,
|
||||
form_completion_status,
|
||||
@@ -1123,21 +1167,30 @@ pub struct RevenueQuery {
|
||||
pub end: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct RevenueResponse {
|
||||
pub all_time: Decimal,
|
||||
pub all_time_available: Decimal,
|
||||
pub data: Vec<RevenueData>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct RevenueData {
|
||||
pub time: u64,
|
||||
pub revenue: Decimal,
|
||||
pub creator_revenue: Decimal,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get platform revenue.
|
||||
#[utoipa::path(
|
||||
context_path = "/payout",
|
||||
tag = "payouts",
|
||||
params(
|
||||
("start" = Option<String>, Query),
|
||||
("end" = Option<String>, Query)
|
||||
),
|
||||
responses((status = OK, body = RevenueResponse)),
|
||||
)]
|
||||
#[get("/platform_revenue")]
|
||||
pub async fn platform_revenue(
|
||||
query: web::Query<RevenueQuery>,
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::models::ids::{ImageId, OrganizationId, ProjectId, VersionId};
|
||||
use crate::models::images::{Image, ImageContext};
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::projects::{
|
||||
License, Link, MonetizationStatus, ProjectStatus,
|
||||
License, Link, MonetizationStatus, Project, ProjectStatus,
|
||||
SideTypesMigrationReviewStatus, VersionStatus,
|
||||
};
|
||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
@@ -44,9 +44,9 @@ use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use validator::Validate;
|
||||
|
||||
mod new;
|
||||
pub mod new;
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(project_create)
|
||||
.service(project_create_with_id)
|
||||
.configure(new::config);
|
||||
@@ -283,7 +283,16 @@ pub async fn undo_uploads(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Create a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing project metadata and files"
|
||||
),
|
||||
responses((status = OK, body = Project))
|
||||
)]
|
||||
#[post("")]
|
||||
pub async fn project_create(
|
||||
req: HttpRequest,
|
||||
@@ -361,10 +370,18 @@ pub async fn project_create_internal(
|
||||
result
|
||||
}
|
||||
|
||||
/// Allows creating a project with a specific ID.
|
||||
/// Create a project with a specific ID.
|
||||
///
|
||||
/// This is a testing endpoint only accessible behind an admin key.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing project metadata and files"
|
||||
),
|
||||
responses((status = OK, body = Project))
|
||||
)]
|
||||
#[post("/{id}", guard = "admin_key_guard")]
|
||||
pub async fn project_create_with_id(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -36,7 +36,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(create);
|
||||
}
|
||||
|
||||
@@ -110,11 +110,14 @@ pub struct ProjectCreate {
|
||||
pub components: exp::ProjectEdit,
|
||||
}
|
||||
|
||||
/// Creates a new project with the given components.
|
||||
/// Create a project from components.
|
||||
///
|
||||
/// Components must include `base` ([`exp::base::Project`]), and at least one
|
||||
/// other component.
|
||||
#[utoipa::path]
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = OK, body = ProjectId))
|
||||
)]
|
||||
#[put("")]
|
||||
pub async fn create(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -42,20 +42,17 @@ use eyre::eyre;
|
||||
use futures::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("search", web::get().to(project_search));
|
||||
cfg.service(project_search_post);
|
||||
cfg.route("projects", web::get().to(projects_get));
|
||||
cfg.route("projects", web::patch().to(projects_edit));
|
||||
cfg.route("projects_random", web::get().to(random_projects_get));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(project_search)
|
||||
.service(project_search_post)
|
||||
.service(projects_get_route)
|
||||
.service(projects_edit_route)
|
||||
.service(random_projects_get_route);
|
||||
}
|
||||
|
||||
pub fn utoipa_config(
|
||||
cfg: &mut utoipa_actix_web::service_config::ServiceConfig,
|
||||
) {
|
||||
pub fn project_config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(project_get)
|
||||
.service(project_get_check)
|
||||
.service(project_delete)
|
||||
@@ -99,6 +96,20 @@ pub struct RandomProjects {
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
params(("count" = u32, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/projects_random")]
|
||||
pub async fn random_projects_get_route(
|
||||
count: web::Query<RandomProjects>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
random_projects_get(count, pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn random_projects_get(
|
||||
web::Query(count): web::Query<RandomProjects>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -140,11 +151,32 @@ pub async fn random_projects_get(
|
||||
Ok(HttpResponse::Ok().json(projects_data))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct ProjectIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct ProjectCheckResponse {
|
||||
pub id: ProjectId,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/projects")]
|
||||
pub async fn projects_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<ProjectIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
projects_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn projects_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
@@ -174,9 +206,13 @@ pub async fn projects_get(
|
||||
Ok(HttpResponse::Ok().json(projects))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = OK, body = Project))
|
||||
)]
|
||||
#[get("/{id}")]
|
||||
async fn project_get(
|
||||
pub async fn project_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -305,9 +341,13 @@ pub struct EditProject {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path]
|
||||
/// Update a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/{id}")]
|
||||
async fn project_edit(
|
||||
pub async fn project_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1205,6 +1245,27 @@ pub async fn edit_project_categories(
|
||||
// pub total_hits: usize,
|
||||
// }
|
||||
|
||||
/// Search projects.
|
||||
#[utoipa::path(
|
||||
tag = "search",
|
||||
get,
|
||||
operation_id = "v3SearchProjects",
|
||||
params(
|
||||
("query" = Option<String>, Query, description = "The query to search for"),
|
||||
("facets" = Option<String>, Query, description = "Search facets JSON"),
|
||||
("filters" = Option<String>, Query, description = "Search filters JSON"),
|
||||
("new_filters" = Option<String>, Query, description = "Search filters JSON"),
|
||||
("index" = Option<String>, Query, description = "Search index to use"),
|
||||
("offset" = Option<String>, Query, description = "Search result offset"),
|
||||
("limit" = Option<String>, Query, description = "Maximum number of search results"),
|
||||
("version" = Option<String>, Query, description = "Game version to filter for")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = SearchResults),
|
||||
(status = 400, description = "Request was invalid, see given error")
|
||||
)
|
||||
)]
|
||||
#[get("/search")]
|
||||
pub async fn project_search(
|
||||
web::Query(info): web::Query<SearchQuery>,
|
||||
search_backend: web::Data<dyn SearchBackend>,
|
||||
@@ -1230,6 +1291,12 @@ pub async fn project_search(
|
||||
}
|
||||
|
||||
// for more complicated search queries
|
||||
/// Search projects.
|
||||
#[utoipa::path(
|
||||
tag = "search",
|
||||
request_body = serde_json::Value,
|
||||
responses((status = OK, body = SearchResults))
|
||||
)]
|
||||
#[post("/search")]
|
||||
pub async fn project_search_post(
|
||||
web::Json(info): web::Json<SearchRequest>,
|
||||
@@ -1241,9 +1308,13 @@ pub async fn project_search_post(
|
||||
}
|
||||
|
||||
//checks the validity of a project id or slug
|
||||
#[utoipa::path]
|
||||
/// Check project availability.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = OK, body = ProjectCheckResponse))
|
||||
)]
|
||||
#[get("/{id}/check")]
|
||||
async fn project_get_check(
|
||||
pub async fn project_get_check(
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -1262,21 +1333,25 @@ pub async fn project_get_check_internal(
|
||||
db_models::DBProject::get(&slug, &**pool, &redis).await?;
|
||||
|
||||
if let Some(project) = project_data {
|
||||
Ok(HttpResponse::Ok().json(json! ({
|
||||
"id": models::ids::ProjectId::from(project.inner.id)
|
||||
})))
|
||||
Ok(HttpResponse::Ok().json(ProjectCheckResponse {
|
||||
id: models::ids::ProjectId::from(project.inner.id),
|
||||
}))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct DependencyInfo {
|
||||
pub projects: Vec<Project>,
|
||||
pub versions: Vec<models::projects::Version>,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// List project dependencies.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = OK, body = DependencyInfo))
|
||||
)]
|
||||
#[get("/{project_id}/dependencies")]
|
||||
pub async fn dependency_list(
|
||||
req: HttpRequest,
|
||||
@@ -1400,7 +1475,7 @@ pub struct CategoryChanges<'a> {
|
||||
pub remove_categories: &'a Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct BulkEditProject {
|
||||
#[validate(length(max = 3))]
|
||||
pub categories: Option<Vec<String>>,
|
||||
@@ -1420,6 +1495,33 @@ pub struct BulkEditProject {
|
||||
pub link_urls: Option<HashMap<String, Option<String>>>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "projects",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/projects")]
|
||||
pub async fn projects_edit_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<ProjectIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
bulk_edit_project: web::Json<BulkEditProject>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
search_state: web::Data<SearchState>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
projects_edit(
|
||||
req,
|
||||
ids,
|
||||
pool,
|
||||
bulk_edit_project,
|
||||
redis,
|
||||
session_queue,
|
||||
search_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn projects_edit(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ProjectIds>,
|
||||
@@ -1722,9 +1824,18 @@ pub struct Extension {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path]
|
||||
/// Update a project icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
params(
|
||||
("ext" = String, Query)
|
||||
),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/{id}/icon")]
|
||||
async fn project_icon_edit(
|
||||
pub async fn project_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -1866,9 +1977,13 @@ pub async fn project_icon_edit_internal(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Delete a project icon.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}/icon")]
|
||||
async fn delete_project_icon(
|
||||
pub async fn delete_project_icon(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -1992,7 +2107,20 @@ pub struct GalleryCreateQuery {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path]
|
||||
/// Add a gallery item.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
params(
|
||||
("ext" = String, Query),
|
||||
("featured" = bool, Query),
|
||||
("name" = Option<String>, Query),
|
||||
("description" = Option<String>, Query),
|
||||
("ordering" = Option<i64>, Query)
|
||||
),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/{id}/gallery")]
|
||||
pub async fn add_gallery_item(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
@@ -2190,9 +2318,21 @@ pub struct GalleryEditQuery {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Update a gallery item.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
params(
|
||||
("url" = String, Query),
|
||||
("featured" = Option<bool>, Query),
|
||||
("name" = Option<String>, Query),
|
||||
("description" = Option<String>, Query),
|
||||
("ordering" = Option<i64>, Query)
|
||||
),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/{id}/gallery")]
|
||||
async fn edit_gallery_item(
|
||||
pub async fn edit_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryEditQuery>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -2379,9 +2519,17 @@ pub struct GalleryDeleteQuery {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Delete a gallery item.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects",
|
||||
params(
|
||||
("url" = String, Query)
|
||||
),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}/gallery")]
|
||||
async fn delete_gallery_item(
|
||||
pub async fn delete_gallery_item(
|
||||
req: HttpRequest,
|
||||
web::Query(item): web::Query<GalleryDeleteQuery>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -2514,9 +2662,13 @@ pub async fn delete_gallery_item_internal(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Delete a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}")]
|
||||
async fn project_delete(
|
||||
pub async fn project_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -2672,9 +2824,13 @@ pub async fn project_delete_internal(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Follow a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[post("/{id}/follow")]
|
||||
async fn project_follow(
|
||||
pub async fn project_follow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -2764,9 +2920,13 @@ pub async fn project_follow_internal(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Unfollow a project.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[delete("/{id}/follow")]
|
||||
async fn project_unfollow(
|
||||
pub async fn project_unfollow(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -2852,7 +3012,11 @@ pub async fn project_unfollow_internal(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// Get a project's organization.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "projects", responses((status = OK, body = models::organizations::Organization))
|
||||
)]
|
||||
#[get("/{id}/organization")]
|
||||
pub async fn project_get_organization(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -18,23 +18,23 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::img;
|
||||
use crate::util::routes::read_typed_from_payload;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use ariadne::ids::UserId;
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use chrono::Utc;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("report", web::post().to(report_create));
|
||||
cfg.route("report", web::get().to(reports));
|
||||
cfg.route("reports", web::get().to(reports_get));
|
||||
cfg.route("report/{id}", web::get().to(report_get));
|
||||
cfg.route("report/{id}", web::patch().to(report_edit));
|
||||
cfg.route("report/{id}", web::delete().to(report_delete));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(report_create_route)
|
||||
.service(reports_route)
|
||||
.service(reports_get_route)
|
||||
.service(report_get_route)
|
||||
.service(report_edit_route)
|
||||
.service(report_delete_route);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct CreateReport {
|
||||
pub report_type: String,
|
||||
pub item_id: String,
|
||||
@@ -46,6 +46,22 @@ pub struct CreateReport {
|
||||
pub uploaded_images: Vec<ImageId>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
request_body = CreateReport,
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[post("/report")]
|
||||
pub async fn report_create_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
body: web::Payload,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
report_create(req, pool, body, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn report_create(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -247,6 +263,26 @@ fn default_all() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
params(
|
||||
("count" = Option<u16>, Query),
|
||||
("offset" = Option<u32>, Query),
|
||||
("all" = Option<bool>, Query)
|
||||
),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/report")]
|
||||
pub async fn reports_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
request_opts: web::Query<ReportsRequestOptions>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
reports(req, pool, redis, request_opts, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn reports(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -322,6 +358,22 @@ pub struct ReportIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "reports",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/reports")]
|
||||
pub async fn reports_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<ReportIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
reports_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn reports_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ReportIds>,
|
||||
@@ -361,6 +413,18 @@ pub async fn reports_get(
|
||||
Ok(HttpResponse::Ok().json(all_reports))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "reports", responses((status = OK)))]
|
||||
#[get("/report/{id}")]
|
||||
pub async fn report_get_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
info: web::Path<(crate::models::ids::ReportId,)>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
report_get(req, pool, redis, info, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn report_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -395,13 +459,26 @@ pub async fn report_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditReport {
|
||||
#[validate(length(max = 65536))]
|
||||
pub body: Option<String>,
|
||||
pub closed: Option<bool>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "reports", responses((status = NO_CONTENT)))]
|
||||
#[patch("/report/{id}")]
|
||||
pub async fn report_edit_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
info: web::Path<(crate::models::ids::ReportId,)>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
edit_report: web::Json<EditReport>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
report_edit(req, pool, redis, info, session_queue, edit_report).await
|
||||
}
|
||||
|
||||
pub async fn report_edit(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -511,6 +588,18 @@ pub async fn report_edit(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "reports", responses((status = NO_CONTENT)))]
|
||||
#[delete("/report/{id}")]
|
||||
pub async fn report_delete_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
info: web::Path<(crate::models::ids::ReportId,)>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
report_delete(req, pool, info, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn report_delete(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::routes::v3::project_creation::UploadedFile;
|
||||
use crate::util::ext::MRPACK_MIME_TYPE;
|
||||
use actix_web::http::header::ContentLength;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
use bytes::BytesMut;
|
||||
use chrono::Utc;
|
||||
use futures_util::StreamExt;
|
||||
@@ -29,13 +29,33 @@ use hex::FromHex;
|
||||
const MAX_FILE_SIZE: usize = 500 * 1024 * 1024;
|
||||
const MAX_FILE_SIZE_TEXT: &str = "500 MB";
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route(
|
||||
"shared-instance/{id}/version",
|
||||
web::post().to(shared_instance_version_create),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(shared_instance_version_create);
|
||||
}
|
||||
|
||||
/// Create a shared instance version.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
post,
|
||||
params(
|
||||
("id" = SharedInstanceId, Path, description = "The ID of the shared instance")
|
||||
),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses(
|
||||
(status = 201, description = "Expected response to a valid request", body = SharedInstanceVersion),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["SHARED_INSTANCE_VERSION_CREATE"]))
|
||||
)]
|
||||
#[post("/shared-instance/{id}/version")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn shared_instance_version_create(
|
||||
req: HttpRequest,
|
||||
|
||||
@@ -19,33 +19,24 @@ use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::routes::read_typed_from_payload;
|
||||
use actix_web::web::{Data, Redirect};
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use futures_util::future::try_join_all;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("shared-instance", web::post().to(shared_instance_create));
|
||||
cfg.route("shared-instance", web::get().to(shared_instance_list));
|
||||
cfg.service(
|
||||
web::scope("shared-instance")
|
||||
.route("{id}", web::get().to(shared_instance_get))
|
||||
.route("{id}", web::patch().to(shared_instance_edit))
|
||||
.route("{id}", web::delete().to(shared_instance_delete))
|
||||
.route("{id}/version", web::get().to(shared_instance_version_list)),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("shared-instance-version")
|
||||
.route("{id}", web::get().to(shared_instance_version_get))
|
||||
.route("{id}", web::delete().to(shared_instance_version_delete))
|
||||
.route(
|
||||
"{id}/download",
|
||||
web::get().to(shared_instance_version_download),
|
||||
),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(shared_instance_create)
|
||||
.service(shared_instance_list)
|
||||
.service(shared_instance_get)
|
||||
.service(shared_instance_edit)
|
||||
.service(shared_instance_delete)
|
||||
.service(shared_instance_version_list)
|
||||
.service(shared_instance_version_get)
|
||||
.service(shared_instance_version_delete)
|
||||
.service(shared_instance_version_download);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct CreateSharedInstance {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -56,6 +47,12 @@ pub struct CreateSharedInstance {
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "shared instances",
|
||||
request_body = CreateSharedInstance,
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[post("/shared-instance")]
|
||||
pub async fn shared_instance_create(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -101,6 +98,8 @@ pub async fn shared_instance_create(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = OK)))]
|
||||
#[get("/shared-instance")]
|
||||
pub async fn shared_instance_list(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -148,6 +147,8 @@ pub async fn shared_instance_list(
|
||||
Ok(HttpResponse::Ok().json(instances))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = OK)))]
|
||||
#[get("/shared-instance/{id}")]
|
||||
pub async fn shared_instance_get(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -209,7 +210,7 @@ fn can_access_instance_privately(
|
||||
|| users.iter().any(|x| x.user_id == user.id.into())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Validate)]
|
||||
#[derive(Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditSharedInstance {
|
||||
#[validate(
|
||||
length(min = 3, max = 64),
|
||||
@@ -219,6 +220,12 @@ pub struct EditSharedInstance {
|
||||
pub public: Option<bool>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "shared instances",
|
||||
request_body = EditSharedInstance,
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/shared-instance/{id}")]
|
||||
pub async fn shared_instance_edit(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -299,6 +306,8 @@ pub async fn shared_instance_edit(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = NO_CONTENT)))]
|
||||
#[delete("/shared-instance/{id}")]
|
||||
pub async fn shared_instance_delete(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -355,6 +364,8 @@ pub async fn shared_instance_delete(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = OK)))]
|
||||
#[get("/shared-instance/{id}/version")]
|
||||
pub async fn shared_instance_version_list(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -401,6 +412,8 @@ pub async fn shared_instance_version_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = OK)))]
|
||||
#[get("/shared-instance-version/{id}")]
|
||||
pub async fn shared_instance_version_get(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -461,6 +474,8 @@ async fn can_access_instance_as_maybe_user(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = NO_CONTENT)))]
|
||||
#[delete("/shared-instance-version/{id}")]
|
||||
pub async fn shared_instance_version_delete(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
@@ -560,6 +575,8 @@ async fn delete_instance_version(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "shared instances", responses((status = TEMPORARY_REDIRECT)))]
|
||||
#[get("/shared-instance-version/{id}/download")]
|
||||
pub async fn shared_instance_version_download(
|
||||
req: HttpRequest,
|
||||
pool: Data<PgPool>,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::database::PgPool;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{HttpResponse, web};
|
||||
use actix_web::{HttpResponse, get, web};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("statistics", web::get().to(get_stats));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_stats_route);
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -14,6 +14,14 @@ pub struct V3Stats {
|
||||
pub files: Option<i64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "statistics", responses((status = OK)))]
|
||||
#[get("/statistics")]
|
||||
pub async fn get_stats_route(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
get_stats(pool).await
|
||||
}
|
||||
|
||||
pub async fn get_stats(
|
||||
pool: web::Data<PgPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
|
||||
@@ -8,25 +8,22 @@ use crate::database::models::loader_fields::{
|
||||
Game, Loader, LoaderField, LoaderFieldEnumValue, LoaderFieldType,
|
||||
};
|
||||
use crate::database::redis::RedisPool;
|
||||
use actix_web::{HttpResponse, web};
|
||||
use actix_web::{HttpResponse, get, web};
|
||||
|
||||
use crate::database::PgPool;
|
||||
use itertools::Itertools;
|
||||
use serde_json::Value;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("tag")
|
||||
.route("category", web::get().to(category_list))
|
||||
.route("loader", web::get().to(loader_list)),
|
||||
)
|
||||
.route("games", web::get().to(games_list))
|
||||
.route("loader_field", web::get().to(loader_fields_list))
|
||||
.route("license", web::get().to(license_list))
|
||||
.route("license/{id}", web::get().to(license_text))
|
||||
.route("link_platform", web::get().to(link_platform_list))
|
||||
.route("report_type", web::get().to(report_type_list))
|
||||
.route("project_type", web::get().to(project_type_list));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(category_list_route)
|
||||
.service(loader_list_route)
|
||||
.service(games_list_route)
|
||||
.service(loader_fields_list_route)
|
||||
.service(license_list_route)
|
||||
.service(license_text_route)
|
||||
.service(link_platform_list_route)
|
||||
.service(report_type_list_route)
|
||||
.service(project_type_list_route);
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -37,6 +34,15 @@ pub struct GameData {
|
||||
pub banner: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/games")]
|
||||
pub async fn games_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
games_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn games_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -63,6 +69,15 @@ pub struct CategoryData {
|
||||
pub header: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/tag/category")]
|
||||
pub async fn category_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
category_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn category_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -91,6 +106,15 @@ pub struct LoaderData {
|
||||
pub metadata: Value,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/tag/loader")]
|
||||
pub async fn loader_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
loader_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn loader_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -131,6 +155,23 @@ pub struct LoaderFieldsEnumQuery {
|
||||
}
|
||||
|
||||
// Provides the variants for any enumerable loader field.
|
||||
#[utoipa::path(
|
||||
tag = "tags",
|
||||
params(
|
||||
("loader_field" = String, Query),
|
||||
("filters" = Option<HashMap<String, Value>>, Query)
|
||||
),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/loader_field")]
|
||||
pub async fn loader_fields_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
web::Query(query): web::Query<LoaderFieldsEnumQuery>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
loader_fields_list(pool, web::Query(query), redis).await
|
||||
}
|
||||
|
||||
pub async fn loader_fields_list(
|
||||
pool: web::Data<PgPool>,
|
||||
query: web::Query<LoaderFieldsEnumQuery>,
|
||||
@@ -181,6 +222,12 @@ pub struct License {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/license")]
|
||||
pub async fn license_list_route() -> HttpResponse {
|
||||
license_list().await
|
||||
}
|
||||
|
||||
pub async fn license_list() -> HttpResponse {
|
||||
let licenses = spdx::identifiers::LICENSES;
|
||||
let mut results: Vec<License> = Vec::with_capacity(licenses.len());
|
||||
@@ -201,6 +248,14 @@ pub struct LicenseText {
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/license/{id}")]
|
||||
pub async fn license_text_route(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
license_text(params).await
|
||||
}
|
||||
|
||||
pub async fn license_text(
|
||||
params: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
@@ -231,6 +286,15 @@ pub struct LinkPlatformQueryData {
|
||||
pub donation: bool,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/link_platform")]
|
||||
pub async fn link_platform_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
link_platform_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn link_platform_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -247,6 +311,15 @@ pub async fn link_platform_list(
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/report_type")]
|
||||
pub async fn report_type_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
report_type_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn report_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -255,6 +328,15 @@ pub async fn report_type_list(
|
||||
Ok(HttpResponse::Ok().json(results))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "tags", responses((status = OK)))]
|
||||
#[get("/project_type")]
|
||||
pub async fn project_type_list_route(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
project_type_list(pool, redis).await
|
||||
}
|
||||
|
||||
pub async fn project_type_list(
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
|
||||
@@ -13,26 +13,20 @@ use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use crate::util::error::Context;
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||
use ariadne::ids::UserId;
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("teams", web::get().to(teams_get));
|
||||
|
||||
cfg.service(
|
||||
web::scope("team")
|
||||
.route("{id}/members", web::get().to(team_members_get))
|
||||
.route("{id}/members/{user_id}", web::patch().to(edit_team_member))
|
||||
.route(
|
||||
"{id}/members/{user_id}",
|
||||
web::delete().to(remove_team_member),
|
||||
)
|
||||
.route("{id}/members", web::post().to(add_team_member))
|
||||
.route("{id}/join", web::post().to(join_team))
|
||||
.route("{id}/owner", web::patch().to(transfer_ownership)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(teams_get_route)
|
||||
.service(team_members_get_route)
|
||||
.service(team_members_get_organization)
|
||||
.service(edit_team_member_route)
|
||||
.service(remove_team_member_route)
|
||||
.service(add_team_member_route)
|
||||
.service(join_team_route)
|
||||
.service(transfer_ownership_route);
|
||||
}
|
||||
|
||||
// Returns all members of a project,
|
||||
@@ -40,9 +34,13 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
// also the members of the organization's team if the project is associated with an organization
|
||||
// (Unlike team_members_get_project, which only returns the members of the project's team)
|
||||
// They can be differentiated by the "organization_permissions" field being null or not
|
||||
#[utoipa::path]
|
||||
/// Get a project's team members.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "teams", responses((status = OK, body = Vec<crate::models::teams::TeamMember>))
|
||||
)]
|
||||
#[get("/{project_id}/members")]
|
||||
async fn team_members_get_project(
|
||||
pub async fn team_members_get_project(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -136,6 +134,8 @@ pub async fn team_members_get_project_internal(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = OK, body = Vec<crate::models::teams::TeamMember>)))]
|
||||
#[get("/organization/{id}/members")]
|
||||
pub async fn team_members_get_organization(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -213,6 +213,18 @@ pub async fn team_members_get_organization(
|
||||
}
|
||||
|
||||
// Returns all members of a team, but not necessarily those of a project-team's organization (unlike team_members_get_project)
|
||||
#[utoipa::path(tag = "teams", responses((status = OK, body = Vec<crate::models::teams::TeamMember>)))]
|
||||
#[get("/team/{id}/members")]
|
||||
pub async fn team_members_get_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
team_members_get(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn team_members_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -278,6 +290,22 @@ pub struct TeamIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "teams",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/teams")]
|
||||
pub async fn teams_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<TeamIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
teams_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn teams_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<TeamIds>,
|
||||
@@ -348,6 +376,18 @@ pub async fn teams_get(
|
||||
Ok(HttpResponse::Ok().json(teams))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = NO_CONTENT)))]
|
||||
#[post("/team/{id}/join")]
|
||||
pub async fn join_team_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
join_team(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn join_team(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -417,7 +457,7 @@ fn default_ordering() -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct NewTeamMember {
|
||||
pub user_id: UserId,
|
||||
#[serde(default = "default_role")]
|
||||
@@ -433,6 +473,19 @@ pub struct NewTeamMember {
|
||||
pub ordering: i64,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = NO_CONTENT)))]
|
||||
#[post("/team/{id}/members")]
|
||||
pub async fn add_team_member_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_member: web::Json<NewTeamMember>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
add_team_member(req, info, pool, new_member, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn add_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -678,7 +731,7 @@ pub async fn add_team_member(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
#[derive(Serialize, Deserialize, Clone, utoipa::ToSchema)]
|
||||
pub struct EditTeamMember {
|
||||
pub permissions: Option<ProjectPermissions>,
|
||||
pub organization_permissions: Option<OrganizationPermissions>,
|
||||
@@ -687,6 +740,19 @@ pub struct EditTeamMember {
|
||||
pub ordering: Option<i64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = NO_CONTENT)))]
|
||||
#[patch("/team/{id}/members/{user_id}")]
|
||||
pub async fn edit_team_member_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, String)>,
|
||||
pool: web::Data<PgPool>,
|
||||
edit_member: web::Json<EditTeamMember>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
edit_team_member(req, info, pool, edit_member, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn edit_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, String)>,
|
||||
@@ -882,11 +948,24 @@ pub async fn edit_team_member(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct TransferOwnership {
|
||||
pub user_id: UserId,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = NO_CONTENT)))]
|
||||
#[patch("/team/{id}/owner")]
|
||||
pub async fn transfer_ownership_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_owner: web::Json<TransferOwnership>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
transfer_ownership(req, info, pool, new_owner, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn transfer_ownership(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId,)>,
|
||||
@@ -1072,6 +1151,18 @@ pub async fn transfer_ownership(
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "teams", responses((status = NO_CONTENT)))]
|
||||
#[delete("/team/{id}/members/{user_id}")]
|
||||
pub async fn remove_team_member_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
remove_team_member(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn remove_team_member(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(TeamId, UserId)>,
|
||||
|
||||
@@ -16,20 +16,15 @@ use crate::models::threads::{MessageBody, Thread, ThreadType};
|
||||
use crate::models::users::User;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use futures::TryStreamExt;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("thread")
|
||||
.route("{id}", web::get().to(thread_get))
|
||||
.route("{id}", web::post().to(thread_send_message)),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("message").route("{id}", web::delete().to(message_delete)),
|
||||
);
|
||||
cfg.route("threads", web::get().to(threads_get));
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(thread_get_route)
|
||||
.service(thread_send_message_route)
|
||||
.service(message_delete_route)
|
||||
.service(threads_get_route);
|
||||
}
|
||||
|
||||
pub async fn is_authorized_thread(
|
||||
@@ -267,6 +262,18 @@ pub async fn filter_authorized_threads(
|
||||
Ok(final_threads)
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "threads", responses((status = OK, body = Thread)))]
|
||||
#[get("/thread/{id}")]
|
||||
pub async fn thread_get_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
thread_get(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn thread_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
@@ -324,6 +331,22 @@ pub struct ThreadIds {
|
||||
pub ids: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "threads",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK, body = Vec<Thread>))
|
||||
)]
|
||||
#[get("/threads")]
|
||||
pub async fn threads_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<ThreadIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
threads_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn threads_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<ThreadIds>,
|
||||
@@ -356,11 +379,25 @@ pub async fn threads_get(
|
||||
Ok(HttpResponse::Ok().json(threads))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct NewThreadMessage {
|
||||
pub body: MessageBody,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "threads", responses((status = NO_CONTENT)))]
|
||||
#[post("/thread/{id}")]
|
||||
pub async fn thread_send_message_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
new_message: web::Json<NewThreadMessage>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
thread_send_message(req, info, pool, new_message, redis, session_queue)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn thread_send_message(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadId,)>,
|
||||
@@ -591,6 +628,19 @@ pub async fn thread_send_message_internal(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "threads", responses((status = NO_CONTENT)))]
|
||||
#[delete("/message/{id}")]
|
||||
pub async fn message_delete_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadMessageId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
file_host: web::Data<dyn FileHost>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
message_delete(req, info, pool, redis, session_queue, file_host).await
|
||||
}
|
||||
|
||||
pub async fn message_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(ThreadMessageId,)>,
|
||||
|
||||
@@ -31,33 +31,29 @@ use crate::{
|
||||
validate::validation_errors_to_string,
|
||||
},
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use ariadne::ids::UserId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route("user", web::get().to(user_auth_get));
|
||||
cfg.route("users", web::get().to(users_get));
|
||||
cfg.route("users/search", web::get().to(users_search));
|
||||
cfg.route("user_email", web::get().to(admin_user_email));
|
||||
|
||||
cfg.service(
|
||||
web::scope("user")
|
||||
.route("{user_id}/all-projects", web::get().to(all_projects))
|
||||
.route("{user_id}/projects", web::get().to(projects_list))
|
||||
.route("{id}/notes", web::patch().to(user_notes_edit))
|
||||
.route("{id}", web::get().to(user_get))
|
||||
.route("{user_id}/collections", web::get().to(collections_list))
|
||||
.route("{user_id}/organizations", web::get().to(orgs_list))
|
||||
.route("{id}", web::patch().to(user_edit))
|
||||
.route("{id}/icon", web::patch().to(user_icon_edit))
|
||||
.route("{id}/icon", web::delete().to(user_icon_delete))
|
||||
.route("{id}", web::delete().to(user_delete))
|
||||
.route("{id}/follows", web::get().to(user_follows))
|
||||
.route("{id}/notifications", web::get().to(user_notifications))
|
||||
.route("{id}/oauth_apps", web::get().to(get_user_clients)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(user_auth_get_route)
|
||||
.service(users_get_route)
|
||||
.service(users_search)
|
||||
.service(admin_user_email)
|
||||
.service(all_projects)
|
||||
.service(projects_list_route)
|
||||
.service(user_notes_edit)
|
||||
.service(user_get_route)
|
||||
.service(collections_list)
|
||||
.service(orgs_list)
|
||||
.service(user_edit_route)
|
||||
.service(user_icon_edit_route)
|
||||
.service(user_icon_delete_route)
|
||||
.service(user_delete_route)
|
||||
.service(user_follows_route)
|
||||
.service(user_notifications_route)
|
||||
.service(get_user_clients);
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -71,6 +67,8 @@ pub struct UserEmailQuery {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{user_id}/all-projects")]
|
||||
pub async fn all_projects(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -200,6 +198,12 @@ pub async fn all_projects(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "users",
|
||||
params(("email" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/user_email")]
|
||||
pub async fn admin_user_email(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -254,6 +258,18 @@ pub async fn admin_user_email(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{user_id}/projects")]
|
||||
pub async fn projects_list_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
projects_list(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn projects_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -291,6 +307,17 @@ pub async fn projects_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user")]
|
||||
pub async fn user_auth_get_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_auth_get(req, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_auth_get(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -333,6 +360,12 @@ pub struct UserSearchQuery {
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "users",
|
||||
params(("query" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/users/search")]
|
||||
pub async fn users_search(
|
||||
web::Query(query): web::Query<UserSearchQuery>,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -348,6 +381,22 @@ pub async fn users_search(
|
||||
Ok(web::Json(users))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "users",
|
||||
params(("ids" = String, Query)),
|
||||
responses((status = OK))
|
||||
)]
|
||||
#[get("/users")]
|
||||
pub async fn users_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<UserIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
users_get(req, ids, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn users_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<UserIds>,
|
||||
@@ -396,6 +445,18 @@ pub async fn users_get(
|
||||
Ok(HttpResponse::Ok().json(users))
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{id}")]
|
||||
pub async fn user_get_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_get(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -439,6 +500,8 @@ pub async fn user_get(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = NO_CONTENT)))]
|
||||
#[patch("/user/{id}/notes")]
|
||||
pub async fn user_notes_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -506,6 +569,8 @@ pub async fn user_notes_edit(
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{user_id}/collections")]
|
||||
pub async fn collections_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -545,6 +610,8 @@ pub async fn collections_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{user_id}/organizations")]
|
||||
pub async fn orgs_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -640,7 +707,7 @@ pub async fn orgs_list(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
#[derive(Serialize, Deserialize, Validate, utoipa::ToSchema)]
|
||||
pub struct EditUser {
|
||||
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||
pub username: Option<String>,
|
||||
@@ -658,6 +725,19 @@ pub struct EditUser {
|
||||
pub allow_friend_requests: Option<bool>,
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = NO_CONTENT)))]
|
||||
#[patch("/user/{id}")]
|
||||
pub async fn user_edit_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
new_user: web::Json<EditUser>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_edit(req, info, new_user, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -827,6 +907,36 @@ pub struct Extension {
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[utoipa::path(
|
||||
tag = "users",
|
||||
params(("ext" = String, Query)),
|
||||
request_body(content = Vec<u8>, content_type = "application/octet-stream"),
|
||||
responses((status = NO_CONTENT))
|
||||
)]
|
||||
#[patch("/user/{id}/icon")]
|
||||
pub async fn user_icon_edit_route(
|
||||
ext: web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<dyn FileHost>,
|
||||
payload: web::Payload,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_icon_edit(
|
||||
ext,
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
file_host,
|
||||
payload,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user_icon_edit(
|
||||
web::Query(ext): web::Query<Extension>,
|
||||
req: HttpRequest,
|
||||
@@ -903,6 +1013,19 @@ pub async fn user_icon_edit(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = NO_CONTENT)))]
|
||||
#[delete("/user/{id}/icon")]
|
||||
pub async fn user_icon_delete_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_host: web::Data<dyn FileHost>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_icon_delete(req, info, pool, redis, file_host, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_icon_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -957,6 +1080,18 @@ pub async fn user_icon_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = NO_CONTENT)))]
|
||||
#[delete("/user/{id}")]
|
||||
pub async fn user_delete_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<(), ApiError> {
|
||||
user_delete(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -1005,6 +1140,18 @@ pub async fn user_delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{id}/follows")]
|
||||
pub async fn user_follows_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_follows(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_follows(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -1047,6 +1194,18 @@ pub async fn user_follows(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(tag = "users", responses((status = OK)))]
|
||||
#[get("/user/{id}/notifications")]
|
||||
pub async fn user_notifications_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
user_notifications(req, info, pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn user_notifications(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::util::validate::validation_errors_to_string;
|
||||
use crate::validate::{ValidationResult, validate_file};
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use hex::ToHex;
|
||||
@@ -104,6 +104,48 @@ struct InitialFileData {
|
||||
}
|
||||
|
||||
// under `/api/v1/version`
|
||||
/// Create a version on an existing project.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
post,
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing `data` and uploaded files"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = Version),
|
||||
(status = 400, description = "Request was invalid, see given error"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_CREATE"]))
|
||||
)]
|
||||
#[post("/version")]
|
||||
pub async fn version_create_route(
|
||||
req: HttpRequest,
|
||||
payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: Data<dyn FileHost>,
|
||||
session_queue: Data<AuthQueue>,
|
||||
http: web::Data<HttpClient>,
|
||||
search_state: Data<SearchState>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
version_create(
|
||||
req,
|
||||
payload,
|
||||
client,
|
||||
redis,
|
||||
file_host,
|
||||
session_queue,
|
||||
http,
|
||||
search_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn version_create(
|
||||
req: HttpRequest,
|
||||
mut payload: Multipart,
|
||||
@@ -529,6 +571,56 @@ async fn version_create_inner(
|
||||
Ok((HttpResponse::Ok().json(response), project_id))
|
||||
}
|
||||
|
||||
/// Add files to an existing version.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
post,
|
||||
params(
|
||||
("version_id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
request_body(
|
||||
content(("multipart/form-data")),
|
||||
description = "Multipart payload containing files to upload"
|
||||
),
|
||||
responses(
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_WRITE"]))
|
||||
)]
|
||||
#[post("/version/{version_id}/file")]
|
||||
pub async fn upload_file_to_version_route(
|
||||
req: HttpRequest,
|
||||
url_data: web::Path<(VersionId,)>,
|
||||
payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
redis: Data<RedisPool>,
|
||||
file_host: Data<dyn FileHost>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
http: web::Data<HttpClient>,
|
||||
search_state: Data<SearchState>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
upload_file_to_version(
|
||||
req,
|
||||
url_data,
|
||||
payload,
|
||||
client,
|
||||
redis,
|
||||
file_host,
|
||||
session_queue,
|
||||
http,
|
||||
search_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn upload_file_to_version(
|
||||
req: HttpRequest,
|
||||
url_data: web::Path<(VersionId,)>,
|
||||
|
||||
@@ -10,32 +10,56 @@ use crate::models::projects::{ProjectStatus, VersionStatus, VersionType};
|
||||
use crate::models::teams::ProjectPermissions;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::routes::internal::delphi;
|
||||
use crate::routes::{FileHash, HashAlgorithm};
|
||||
use crate::{database, models};
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
|
||||
use dashmap::DashMap;
|
||||
use futures::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
web::scope("version_file")
|
||||
.route("{version_id}", web::get().to(get_version_from_hash))
|
||||
.route("{version_id}/update", web::post().to(get_update_from_hash))
|
||||
.route("project", web::post().to(get_projects_from_hashes))
|
||||
.route("{version_id}", web::delete().to(delete_file))
|
||||
.route("{version_id}/download", web::get().to(download_version)),
|
||||
);
|
||||
cfg.service(
|
||||
web::scope("version_files")
|
||||
// DEPRECATED - use `update_many` instead
|
||||
// see `fn update_files` comment
|
||||
.route("update", web::post().to(update_files))
|
||||
.route("update_many", web::post().to(update_files_many))
|
||||
.route("update_individual", web::post().to(update_individual_files))
|
||||
.route("", web::post().to(get_versions_from_hashes)),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(get_version_from_hash_route)
|
||||
.service(get_update_from_hash_route)
|
||||
.service(get_projects_from_hashes_route)
|
||||
.service(delete_file_route)
|
||||
.service(download_version_route)
|
||||
.service(update_files_route)
|
||||
.service(update_files_many_route)
|
||||
.service(update_individual_files_route)
|
||||
.service(get_versions_from_hashes_route);
|
||||
}
|
||||
|
||||
/// Get version metadata by file hash.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
get,
|
||||
operation_id = "v3VersionFromHash",
|
||||
params(
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<String>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = models::projects::Version),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/version_file/{version_id}")]
|
||||
pub async fn get_version_from_hash_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
get_version_from_hash(req, info, pool, redis, hash_query, session_queue)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_version_from_hash(
|
||||
@@ -89,7 +113,7 @@ pub async fn get_version_from_hash(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct HashQuery {
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub version_id: Option<VersionId>,
|
||||
@@ -110,7 +134,7 @@ pub fn default_algorithm_from_hashes(hashes: &[String]) -> String {
|
||||
"sha1".into()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateData {
|
||||
pub loaders: Option<Vec<String>>,
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
@@ -123,6 +147,47 @@ pub struct UpdateData {
|
||||
pub loader_fields: Option<HashMap<String, Vec<serde_json::Value>>>,
|
||||
}
|
||||
|
||||
/// Get the latest matching version by file hash.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3UpdateFromHash",
|
||||
params(
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<String>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
request_body = UpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = models::projects::Version),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[post("/version_file/{version_id}/update")]
|
||||
pub async fn get_update_from_hash_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
update_data: web::Json<UpdateData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
get_update_from_hash(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
hash_query,
|
||||
update_data,
|
||||
session_queue,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_update_from_hash(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -208,12 +273,40 @@ pub async fn get_update_from_hash(
|
||||
}
|
||||
|
||||
// Requests above with multiple versions below
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct FileHashes {
|
||||
/// Hash algorithm to use (sha1 or sha512)
|
||||
#[schema(value_type = Option<HashAlgorithm>)]
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
#[schema(value_type = Vec<FileHash>)]
|
||||
pub hashes: Vec<String>,
|
||||
}
|
||||
|
||||
/// Get versions by file hashes.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3VersionsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<String, models::projects::Version>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[post("/version_files")]
|
||||
pub async fn get_versions_from_hashes_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_data: web::Json<FileHashes>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
get_versions_from_hashes(req, pool, redis, file_data, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn get_versions_from_hashes(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
@@ -269,6 +362,31 @@ pub async fn get_versions_from_hashes(
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
/// Get projects by file hashes.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3ProjectsFromHashes",
|
||||
request_body = FileHashes,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<String, models::projects::Project>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[post("/version_file/project")]
|
||||
pub async fn get_projects_from_hashes_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
file_data: web::Json<FileHashes>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
get_projects_from_hashes(req, pool, redis, file_data, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn get_projects_from_hashes(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -327,7 +445,7 @@ pub async fn get_projects_from_hashes(
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyUpdateData {
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub hashes: Vec<String>,
|
||||
@@ -336,6 +454,26 @@ pub struct ManyUpdateData {
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
/// Get latest matching versions by file hashes.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3UpdateFilesMany",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<String, Vec<models::projects::Version>>)
|
||||
)
|
||||
)]
|
||||
#[post("/version_files/update_many")]
|
||||
pub async fn update_files_many_route(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
update_data: web::Json<ManyUpdateData>,
|
||||
) -> Result<web::Json<HashMap<String, Vec<models::projects::Version>>>, ApiError>
|
||||
{
|
||||
update_files_many(pool, redis, update_data).await
|
||||
}
|
||||
|
||||
pub async fn update_files_many(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -368,6 +506,25 @@ pub async fn update_files_many(
|
||||
// This endpoint is kept for backwards compat, since it still works in 99% of
|
||||
// cases where H only maps to a single version, and for older clients. This
|
||||
// endpoint will only take the first version for each file hash.
|
||||
/// Get the latest matching version by file hash.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3UpdateFiles",
|
||||
request_body = ManyUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<String, models::projects::Version>)
|
||||
)
|
||||
)]
|
||||
#[post("/version_files/update")]
|
||||
pub async fn update_files_route(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
update_data: web::Json<ManyUpdateData>,
|
||||
) -> Result<web::Json<HashMap<String, models::projects::Version>>, ApiError> {
|
||||
update_files(pool, redis, update_data).await
|
||||
}
|
||||
|
||||
pub async fn update_files(
|
||||
pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
@@ -468,7 +625,7 @@ async fn update_files_internal(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct FileUpdateData {
|
||||
pub hash: String,
|
||||
pub loaders: Option<Vec<String>>,
|
||||
@@ -476,12 +633,33 @@ pub struct FileUpdateData {
|
||||
pub version_types: Option<Vec<VersionType>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct ManyFileUpdateData {
|
||||
pub algorithm: Option<String>, // Defaults to calculation based on size of hash
|
||||
pub hashes: Vec<FileUpdateData>,
|
||||
}
|
||||
|
||||
/// Get latest matching versions by individual file filters.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
post,
|
||||
operation_id = "v3UpdateIndividualFiles",
|
||||
request_body = ManyFileUpdateData,
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = HashMap<String, models::projects::Version>)
|
||||
)
|
||||
)]
|
||||
#[post("/version_files/update_individual")]
|
||||
pub async fn update_individual_files_route(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
update_data: web::Json<ManyFileUpdateData>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
update_individual_files(req, pool, redis, update_data, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn update_individual_files(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
@@ -603,6 +781,40 @@ pub async fn update_individual_files(
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}
|
||||
/// Delete a file by hash.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
delete,
|
||||
operation_id = "v3DeleteFileFromHash",
|
||||
params(
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<String>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<VersionId>, Query, description = "Optional version ID to delete from")
|
||||
),
|
||||
responses(
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[delete("/version_file/{version_id}")]
|
||||
pub async fn delete_file_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
delete_file(req, info, pool, redis, hash_query, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn delete_file(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
@@ -740,12 +952,42 @@ pub async fn delete_file(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct DownloadRedirect {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
// under /api/v1/version_file/{hash}/download
|
||||
/// Download a file by hash.
|
||||
#[utoipa::path(
|
||||
tag = "version files",
|
||||
get,
|
||||
operation_id = "v3DownloadVersionFromHash",
|
||||
params(
|
||||
("version_id" = String, Path, description = "The hexadecimal file hash"),
|
||||
("algorithm" = Option<String>, Query, description = "Hash algorithm to use (sha1 or sha512)"),
|
||||
("version_id" = Option<VersionId>, Query, description = "Optional version ID when hash maps to multiple files")
|
||||
),
|
||||
responses(
|
||||
(status = 302, description = "Temporary redirect to file URL", body = DownloadRedirect),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/version_file/{version_id}/download")]
|
||||
pub async fn download_version_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
hash_query: web::Query<HashQuery>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
download_version(req, info, pool, redis, hash_query, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn download_version(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
|
||||
@@ -31,33 +31,34 @@ use crate::search::{SearchBackend, SearchState};
|
||||
use crate::util::error::Context;
|
||||
use crate::util::img;
|
||||
use crate::util::validate::validation_errors_to_string;
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||
use ariadne::ids::base62_impl::parse_base62;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.route(
|
||||
"version",
|
||||
web::post().to(super::version_creation::version_create),
|
||||
);
|
||||
cfg.route("versions", web::get().to(versions_get));
|
||||
|
||||
cfg.service(
|
||||
web::scope("version")
|
||||
.route("{id}", web::get().to(version_get))
|
||||
.route("{id}", web::patch().to(version_edit))
|
||||
.route("{id}", web::delete().to(version_delete))
|
||||
.route(
|
||||
"{version_id}/file",
|
||||
web::post().to(super::version_creation::upload_file_to_version),
|
||||
),
|
||||
);
|
||||
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
|
||||
cfg.service(super::version_creation::version_create_route)
|
||||
.service(versions_get_route)
|
||||
.service(version_get_route)
|
||||
.service(version_edit_route)
|
||||
.service(version_delete_route)
|
||||
.service(super::version_creation::upload_file_to_version_route);
|
||||
}
|
||||
|
||||
// Given a project ID/slug and a version slug
|
||||
#[utoipa::path]
|
||||
/// Get a project version.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "versions",
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = models::projects::Version),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{project_id}/version/{slug}")]
|
||||
pub async fn version_project_get(
|
||||
req: HttpRequest,
|
||||
@@ -162,6 +163,28 @@ fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Get multiple versions by ID.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
get,
|
||||
params(
|
||||
("ids" = String, Query, description = "The JSON array of version IDs"),
|
||||
("include_changelog" = Option<bool>, Query, description = "Whether to include changelog fields")
|
||||
),
|
||||
responses((status = 200, description = "Expected response to a valid request", body = Vec<models::projects::Version>))
|
||||
)]
|
||||
#[get("/versions")]
|
||||
pub async fn versions_get_route(
|
||||
req: HttpRequest,
|
||||
ids: web::Query<VersionIds>,
|
||||
pool: web::Data<PgPool>,
|
||||
ro_pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
versions_get(req, ids, pool, ro_pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn versions_get(
|
||||
req: HttpRequest,
|
||||
web::Query(ids): web::Query<VersionIds>,
|
||||
@@ -211,6 +234,33 @@ pub async fn versions_get(
|
||||
Ok(HttpResponse::Ok().json(versions))
|
||||
}
|
||||
|
||||
/// Get a version by ID.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
get,
|
||||
params(
|
||||
("id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = models::projects::Version),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/version/{id}")]
|
||||
pub async fn version_get_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
ro_pool: web::Data<ReadOnlyPgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<web::Json<models::projects::Version>, ApiError> {
|
||||
version_get(req, info, pool, ro_pool, redis, session_queue).await
|
||||
}
|
||||
|
||||
pub async fn version_get(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(models::ids::VersionId,)>,
|
||||
@@ -330,6 +380,48 @@ pub struct EditVersionFileType {
|
||||
pub file_type: Option<FileType>,
|
||||
}
|
||||
|
||||
/// Update an existing version.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
patch,
|
||||
params(
|
||||
("id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
responses(
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_WRITE"]))
|
||||
)]
|
||||
#[patch("/version/{id}")]
|
||||
pub async fn version_edit_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
new_version: web::Json<serde_json::Value>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
search_state: web::Data<SearchState>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
version_edit(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
new_version,
|
||||
session_queue,
|
||||
search_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn version_edit(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
@@ -798,9 +890,29 @@ pub struct VersionListFilters {
|
||||
pub include_changelog: bool,
|
||||
}
|
||||
|
||||
#[utoipa::path]
|
||||
/// List project versions.
|
||||
#[utoipa::path(
|
||||
context_path = "/project",
|
||||
tag = "versions",
|
||||
params(
|
||||
("loaders" = Option<String>, Query),
|
||||
("featured" = Option<bool>, Query),
|
||||
("version_type" = Option<VersionType>, Query),
|
||||
("limit" = Option<usize>, Query),
|
||||
("offset" = Option<usize>, Query),
|
||||
("loader_fields" = Option<String>, Query),
|
||||
("include_changelog" = Option<bool>, Query)
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Expected response to a valid request", body = Vec<models::projects::Version>),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
)
|
||||
)]
|
||||
#[get("/{project_id}/version")]
|
||||
async fn version_list(
|
||||
pub async fn version_list(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
web::Query(filters): web::Query<VersionListFilters>,
|
||||
@@ -990,6 +1102,48 @@ pub async fn version_list_internal(
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a version by ID.
|
||||
#[utoipa::path(
|
||||
tag = "versions",
|
||||
delete,
|
||||
params(
|
||||
("id" = VersionId, Path, description = "The ID of the version")
|
||||
),
|
||||
responses(
|
||||
(status = NO_CONTENT, description = "Expected response to a valid request"),
|
||||
(
|
||||
status = 401,
|
||||
description = "Incorrect token scopes or no authorization to access the requested item(s)"
|
||||
),
|
||||
(
|
||||
status = 404,
|
||||
description = "The requested item(s) were not found or no authorization to access the requested item(s)"
|
||||
)
|
||||
),
|
||||
security(("bearer_auth" = ["VERSION_DELETE"]))
|
||||
)]
|
||||
#[delete("/version/{id}")]
|
||||
pub async fn version_delete_route(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
search_backend: web::Data<dyn SearchBackend>,
|
||||
search_state: web::Data<SearchState>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
version_delete(
|
||||
req,
|
||||
info,
|
||||
pool,
|
||||
redis,
|
||||
session_queue,
|
||||
search_backend,
|
||||
search_state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn version_delete(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(VersionId,)>,
|
||||
|
||||
@@ -285,7 +285,7 @@ pub struct UploadSearchProject {
|
||||
pub loader_fields: HashMap<String, Vec<serde_json::Value>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
pub struct SearchProjectDependency {
|
||||
pub project_id: String,
|
||||
pub dependency_type: DependencyType,
|
||||
@@ -294,7 +294,7 @@ pub struct SearchProjectDependency {
|
||||
pub icon_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, ToSchema)]
|
||||
pub struct SearchResults {
|
||||
pub hits: Vec<ResultSearchProject>,
|
||||
pub page: usize,
|
||||
@@ -302,7 +302,7 @@ pub struct SearchResults {
|
||||
pub total_hits: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
pub struct ResultSearchProject {
|
||||
pub version_id: String,
|
||||
pub project_id: String,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user