FlameAnvil Project Sync (#481)
* FlameAnvil Project Sync * Perm fixes * Fix compile * Fix clippy + run prepare
This commit is contained in:
parent
589761bfd9
commit
f259d81249
2
.env
2
.env
@ -1,6 +1,8 @@
|
||||
DEBUG=true
|
||||
RUST_LOG=info,sqlx::query=warn
|
||||
|
||||
SENTRY_DSN=none
|
||||
|
||||
SITE_URL=https://modrinth.com
|
||||
CDN_URL=https://staging-cdn.modrinth.com
|
||||
LABRINTH_ADMIN_KEY=feedbeef
|
||||
|
||||
242
Cargo.lock
generated
242
Cargo.lock
generated
@ -250,6 +250,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@ -413,6 +422,21 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
@ -579,6 +603,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chunked_transfer"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@ -851,6 +881,16 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.17"
|
||||
@ -1144,6 +1184,12 @@ dependencies = [
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
@ -1233,6 +1279,17 @@ dependencies = [
|
||||
"digest 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
@ -1473,7 +1530,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "labrinth"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-cors",
|
||||
@ -1502,6 +1559,8 @@ dependencies = [
|
||||
"reqwest",
|
||||
"rust-s3",
|
||||
"rust_decimal",
|
||||
"sentry",
|
||||
"sentry-actix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
@ -1634,6 +1693,12 @@ dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
@ -1698,6 +1763,16 @@ version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minidom"
|
||||
version = "0.15.0"
|
||||
@ -1822,6 +1897,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
@ -1895,6 +1979,17 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4750134fb6a5d49afc80777394ad5d95b04bc12068c6abb92fae8f43817270f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.0.0"
|
||||
@ -2196,6 +2291,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
@ -2281,6 +2377,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@ -2397,6 +2499,101 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
|
||||
|
||||
[[package]]
|
||||
name = "sentry"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a120fb5e8b7975736bf1fc57de380531e617a6a8f5a55d037bcea25a7f5e8371"
|
||||
dependencies = [
|
||||
"httpdate",
|
||||
"native-tls",
|
||||
"reqwest",
|
||||
"sentry-backtrace",
|
||||
"sentry-contexts",
|
||||
"sentry-core",
|
||||
"sentry-panic",
|
||||
"tokio",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-actix"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b889eb376e04a7f3b61aee3ed158c90dac12671042e07b6f54452be099ba5db7"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"futures-util",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-backtrace"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ac56ff9aae25b024a5aad4f0242808dfde29161c82d183adce778338c6822ef"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-contexts"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "188506b08b5e64004c71b7a5edb34959083e6e1288fada3b8d18d0bc7449ce1e"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"libc",
|
||||
"os_info",
|
||||
"rustc_version",
|
||||
"sentry-core",
|
||||
"uname",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-core"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff58433a7ad557b586a09c42c4298d5f3ddb0c777e1a79d950e510d7b93fce0e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rand",
|
||||
"sentry-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-panic"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4145005d9b5c117132765c34e2cb33e9d24d16e73d7f3a357122b77fe3a3b815"
|
||||
dependencies = [
|
||||
"sentry-backtrace",
|
||||
"sentry-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-types"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb30d75498a041005a774ec1b6b7d9589c5906d17ebaca338cb685dc92170f9b"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"debugid",
|
||||
"getrandom",
|
||||
"hex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time 0.3.16",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
@ -2973,12 +3170,30 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "uname"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unchecked-index"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.8"
|
||||
@ -3024,6 +3239,20 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"chunked_transfer",
|
||||
"log",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
@ -3033,6 +3262,7 @@ dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 0.3.0",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3041,6 +3271,16 @@ version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.16.0"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "labrinth"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
authors = ["geometrically <jai@modrinth.com>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3.0"
|
||||
@ -29,7 +29,7 @@ lazy_static = "1.4.0"
|
||||
|
||||
meilisearch-sdk = "0.15.0"
|
||||
rust-s3 = "0.32.3"
|
||||
reqwest = { version = "0.11.12", features = ["json"] }
|
||||
reqwest = { version = "0.11.12", features = ["json", "multipart"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@ -66,4 +66,7 @@ env_logger = "0.9.1"
|
||||
thiserror = "1.0.37"
|
||||
|
||||
sqlx = { version = "0.6.2", features = ["runtime-actix-rustls", "postgres", "chrono", "offline", "macros", "migrate", "decimal"] }
|
||||
rust_decimal = { version = "1.26", features = ["serde-with-float", "serde-with-str"] }
|
||||
rust_decimal = { version = "1.26", features = ["serde-with-float", "serde-with-str"] }
|
||||
|
||||
sentry = "0.28.0"
|
||||
sentry-actix = "0.28.0"
|
||||
|
||||
6
migrations/20221116200727_flame-anvil-integration.sql
Normal file
6
migrations/20221116200727_flame-anvil-integration.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE payouts_values ALTER amount TYPE numeric(40, 20);
|
||||
|
||||
ALTER TABLE users ADD COLUMN flame_anvil_key varchar(40) NULL;
|
||||
ALTER TABLE mods ADD COLUMN flame_anvil_project integer NULL;
|
||||
ALTER TABLE mods ADD COLUMN flame_anvil_user bigint REFERENCES users NULL;
|
||||
3059
sqlx-data.json
3059
sqlx-data.json
File diff suppressed because it is too large
Load Diff
@ -129,6 +129,8 @@ impl ProjectBuilder {
|
||||
slug: self.slug,
|
||||
moderation_message: None,
|
||||
moderation_message_body: None,
|
||||
flame_anvil_project: None,
|
||||
flame_anvil_user: None,
|
||||
};
|
||||
project_struct.insert(&mut *transaction).await?;
|
||||
|
||||
@ -203,6 +205,8 @@ pub struct Project {
|
||||
pub slug: Option<String>,
|
||||
pub moderation_message: Option<String>,
|
||||
pub moderation_message_body: Option<String>,
|
||||
pub flame_anvil_project: Option<i32>,
|
||||
pub flame_anvil_user: Option<UserId>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
@ -267,7 +271,8 @@ impl Project {
|
||||
updated, approved, status,
|
||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||
team_id, client_side, server_side, license, slug,
|
||||
moderation_message, moderation_message_body
|
||||
moderation_message, moderation_message_body, flame_anvil_project,
|
||||
flame_anvil_user
|
||||
FROM mods
|
||||
WHERE id = $1
|
||||
",
|
||||
@ -303,6 +308,8 @@ impl Project {
|
||||
moderation_message: row.moderation_message,
|
||||
moderation_message_body: row.moderation_message_body,
|
||||
approved: row.approved,
|
||||
flame_anvil_project: row.flame_anvil_project,
|
||||
flame_anvil_user: row.flame_anvil_user.map(UserId),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -327,7 +334,8 @@ impl Project {
|
||||
updated, approved, status,
|
||||
issues_url, source_url, wiki_url, discord_url, license_url,
|
||||
team_id, client_side, server_side, license, slug,
|
||||
moderation_message, moderation_message_body
|
||||
moderation_message, moderation_message_body, flame_anvil_project,
|
||||
flame_anvil_user
|
||||
FROM mods
|
||||
WHERE id = ANY($1)
|
||||
",
|
||||
@ -361,6 +369,8 @@ impl Project {
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved,
|
||||
flame_anvil_project: m.flame_anvil_project,
|
||||
flame_anvil_user: m.flame_anvil_user.map(UserId)
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<Project>>()
|
||||
@ -639,7 +649,7 @@ impl Project {
|
||||
m.updated updated, m.approved approved, m.status status,
|
||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||
@ -709,6 +719,8 @@ impl Project {
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved,
|
||||
flame_anvil_project: m.flame_anvil_project,
|
||||
flame_anvil_user: m.flame_anvil_user.map(UserId),
|
||||
},
|
||||
project_type: m.project_type_name,
|
||||
categories,
|
||||
@ -826,7 +838,7 @@ impl Project {
|
||||
m.updated updated, m.approved approved, m.status status,
|
||||
m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,
|
||||
m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,
|
||||
s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,
|
||||
ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,
|
||||
ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,
|
||||
ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,
|
||||
@ -897,7 +909,9 @@ impl Project {
|
||||
follows: m.follows,
|
||||
moderation_message: m.moderation_message,
|
||||
moderation_message_body: m.moderation_message_body,
|
||||
approved: m.approved
|
||||
approved: m.approved,
|
||||
flame_anvil_project: m.flame_anvil_project,
|
||||
flame_anvil_user: m.flame_anvil_user.map(UserId)
|
||||
},
|
||||
project_type: m.project_type_name,
|
||||
categories,
|
||||
|
||||
@ -160,7 +160,7 @@ impl TeamMember {
|
||||
u.avatar_url avatar_url, u.username username, u.bio bio,
|
||||
u.created created, u.role user_role, u.badges badges, u.balance balance,
|
||||
u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,
|
||||
u.payout_address payout_address
|
||||
u.payout_address payout_address, u.flame_anvil_key flame_anvil_key
|
||||
FROM team_members tm
|
||||
INNER JOIN users u ON u.id = tm.user_id
|
||||
WHERE tm.team_id = $1
|
||||
@ -191,7 +191,8 @@ impl TeamMember {
|
||||
balance: m.balance,
|
||||
payout_wallet: m.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||
payout_wallet_type: m.payout_wallet_type.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: m.payout_address
|
||||
payout_address: m.payout_address,
|
||||
flame_anvil_key: m.flame_anvil_key,
|
||||
},
|
||||
payouts_split: m.payouts_split
|
||||
})))
|
||||
@ -228,7 +229,7 @@ impl TeamMember {
|
||||
u.avatar_url avatar_url, u.username username, u.bio bio,
|
||||
u.created created, u.role user_role, u.badges badges, u.balance balance,
|
||||
u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,
|
||||
u.payout_address payout_address
|
||||
u.payout_address payout_address, u.flame_anvil_key flame_anvil_key
|
||||
FROM team_members tm
|
||||
INNER JOIN users u ON u.id = tm.user_id
|
||||
WHERE tm.team_id = ANY($1)
|
||||
@ -260,7 +261,8 @@ impl TeamMember {
|
||||
balance: m.balance,
|
||||
payout_wallet: m.payout_wallet.map(|x| RecipientWallet::from_string(&x)),
|
||||
payout_wallet_type: m.payout_wallet_type.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: m.payout_address
|
||||
payout_address: m.payout_address,
|
||||
flame_anvil_key: m.flame_anvil_key,
|
||||
},
|
||||
payouts_split: m.payouts_split
|
||||
})))
|
||||
@ -517,15 +519,24 @@ impl TeamMember {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete<'a, 'b, E>(
|
||||
pub async fn delete<'a, 'b>(
|
||||
id: TeamId,
|
||||
user_id: UserId,
|
||||
executor: E,
|
||||
) -> Result<(), super::DatabaseError>
|
||||
where
|
||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), super::DatabaseError> {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET flame_anvil_user = NULL
|
||||
WHERE (team_id = $1 AND flame_anvil_user = $2 )
|
||||
",
|
||||
id as TeamId,
|
||||
user_id as UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM team_members
|
||||
WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)
|
||||
@ -534,16 +545,9 @@ impl TeamMember {
|
||||
user_id as UserId,
|
||||
crate::models::teams::OWNER_ROLE,
|
||||
)
|
||||
.execute(executor)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() != 1 {
|
||||
return Err(super::DatabaseError::Other(format!(
|
||||
"Deleting a member failed; {} rows deleted",
|
||||
result.rows_affected()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ pub struct User {
|
||||
pub payout_wallet: Option<RecipientWallet>,
|
||||
pub payout_wallet_type: Option<RecipientType>,
|
||||
pub payout_address: Option<String>,
|
||||
pub flame_anvil_key: Option<String>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
@ -63,7 +64,7 @@ impl User {
|
||||
u.avatar_url, u.username, u.bio,
|
||||
u.created, u.role, u.badges,
|
||||
u.balance, u.payout_wallet, u.payout_wallet_type,
|
||||
u.payout_address
|
||||
u.payout_address, u.flame_anvil_key
|
||||
FROM users u
|
||||
WHERE u.id = $1
|
||||
",
|
||||
@ -93,6 +94,7 @@ impl User {
|
||||
.payout_wallet_type
|
||||
.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: row.payout_address,
|
||||
flame_anvil_key: row.flame_anvil_key,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -112,7 +114,7 @@ impl User {
|
||||
u.avatar_url, u.username, u.bio,
|
||||
u.created, u.role, u.badges,
|
||||
u.balance, u.payout_wallet, u.payout_wallet_type,
|
||||
u.payout_address
|
||||
u.payout_address, u.flame_anvil_key
|
||||
FROM users u
|
||||
WHERE u.github_id = $1
|
||||
",
|
||||
@ -142,6 +144,7 @@ impl User {
|
||||
.payout_wallet_type
|
||||
.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: row.payout_address,
|
||||
flame_anvil_key: row.flame_anvil_key,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -161,7 +164,7 @@ impl User {
|
||||
u.avatar_url, u.username, u.bio,
|
||||
u.created, u.role, u.badges,
|
||||
u.balance, u.payout_wallet, u.payout_wallet_type,
|
||||
u.payout_address
|
||||
u.payout_address, u.flame_anvil_key
|
||||
FROM users u
|
||||
WHERE LOWER(u.username) = LOWER($1)
|
||||
",
|
||||
@ -191,6 +194,7 @@ impl User {
|
||||
.payout_wallet_type
|
||||
.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: row.payout_address,
|
||||
flame_anvil_key: row.flame_anvil_key,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -214,7 +218,7 @@ impl User {
|
||||
u.avatar_url, u.username, u.bio,
|
||||
u.created, u.role, u.badges,
|
||||
u.balance, u.payout_wallet, u.payout_wallet_type,
|
||||
u.payout_address
|
||||
u.payout_address, u.flame_anvil_key
|
||||
FROM users u
|
||||
WHERE u.id = ANY($1)
|
||||
",
|
||||
@ -241,6 +245,7 @@ impl User {
|
||||
.payout_wallet_type
|
||||
.map(|x| RecipientType::from_string(&x)),
|
||||
payout_address: u.payout_address,
|
||||
flame_anvil_key: u.flame_anvil_key,
|
||||
}))
|
||||
})
|
||||
.try_collect::<Vec<User>>()
|
||||
|
||||
13
src/main.rs
13
src/main.rs
@ -1,5 +1,6 @@
|
||||
use crate::file_hosting::S3Host;
|
||||
use crate::queue::download::DownloadQueue;
|
||||
use crate::queue::flameanvil::FlameAnvilQueue;
|
||||
use crate::queue::payouts::PayoutsQueue;
|
||||
use crate::ratelimit::errors::ARError;
|
||||
use crate::ratelimit::memory::{MemoryStore, MemoryStoreActor};
|
||||
@ -41,6 +42,14 @@ async fn main() -> std::io::Result<()> {
|
||||
error!("Some environment variables are missing!");
|
||||
}
|
||||
|
||||
// DSN is from SENTRY_DSN env variable.
|
||||
// Has no effect if not set.
|
||||
let sentry = sentry::init(());
|
||||
if sentry.is_enabled() {
|
||||
info!("Enabled Sentry integration");
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
}
|
||||
|
||||
info!(
|
||||
"Starting Labrinth on {}",
|
||||
dotenvy::var("BIND_ADDR").unwrap()
|
||||
@ -166,6 +175,8 @@ async fn main() -> std::io::Result<()> {
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
let flame_anvil_queue = Arc::new(Mutex::new(FlameAnvilQueue::new()));
|
||||
|
||||
let store = MemoryStore::new();
|
||||
|
||||
info!("Starting Actix HTTP server!");
|
||||
@ -220,7 +231,9 @@ async fn main() -> std::io::Result<()> {
|
||||
.app_data(web::Data::new(search_config.clone()))
|
||||
.app_data(web::Data::new(download_queue.clone()))
|
||||
.app_data(web::Data::new(payouts_queue.clone()))
|
||||
.app_data(flame_anvil_queue.clone())
|
||||
.app_data(web::Data::new(ip_salt.clone()))
|
||||
.wrap(sentry_actix::Sentry::new())
|
||||
.configure(routes::v1_config)
|
||||
.configure(routes::v2_config)
|
||||
.service(routes::index_get)
|
||||
|
||||
@ -89,6 +89,11 @@ pub struct Project {
|
||||
|
||||
/// A string of URLs to visual content featuring the project
|
||||
pub gallery: Vec<GalleryItem>,
|
||||
|
||||
/// The project linked from FlameAnvil to sync with
|
||||
pub flame_anvil_project: Option<i32>,
|
||||
/// The user_id of the team member whose token
|
||||
pub flame_anvil_user: Option<UserId>,
|
||||
}
|
||||
|
||||
impl From<QueryProject> for Project {
|
||||
@ -153,6 +158,8 @@ impl From<QueryProject> for Project {
|
||||
created: x.created,
|
||||
})
|
||||
.collect(),
|
||||
flame_anvil_project: m.flame_anvil_project,
|
||||
flame_anvil_user: m.flame_anvil_user.map(|x| x.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,9 +69,16 @@ pub struct TeamMember {
|
||||
|
||||
impl TeamMember {
|
||||
pub fn from(data: QueryTeamMember, override_permissions: bool) -> Self {
|
||||
let has_flame_anvil_key = data.user.flame_anvil_key.is_some();
|
||||
let mut user: User = data.user.into();
|
||||
|
||||
if !override_permissions {
|
||||
user.has_flame_anvil_key = Some(has_flame_anvil_key);
|
||||
}
|
||||
|
||||
Self {
|
||||
team_id: data.team_id.into(),
|
||||
user: data.user.into(),
|
||||
user,
|
||||
role: data.role,
|
||||
permissions: if override_permissions {
|
||||
None
|
||||
|
||||
@ -47,6 +47,7 @@ pub struct User {
|
||||
pub role: Role,
|
||||
pub badges: Badges,
|
||||
pub payout_data: Option<UserPayoutData>,
|
||||
pub has_flame_anvil_key: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
@ -140,6 +141,7 @@ impl From<DBUser> for User {
|
||||
role: Role::from_string(&data.role),
|
||||
badges: data.badges,
|
||||
payout_data: None,
|
||||
has_flame_anvil_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
250
src/queue/flameanvil.rs
Normal file
250
src/queue/flameanvil.rs
Normal file
@ -0,0 +1,250 @@
|
||||
use crate::database::models::categories::GameVersion;
|
||||
use crate::file_hosting::FileHostingError;
|
||||
use crate::routes::project_creation::CreateError;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct FlameGameVersionType {
|
||||
id: i32,
|
||||
slug: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct FlameGameVersion {
|
||||
id: i32,
|
||||
#[serde(rename = "gameVersionTypeID")]
|
||||
game_version_type_id: i32,
|
||||
name: String,
|
||||
slug: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FlameUploadFile {
|
||||
changelog: String,
|
||||
// always "markdown"
|
||||
changelog_type: String,
|
||||
display_name: String,
|
||||
game_versions: Vec<i32>,
|
||||
release_type: String,
|
||||
// TODO: relations?
|
||||
}
|
||||
|
||||
pub struct FlameAnvilQueue {
|
||||
mod_loaders: Vec<FlameGameVersion>,
|
||||
minecraft_versions: Vec<FlameGameVersion>,
|
||||
last_updated: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct UploadFile {
|
||||
pub loaders: Vec<String>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub display_name: String,
|
||||
pub changelog: String,
|
||||
pub version_type: String,
|
||||
}
|
||||
|
||||
// Batches download transactions every thirty seconds
|
||||
impl FlameAnvilQueue {
|
||||
pub fn new() -> Self {
|
||||
FlameAnvilQueue {
|
||||
mod_loaders: vec![],
|
||||
minecraft_versions: vec![],
|
||||
last_updated: Utc::now() - Duration::days(365),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_game_versions_to_flame(
|
||||
&self,
|
||||
original: Vec<String>,
|
||||
game_versions: &[GameVersion],
|
||||
) -> Vec<i32> {
|
||||
let mut og_to_flame = HashMap::new();
|
||||
let mut last_visited = if self
|
||||
.minecraft_versions
|
||||
.last()
|
||||
.map(|x| x.name.ends_with("-Snapshot"))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
self.minecraft_versions
|
||||
.iter()
|
||||
.rfind(|x| !x.name.ends_with("-Snapshot"))
|
||||
.cloned()
|
||||
};
|
||||
|
||||
for game_version in game_versions {
|
||||
if let Some(flame_game_version) =
|
||||
self.minecraft_versions.iter().find(|x| {
|
||||
x.name
|
||||
== if game_version.version.starts_with('b') {
|
||||
game_version.version.replace('b', "Beta ")
|
||||
} else {
|
||||
game_version.version.clone()
|
||||
}
|
||||
})
|
||||
{
|
||||
last_visited = Some(flame_game_version.clone());
|
||||
og_to_flame
|
||||
.insert(&game_version.version, flame_game_version.id);
|
||||
} else if let Some(last_visited) = &last_visited {
|
||||
if game_version.major {
|
||||
og_to_flame.insert(&game_version.version, last_visited.id);
|
||||
} else {
|
||||
let mut splits = last_visited.name.split('.');
|
||||
let new_str = format!(
|
||||
"{}.{}-Snapshot",
|
||||
splits.next().unwrap_or_default(),
|
||||
splits.next().unwrap_or_default()
|
||||
);
|
||||
|
||||
if let Some(flame_game_version) = self
|
||||
.minecraft_versions
|
||||
.iter()
|
||||
.find(|x| x.name == new_str)
|
||||
{
|
||||
og_to_flame.insert(
|
||||
&game_version.version,
|
||||
flame_game_version.id,
|
||||
);
|
||||
} else {
|
||||
og_to_flame
|
||||
.insert(&game_version.version, last_visited.id);
|
||||
}
|
||||
}
|
||||
} else if let Some(first) = self.minecraft_versions.last() {
|
||||
og_to_flame.insert(&game_version.version, first.id);
|
||||
}
|
||||
}
|
||||
|
||||
let mut new = Vec::new();
|
||||
|
||||
for x in original {
|
||||
if let Some(value) = og_to_flame.get(&&x) {
|
||||
new.push(*value);
|
||||
}
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn upload_file(
|
||||
&mut self,
|
||||
api_token: &str,
|
||||
project_id: i32,
|
||||
upload_file: UploadFile,
|
||||
game_versions: &[GameVersion],
|
||||
file: Vec<u8>,
|
||||
file_name: String,
|
||||
mime_type: String,
|
||||
) -> Result<i32, CreateError> {
|
||||
if self.last_updated < (Utc::now() - Duration::minutes(30)) {
|
||||
self.index(api_token).await.map_err(|_| {
|
||||
CreateError::InvalidInput(
|
||||
"Indexing metadata from FlameAnvil failed!".to_string(),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let mut loaders_converted = upload_file
|
||||
.loaders
|
||||
.into_iter()
|
||||
.filter_map(|x| self.mod_loaders.iter().find(|y| y.slug == x))
|
||||
.map(|x| x.id)
|
||||
.collect::<Vec<i32>>();
|
||||
|
||||
let mut game_versions_converted = self.convert_game_versions_to_flame(
|
||||
upload_file.game_versions,
|
||||
game_versions,
|
||||
);
|
||||
|
||||
loaders_converted.append(&mut game_versions_converted);
|
||||
|
||||
let file = reqwest::multipart::Part::bytes(file)
|
||||
.file_name(file_name)
|
||||
.mime_str(&mime_type)
|
||||
.map_err(|_| {
|
||||
CreateError::InvalidInput(
|
||||
"Error while converting inputted file to multipart payload"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.text(
|
||||
"metadata",
|
||||
serde_json::to_string(&FlameUploadFile {
|
||||
changelog: upload_file.changelog,
|
||||
changelog_type: "markdown".to_string(),
|
||||
display_name: upload_file.display_name,
|
||||
game_versions: loaders_converted,
|
||||
release_type: upload_file.version_type,
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.part("file", file);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FileResponse {
|
||||
id: i32,
|
||||
}
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let id = client.post(&*format!("https://minecraft.curseforge.com/api/projects/{project_id}/upload-file?token={api_token}"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await.map_err(|_| CreateError::FileHostingError(FileHostingError::S3Error("Error uploading file to FlameAnvil!".to_string())))?
|
||||
.json::<FileResponse>()
|
||||
.await.map_err(|_| CreateError::FileHostingError(FileHostingError::S3Error("Error deserializing uploaded file response from FlameAnvil!".to_string())))?;
|
||||
|
||||
Ok(id.id)
|
||||
}
|
||||
|
||||
pub async fn index(
|
||||
&mut self,
|
||||
api_token: &str,
|
||||
) -> Result<(), reqwest::Error> {
|
||||
let (game_versions, game_version_types) = futures::future::try_join(
|
||||
reqwest::get(format!("https://minecraft.curseforge.com/api/game/versions?token={api_token}")),
|
||||
reqwest::get(format!("https://minecraft.curseforge.com/api/game/version-types?token={api_token}"))
|
||||
).await?;
|
||||
|
||||
let (game_versions, game_version_types) = futures::future::try_join(
|
||||
game_versions.json::<Vec<FlameGameVersion>>(),
|
||||
game_version_types.json::<Vec<FlameGameVersionType>>(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mod_loader_types = game_version_types
|
||||
.iter()
|
||||
.filter(|x| x.slug == *"modloader")
|
||||
.map(|x| x.id)
|
||||
.collect::<Vec<_>>();
|
||||
let minecraft_types = game_version_types
|
||||
.iter()
|
||||
.filter(|x| x.slug.starts_with("minecraft"))
|
||||
.map(|x| x.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mod_loaders = game_versions
|
||||
.iter()
|
||||
.filter(|x| mod_loader_types.contains(&x.game_version_type_id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let minecraft_versions = game_versions
|
||||
.iter()
|
||||
.filter(|x| minecraft_types.contains(&x.game_version_type_id))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.mod_loaders = mod_loaders;
|
||||
self.minecraft_versions = minecraft_versions;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod download;
|
||||
pub mod flameanvil;
|
||||
pub mod payouts;
|
||||
|
||||
@ -97,13 +97,18 @@ impl PayoutsQueue {
|
||||
})?;
|
||||
}
|
||||
|
||||
let fee = std::cmp::min(
|
||||
std::cmp::max(
|
||||
Decimal::ONE / Decimal::from(4),
|
||||
(Decimal::from(2) / Decimal::ONE_HUNDRED) * payout.amount.value,
|
||||
),
|
||||
Decimal::from(20),
|
||||
);
|
||||
let fee = if payout.recipient_wallet == *"Venmo" {
|
||||
Decimal::ONE / Decimal::from(4)
|
||||
} else {
|
||||
std::cmp::min(
|
||||
std::cmp::max(
|
||||
Decimal::ONE / Decimal::from(4),
|
||||
(Decimal::from(2) / Decimal::ONE_HUNDRED)
|
||||
* payout.amount.value,
|
||||
),
|
||||
Decimal::from(20),
|
||||
)
|
||||
};
|
||||
|
||||
payout.amount.value -= fee;
|
||||
|
||||
@ -170,9 +175,9 @@ impl PayoutsQueue {
|
||||
}
|
||||
|
||||
// Calculate actual fee + refund if we took too big of a fee.
|
||||
if let Some(res) = res.json::<PayoutsResponse>().await.ok() {
|
||||
if let Ok(res) = res.json::<PayoutsResponse>().await {
|
||||
if let Some(link) = res.links.first() {
|
||||
if let Some(res) = client
|
||||
if let Ok(res) = client
|
||||
.get(&link.href)
|
||||
.header(
|
||||
"Authorization",
|
||||
@ -184,9 +189,8 @@ impl PayoutsQueue {
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.ok()
|
||||
{
|
||||
if let Some(res) = res.json::<PayoutData>().await.ok() {
|
||||
if let Ok(res) = res.json::<PayoutData>().await {
|
||||
if let Some(data) = res.items.first() {
|
||||
if (fee - data.payout_item_fee.value)
|
||||
> Decimal::ZERO
|
||||
|
||||
@ -278,6 +278,7 @@ pub async fn auth_callback(
|
||||
payout_wallet: None,
|
||||
payout_wallet_type: None,
|
||||
payout_address: None,
|
||||
flame_anvil_key: None,
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
@ -5,6 +5,7 @@ use crate::models::projects::{
|
||||
DonationLink, License, ProjectId, ProjectStatus, SideType, VersionId,
|
||||
};
|
||||
use crate::models::users::UserId;
|
||||
use crate::queue::flameanvil::FlameAnvilQueue;
|
||||
use crate::routes::version_creation::InitialVersionData;
|
||||
use crate::search::indexing::IndexingError;
|
||||
use crate::util::auth::{get_user_from_headers, AuthenticationError};
|
||||
@ -22,6 +23,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::Mutex;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
@ -255,6 +257,7 @@ pub async fn project_create(
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
flame_anvil_queue: Data<Arc<Mutex<FlameAnvilQueue>>>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let mut transaction = client.begin().await?;
|
||||
let mut uploaded_files = Vec::new();
|
||||
@ -264,6 +267,7 @@ pub async fn project_create(
|
||||
&mut payload,
|
||||
&mut transaction,
|
||||
&***file_host,
|
||||
&flame_anvil_queue,
|
||||
&mut uploaded_files,
|
||||
)
|
||||
.await;
|
||||
@ -320,6 +324,7 @@ pub async fn project_create_inner(
|
||||
payload: &mut Multipart,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
file_host: &dyn FileHost,
|
||||
flame_anvil_queue: &Mutex<FlameAnvilQueue>,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
// The base URL for files uploaded to backblaze
|
||||
@ -562,6 +567,7 @@ pub async fn project_create_inner(
|
||||
super::version_creation::upload_file(
|
||||
&mut field,
|
||||
file_host,
|
||||
version_data.file_parts.len(),
|
||||
uploaded_files,
|
||||
&mut created_version.files,
|
||||
&mut created_version.dependencies,
|
||||
@ -575,6 +581,12 @@ pub async fn project_create_inner(
|
||||
all_game_versions.clone(),
|
||||
version_data.primary_file.is_some(),
|
||||
version_data.primary_file.as_deref() == Some(name),
|
||||
version_data.version_title.clone(),
|
||||
version_data.version_body.clone().unwrap_or_default(),
|
||||
version_data.release_channel.clone().to_string(),
|
||||
flame_anvil_queue,
|
||||
None,
|
||||
None,
|
||||
transaction,
|
||||
)
|
||||
.await?;
|
||||
@ -787,6 +799,8 @@ pub async fn project_create_inner(
|
||||
discord_url: project_builder.discord_url.clone(),
|
||||
donation_urls: project_create_data.donation_urls.clone(),
|
||||
gallery: gallery_urls,
|
||||
flame_anvil_project: None,
|
||||
flame_anvil_user: None,
|
||||
};
|
||||
|
||||
let _project_id = project_builder.insert(&mut *transaction).await?;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::database;
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models;
|
||||
use crate::models::ids::UserId;
|
||||
use crate::models::projects::{
|
||||
DonationLink, Project, ProjectId, ProjectStatus, SearchRequest, SideType,
|
||||
};
|
||||
@ -341,6 +342,18 @@ pub struct EditProject {
|
||||
)]
|
||||
#[validate(length(max = 65536))]
|
||||
pub moderation_message_body: Option<Option<String>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
pub flame_anvil_user: Option<Option<UserId>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
pub flame_anvil_project: Option<Option<i32>>,
|
||||
}
|
||||
|
||||
#[patch("{id}")]
|
||||
@ -979,6 +992,92 @@ pub async fn project_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(project) = &new_project.flame_anvil_project {
|
||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the external syncing project!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if project_item.project_type == "modpack" {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"This project syncing feature is not available for modpacks!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET flame_anvil_project = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
*project,
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(user_id) = &new_project.flame_anvil_user {
|
||||
if !perms.contains(Permissions::EDIT_DETAILS) {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have the permissions to edit the syncing user for this project!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if project_item.project_type == "modpack" {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"This project syncing feature is not available for modpacks!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(user_id) = user_id {
|
||||
if user_id != &user.id && !user.role.is_admin() {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"You may only set yourself as the syncing user!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let results = sqlx::query!(
|
||||
"
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM team_members
|
||||
INNER JOIN users u on team_members.user_id = u.id AND u.flame_anvil_key IS NOT NULL
|
||||
WHERE team_id = $1 AND user_id = $2 AND accepted = TRUE
|
||||
)
|
||||
",
|
||||
project_item.inner.team_id as database::models::ids::TeamId,
|
||||
database::models::ids::UserId::from(*user_id) as database::models::ids::UserId,
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?;
|
||||
|
||||
if !results.exists.unwrap_or(true) {
|
||||
return Err(ApiError::InvalidInput(
|
||||
"The given user is not part of your team or does not have a syncing key added to their account!"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET flame_anvil_user = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
user_id.map(|x| x.0 as i64),
|
||||
id as database::models::ids::ProjectId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
|
||||
@ -572,6 +572,8 @@ pub async fn remove_team_member(
|
||||
));
|
||||
}
|
||||
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if delete_member.accepted {
|
||||
// Members other than the owner can either leave the team, or be
|
||||
// removed by a member with the REMOVE_MEMBER permission.
|
||||
@ -579,7 +581,7 @@ pub async fn remove_team_member(
|
||||
|| (member.permissions.contains(Permissions::REMOVE_MEMBER)
|
||||
&& member.accepted)
|
||||
{
|
||||
TeamMember::delete(id, user_id, &**pool).await?;
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to remove a member from this team".to_string(),
|
||||
@ -592,13 +594,15 @@ pub async fn remove_team_member(
|
||||
// This is a pending invite rather than a member, so the
|
||||
// user being invited or team members with the MANAGE_INVITES
|
||||
// permission can remove it.
|
||||
TeamMember::delete(id, user_id, &**pool).await?;
|
||||
TeamMember::delete(id, user_id, &mut transaction).await?;
|
||||
} else {
|
||||
return Err(ApiError::CustomAuthentication(
|
||||
"You do not have permission to cancel a team invite"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Ok(HttpResponse::NotFound().body(""))
|
||||
|
||||
@ -167,6 +167,13 @@ pub struct EditUser {
|
||||
)]
|
||||
#[validate]
|
||||
pub payout_data: Option<Option<EditPayoutData>>,
|
||||
#[serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "::serde_with::rust::double_option"
|
||||
)]
|
||||
#[validate(length(min = 1, max = 40), regex = "RE_URL_SAFE")]
|
||||
pub flame_anvil_key: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate)]
|
||||
@ -216,10 +223,10 @@ pub async fn user_edit(
|
||||
{
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET username = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
UPDATE users
|
||||
SET username = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
username,
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
@ -388,6 +395,33 @@ pub async fn user_edit(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(flame_anvil_key) = &new_user.flame_anvil_key {
|
||||
if flame_anvil_key.is_none() {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE mods
|
||||
SET flame_anvil_user = NULL
|
||||
WHERE (flame_anvil_user = $1)
|
||||
",
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET flame_anvil_key = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
flame_anvil_key.as_deref(),
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
use crate::file_hosting::FileHost;
|
||||
use crate::models::projects::SearchRequest;
|
||||
use crate::queue::flameanvil::FlameAnvilQueue;
|
||||
use crate::routes::project_creation::{
|
||||
project_create_inner, undo_uploads, CreateError,
|
||||
};
|
||||
@ -15,6 +16,7 @@ use actix_web::{get, post, HttpRequest, HttpResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ResultSearchMod {
|
||||
@ -119,6 +121,7 @@ pub async fn mod_create(
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
flame_anvil_queue: Data<Arc<Mutex<FlameAnvilQueue>>>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let mut transaction = client.begin().await?;
|
||||
let mut uploaded_files = Vec::new();
|
||||
@ -128,6 +131,7 @@ pub async fn mod_create(
|
||||
&mut payload,
|
||||
&mut transaction,
|
||||
&***file_host,
|
||||
&flame_anvil_queue,
|
||||
&mut uploaded_files,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -10,6 +10,7 @@ use crate::models::projects::{
|
||||
VersionFile, VersionId, VersionType,
|
||||
};
|
||||
use crate::models::teams::Permissions;
|
||||
use crate::queue::flameanvil::{FlameAnvilQueue, UploadFile};
|
||||
use crate::routes::project_creation::{CreateError, UploadedFile};
|
||||
use crate::util::auth::get_user_from_headers;
|
||||
use crate::util::routes::read_from_field;
|
||||
@ -18,11 +19,13 @@ use crate::validate::{validate_file, ValidationResult};
|
||||
use actix::fut::ready;
|
||||
use actix_multipart::{Field, Multipart};
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use actix_web::{post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use futures::stream::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::postgres::PgPool;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Serialize, Deserialize, Validate, Clone)]
|
||||
@ -68,7 +71,8 @@ pub async fn version_create(
|
||||
req: HttpRequest,
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
file_host: Data<std::sync::Arc<dyn FileHost + Send + Sync>>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
flame_anvil_queue: Data<Arc<Mutex<FlameAnvilQueue>>>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let mut transaction = client.begin().await?;
|
||||
let mut uploaded_files = Vec::new();
|
||||
@ -78,6 +82,7 @@ pub async fn version_create(
|
||||
&mut payload,
|
||||
&mut transaction,
|
||||
&***file_host,
|
||||
&flame_anvil_queue,
|
||||
&mut uploaded_files,
|
||||
)
|
||||
.await;
|
||||
@ -108,6 +113,7 @@ async fn version_create_inner(
|
||||
payload: &mut Multipart,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
file_host: &dyn FileHost,
|
||||
flame_anvil_queue: &Mutex<FlameAnvilQueue>,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let cdn_url = dotenvy::var("CDN_URL")?;
|
||||
@ -280,16 +286,29 @@ async fn version_create_inner(
|
||||
|
||||
let project_type = sqlx::query!(
|
||||
"
|
||||
SELECT name FROM project_types pt
|
||||
INNER JOIN mods ON mods.project_type = pt.id
|
||||
WHERE mods.id = $1
|
||||
",
|
||||
SELECT name FROM project_types pt
|
||||
INNER JOIN mods ON mods.project_type = pt.id
|
||||
WHERE mods.id = $1
|
||||
",
|
||||
version.project_id as models::ProjectId,
|
||||
)
|
||||
.fetch_one(&mut *transaction)
|
||||
.await?
|
||||
.name;
|
||||
|
||||
let flame_anvil_info = sqlx::query!(
|
||||
"
|
||||
SELECT m.flame_anvil_project, u.flame_anvil_key
|
||||
FROM mods m
|
||||
INNER JOIN users u ON m.flame_anvil_user = u.id
|
||||
WHERE m.id = $1
|
||||
",
|
||||
version.project_id as models::ProjectId,
|
||||
)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?
|
||||
.map(|x| (x.flame_anvil_project, x.flame_anvil_key));
|
||||
|
||||
let version_data = initial_version_data.clone().ok_or_else(|| {
|
||||
CreateError::InvalidInput("`data` field is required".to_string())
|
||||
})?;
|
||||
@ -297,6 +316,7 @@ async fn version_create_inner(
|
||||
upload_file(
|
||||
&mut field,
|
||||
file_host,
|
||||
version_data.file_parts.len(),
|
||||
uploaded_files,
|
||||
&mut version.files,
|
||||
&mut version.dependencies,
|
||||
@ -310,6 +330,12 @@ async fn version_create_inner(
|
||||
all_game_versions.clone(),
|
||||
version_data.primary_file.is_some(),
|
||||
version_data.primary_file.as_deref() == Some(name),
|
||||
version_data.version_title.clone(),
|
||||
version_data.version_body.clone().unwrap_or_default(),
|
||||
version_data.release_channel.clone().to_string(),
|
||||
flame_anvil_queue,
|
||||
flame_anvil_info.clone().and_then(|x| x.0),
|
||||
flame_anvil_info.and_then(|x| x.1),
|
||||
transaction,
|
||||
)
|
||||
.await?;
|
||||
@ -344,9 +370,9 @@ async fn version_create_inner(
|
||||
|
||||
let users = sqlx::query!(
|
||||
"
|
||||
SELECT follower_id FROM mod_follows
|
||||
WHERE mod_id = $1
|
||||
",
|
||||
SELECT follower_id FROM mod_follows
|
||||
WHERE mod_id = $1
|
||||
",
|
||||
builder.project_id as crate::database::models::ids::ProjectId
|
||||
)
|
||||
.fetch_many(&mut *transaction)
|
||||
@ -363,7 +389,7 @@ async fn version_create_inner(
|
||||
notification_type: Some("project_update".to_string()),
|
||||
title: format!("**{}** has been updated!", result.title),
|
||||
text: format!(
|
||||
"The project, {}, has released a new version: {}",
|
||||
"The project {} has released a new version: {}",
|
||||
result.title,
|
||||
version_data.version_number.clone()
|
||||
),
|
||||
@ -428,10 +454,11 @@ async fn version_create_inner(
|
||||
#[post("{version_id}/file")]
|
||||
pub async fn upload_file_to_version(
|
||||
req: HttpRequest,
|
||||
url_data: actix_web::web::Path<(VersionId,)>,
|
||||
url_data: web::Path<(VersionId,)>,
|
||||
mut payload: Multipart,
|
||||
client: Data<PgPool>,
|
||||
file_host: Data<std::sync::Arc<dyn FileHost + Send + Sync>>,
|
||||
file_host: Data<Arc<dyn FileHost + Send + Sync>>,
|
||||
flame_anvil_queue: Data<Arc<Mutex<FlameAnvilQueue>>>,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
let mut transaction = client.begin().await?;
|
||||
let mut uploaded_files = Vec::new();
|
||||
@ -444,6 +471,7 @@ pub async fn upload_file_to_version(
|
||||
client,
|
||||
&mut transaction,
|
||||
&***file_host,
|
||||
&flame_anvil_queue,
|
||||
&mut uploaded_files,
|
||||
version_id,
|
||||
)
|
||||
@ -470,12 +498,14 @@ pub async fn upload_file_to_version(
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn upload_file_to_version_inner(
|
||||
req: HttpRequest,
|
||||
payload: &mut Multipart,
|
||||
client: Data<PgPool>,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
file_host: &dyn FileHost,
|
||||
flame_anvil_queue: &Mutex<FlameAnvilQueue>,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
version_id: models::VersionId,
|
||||
) -> Result<HttpResponse, CreateError> {
|
||||
@ -578,6 +608,7 @@ async fn upload_file_to_version_inner(
|
||||
upload_file(
|
||||
&mut field,
|
||||
file_host,
|
||||
0,
|
||||
uploaded_files,
|
||||
&mut file_builders,
|
||||
&mut dependencies,
|
||||
@ -596,6 +627,12 @@ async fn upload_file_to_version_inner(
|
||||
all_game_versions.clone(),
|
||||
true,
|
||||
false,
|
||||
version.name.clone(),
|
||||
version.changelog.clone(),
|
||||
version.version_type.clone(),
|
||||
flame_anvil_queue,
|
||||
None,
|
||||
None,
|
||||
transaction,
|
||||
)
|
||||
.await?;
|
||||
@ -620,6 +657,7 @@ async fn upload_file_to_version_inner(
|
||||
pub async fn upload_file(
|
||||
field: &mut Field,
|
||||
file_host: &dyn FileHost,
|
||||
total_files_len: usize,
|
||||
uploaded_files: &mut Vec<UploadedFile>,
|
||||
version_files: &mut Vec<VersionFileBuilder>,
|
||||
dependencies: &mut Vec<DependencyBuilder>,
|
||||
@ -633,6 +671,12 @@ pub async fn upload_file(
|
||||
all_game_versions: Vec<models::categories::GameVersion>,
|
||||
ignore_primary: bool,
|
||||
force_primary: bool,
|
||||
version_display_name: String,
|
||||
version_changelog: String,
|
||||
version_type: String,
|
||||
flame_anvil_queue: &Mutex<FlameAnvilQueue>,
|
||||
flame_anvil_project: Option<i32>,
|
||||
flame_anvil_key: Option<String>,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||
) -> Result<(), CreateError> {
|
||||
let (file_name, file_extension) = get_name_ext(content_disposition)?;
|
||||
@ -672,9 +716,9 @@ pub async fn upload_file(
|
||||
data.clone().into(),
|
||||
file_extension.to_string(),
|
||||
project_type.to_string(),
|
||||
loaders,
|
||||
game_versions,
|
||||
all_game_versions,
|
||||
loaders.clone(),
|
||||
game_versions.clone(),
|
||||
all_game_versions.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@ -745,6 +789,43 @@ pub async fn upload_file(
|
||||
}
|
||||
}
|
||||
|
||||
let data = data.freeze();
|
||||
|
||||
let primary = (validation_result.is_passed()
|
||||
&& version_files.iter().all(|x| !x.primary)
|
||||
&& !ignore_primary)
|
||||
|| force_primary
|
||||
|| total_files_len == 1;
|
||||
|
||||
if primary {
|
||||
if let Some(project_id) = flame_anvil_project {
|
||||
if let Some(key) = flame_anvil_key {
|
||||
let mut flame_anvil_queue = flame_anvil_queue.lock().await;
|
||||
|
||||
flame_anvil_queue
|
||||
.upload_file(
|
||||
&key,
|
||||
project_id,
|
||||
UploadFile {
|
||||
loaders: loaders.into_iter().map(|x| x.0).collect(),
|
||||
game_versions: game_versions
|
||||
.into_iter()
|
||||
.map(|x| x.0)
|
||||
.collect(),
|
||||
display_name: version_display_name,
|
||||
changelog: version_changelog,
|
||||
version_type,
|
||||
},
|
||||
&all_game_versions,
|
||||
data.to_vec(),
|
||||
file_name.to_string(),
|
||||
content_type.to_string(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file_path_encode = format!(
|
||||
"data/{}/versions/{}/{}",
|
||||
project_id,
|
||||
@ -755,7 +836,7 @@ pub async fn upload_file(
|
||||
format!("data/{}/versions/{}/{}", project_id, version_id, &file_name);
|
||||
|
||||
let upload_data = file_host
|
||||
.upload_file(content_type, &file_path, data.freeze())
|
||||
.upload_file(content_type, &file_path, data)
|
||||
.await?;
|
||||
|
||||
uploaded_files.push(UploadedFile {
|
||||
@ -777,7 +858,7 @@ pub async fn upload_file(
|
||||
));
|
||||
}
|
||||
|
||||
version_files.push(models::version_item::VersionFileBuilder {
|
||||
version_files.push(VersionFileBuilder {
|
||||
filename: file_name.to_string(),
|
||||
url: format!("{}/{}", cdn_url, file_path_encode),
|
||||
hashes: vec![
|
||||
@ -794,10 +875,7 @@ pub async fn upload_file(
|
||||
hash: sha512_bytes,
|
||||
},
|
||||
],
|
||||
primary: (validation_result.is_passed()
|
||||
&& version_files.iter().all(|x| !x.primary)
|
||||
&& !ignore_primary)
|
||||
|| force_primary,
|
||||
primary,
|
||||
size: upload_data.content_length,
|
||||
});
|
||||
|
||||
|
||||
@ -79,6 +79,7 @@ where
|
||||
payout_wallet_type: result.payout_wallet_type,
|
||||
payout_address: result.payout_address,
|
||||
}),
|
||||
has_flame_anvil_key: Some(result.flame_anvil_key.is_some()),
|
||||
}),
|
||||
None => Err(AuthenticationError::InvalidCredentials),
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user