From f259d81249e51bd44f23d91ccb36604c78ffeb3e Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Sun, 20 Nov 2022 19:50:14 -0700 Subject: [PATCH] FlameAnvil Project Sync (#481) * FlameAnvil Project Sync * Perm fixes * Fix compile * Fix clippy + run prepare --- .env | 2 + Cargo.lock | 242 +- Cargo.toml | 9 +- ...20221116200727_flame-anvil-integration.sql | 6 + sqlx-data.json | 3059 +++++++++-------- src/database/models/project_item.rs | 24 +- src/database/models/team_item.rs | 42 +- src/database/models/user_item.rs | 13 +- src/main.rs | 13 + src/models/projects.rs | 7 + src/models/teams.rs | 9 +- src/models/users.rs | 2 + src/queue/flameanvil.rs | 250 ++ src/queue/mod.rs | 1 + src/queue/payouts.rs | 26 +- src/routes/auth.rs | 1 + src/routes/project_creation.rs | 14 + src/routes/projects.rs | 99 + src/routes/teams.rs | 8 +- src/routes/users.rs | 42 +- src/routes/v1/mods.rs | 4 + src/routes/version_creation.rs | 120 +- src/util/auth.rs | 1 + 23 files changed, 2501 insertions(+), 1493 deletions(-) create mode 100644 migrations/20221116200727_flame-anvil-integration.sql create mode 100644 src/queue/flameanvil.rs diff --git a/.env b/.env index dcc76301a..f4dd1732a 100644 --- a/.env +++ b/.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 diff --git a/Cargo.lock b/Cargo.lock index fa1b7d5fc..8b2ae8b71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 6bbc541a2..99938d512 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "labrinth" -version = "2.5.0" +version = "2.6.0" authors = ["geometrically "] 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"] } \ No newline at end of file +rust_decimal = { version = "1.26", features = ["serde-with-float", "serde-with-str"] } + +sentry = "0.28.0" +sentry-actix = "0.28.0" diff --git a/migrations/20221116200727_flame-anvil-integration.sql b/migrations/20221116200727_flame-anvil-integration.sql new file mode 100644 index 000000000..f5fe8759d --- /dev/null +++ b/migrations/20221116200727_flame-anvil-integration.sql @@ -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; diff --git a/sqlx-data.json b/sqlx-data.json index 5400f6250..e44bb09ca 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -225,230 +225,6 @@ }, "query": "\n UPDATE versions\n SET name = $1\n WHERE (id = $2)\n " }, - "0aa77d3833a3f0b2ed198b7928ef4170c6e3dffcf5144050c8b11fcb911e127f": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 23, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 24, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "short", - "ordinal": 28, - "type_info": "Varchar" - }, - { - "name": "license_name", - "ordinal": 29, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 30, - "type_info": "Varchar" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 32, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 34, - "type_info": "TextArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n 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,\n 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,\n 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n 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,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " - }, "0ba5a9f4d1381ed37a67b7dc90edf7e3ec86cae6c2860e5db1e53144d4654e58": { "describe": { "columns": [ @@ -814,6 +590,19 @@ }, "query": "\n DELETE FROM donation_platforms\n WHERE short = $1\n " }, + "1d1fe6f0c03a63b1c6bd5ffbddfd82aa7d24e1db3f3137ed046724cb78929f88": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET flame_anvil_key = $1\n WHERE (id = $2)\n " + }, "1d3b582e6765e1ae578039e44b5dc9be6f3f845c96ffd43b7ba83f9eab816f93": { "describe": { "columns": [ @@ -894,6 +683,31 @@ }, "query": "SELECT EXISTS(SELECT 1 FROM versions WHERE id=$1)" }, + "20061dd085656ec83b63d893c60bdd3dcbf5b8c78a725358aa8e3312a7571e5c": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET flame_anvil_user = $1\n WHERE (id = $2)\n " + }, + "20413fce27fe9c1dec71900f9563e787acc11e7789b5294786e0ea6f20d7d958": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET flame_anvil_user = NULL\n WHERE (flame_anvil_user = $1)\n " + }, "20c6f94eae9260fc3f91de3e4a42c544e0b5c01227854956d04db7641c03c1b8": { "describe": { "columns": [ @@ -928,6 +742,26 @@ }, "query": "\n DELETE FROM mods_gallery\n WHERE mod_id = $1\n " }, + "21ef50f46b7b3e62b91e7d067c1cb33806e14c33bb76d63c2711f822c44261f6": { + "describe": { + "columns": [ + { + "name": "name", + "ordinal": 0, + "type_info": "Varchar" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT name FROM project_types pt\n INNER JOIN mods ON mods.project_type = pt.id\n WHERE mods.id = $1\n " + }, "220e59ae72edef546e3c7682ae91336bfba3e4230add1543910d80e846e0ad95": { "describe": { "columns": [ @@ -1239,6 +1073,110 @@ }, "query": "\n UPDATE mods\n SET discord_url = $1\n WHERE (id = $2)\n " }, + "2c1b5ee43d200cb643891113ba686ded7b7d29048b6bcdf19cea9cb8952e51e3": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "github_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "role", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 9, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 10, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 14, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + true, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE u.id = ANY($1)\n " + }, "2d2e5b06be5125226ed9e4d7b7b5f99043db73537f2199f2146bdcd56091ae75": { "describe": { "columns": [], @@ -1325,164 +1263,6 @@ }, "query": "\n UPDATE mods\n SET follows = follows - 1\n WHERE id = $1\n " }, - "391f734b23be4346960ea82bc3d0cefe7de025fe0e400634e3dfc136486e7fc9": { - "describe": { - "columns": [ - { - "name": "project_type", - "ordinal": 0, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 11, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 17, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 18, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 21, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 23, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body\n FROM mods\n WHERE id = $1\n " - }, "398ac436f5fe2f6a66544204b9ff01ae1ea1204edf03ffc16de657a861cfe0ba": { "describe": { "columns": [], @@ -1568,6 +1348,244 @@ }, "query": "\n UPDATE mods_gallery\n SET title = $2\n WHERE id = $1\n " }, + "3c9dc1f9c31f9c315f64b6423890cba46cb8c6e456e47965ba8e971853b10956": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 6, + "type_info": "Timestamptz" + }, + { + "name": "role", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 8, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 9, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 13, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT u.id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE u.github_id = $1\n " + }, + "3cb1a3e9db4ddbda0034be3b9f774910f755625b8501c84948cc76067da95052": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "team_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "member_role", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "permissions", + "ordinal": 3, + "type_info": "Int8" + }, + { + "name": "accepted", + "ordinal": 4, + "type_info": "Bool" + }, + { + "name": "payouts_split", + "ordinal": 5, + "type_info": "Numeric" + }, + { + "name": "user_id", + "ordinal": 6, + "type_info": "Int8" + }, + { + "name": "github_id", + "ordinal": 7, + "type_info": "Int8" + }, + { + "name": "user_name", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 9, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 13, + "type_info": "Timestamptz" + }, + { + "name": "user_role", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 15, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 16, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 19, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 20, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,\n u.id user_id, u.github_id github_id, u.name user_name, u.email email,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role, u.badges badges, u.balance balance,\n u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,\n u.payout_address payout_address, u.flame_anvil_key flame_anvil_key\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = ANY($1)\n ORDER BY tm.team_id\n " + }, "3d384766d179f804c17e03d1917da65cc6043f88971ddc3fd23ba3be00717dfc": { "describe": { "columns": [ @@ -1636,6 +1654,242 @@ }, "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n VALUES ($1, $2)\n " }, + "3fd03e7fa83a108d7d2720607e9fce0e2aab060c441d8fbdec3404a379098138": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 18, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "slug", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "short", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "license_name", + "ordinal": 29, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 30, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 31, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 32, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 34, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 35, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 36, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + true, + true, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n 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,\n 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,\n 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n 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,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " + }, "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08": { "describe": { "columns": [], @@ -1765,26 +2019,6 @@ }, "query": "\n INSERT INTO historical_payouts (user_id, amount, status)\n VALUES ($1, $2, $3)\n " }, - "48294a4e0c594e80fff8d14a705aa7282f55e47cf3772e77f1d4bf4849008b60": { - "describe": { - "columns": [ - { - "name": "follower_id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n " - }, "496a710e45c1d0603667e5b7117c02162e27c3ce7b905b2691fda8e046c5373b": { "describe": { "columns": [ @@ -2036,104 +2270,6 @@ }, "query": "\n DELETE FROM game_versions_versions WHERE joining_version_id = $1\n " }, - "50fc72532d4b61d117b6245b5e315f3d88e9fe3a67f4522d281270911177f3ed": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "github_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "role", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 9, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 10, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 13, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - true, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE LOWER(u.username) = LOWER($1)\n " - }, "5295fba2053675c8414c0b37a59943535b9a438a642ea1c68045e987f05ade13": { "describe": { "columns": [ @@ -2844,18 +2980,175 @@ }, "query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n " }, - "70cdf1b4a17405974909d89b1437a8425792d620f9ed67fd8e31e004e4609e83": { + "70a6c63bb0162679cad2634d8c7cc0a64b1bbbc678666de3198118a4086cc6b4": { "describe": { - "columns": [], - "nullable": [], + "columns": [ + { + "name": "project_type", + "ordinal": 0, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 3, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 8, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 11, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 17, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 18, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "slug", + "ordinal": 21, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 24, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 25, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true + ], "parameters": { "Left": [ - "Varchar", "Int8" ] } }, - "query": "\n UPDATE users\n SET username = $1\n WHERE (id = $2)\n " + "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body, flame_anvil_project,\n flame_anvil_user\n FROM mods\n WHERE id = $1\n " }, "712a846d6b56609599ee7a6603ad921acd2d5da2b3ce0c5b3f3642ed83927542": { "describe": { @@ -3290,140 +3583,6 @@ }, "query": "\n INSERT INTO notifications_actions (\n notification_id, title, action_route, action_route_method\n )\n VALUES (\n $1, $2, $3, $4\n )\n " }, - "8187df6b7a47085ec9f844737755c75b162d154a6c38ee2f88c68ebc673baaab": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "team_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "member_role", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "permissions", - "ordinal": 3, - "type_info": "Int8" - }, - { - "name": "accepted", - "ordinal": 4, - "type_info": "Bool" - }, - { - "name": "payouts_split", - "ordinal": 5, - "type_info": "Numeric" - }, - { - "name": "user_id", - "ordinal": 6, - "type_info": "Int8" - }, - { - "name": "github_id", - "ordinal": 7, - "type_info": "Int8" - }, - { - "name": "user_name", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 9, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 13, - "type_info": "Timestamptz" - }, - { - "name": "user_role", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 15, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 16, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 18, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 19, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - true, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT tm.id id, tm.team_id team_id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,\n u.id user_id, u.github_id github_id, u.name user_name, u.email email,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role, u.badges badges, u.balance balance,\n u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,\n u.payout_address payout_address\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = ANY($1)\n ORDER BY tm.team_id\n " - }, "82515e4e7e88f1193c956f032caabc70f535f925e212de30f974afd3ec126092": { "describe": { "columns": [ @@ -3466,6 +3625,19 @@ }, "query": "\n SELECT m.id FROM mods m\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE\n WHERE tm.user_id = $1 AND m.status = (SELECT s.id FROM statuses s WHERE s.status = $2)\n ORDER BY m.downloads DESC\n " }, + "868ee76d507cc9e94cd3c2e44770faff127e2b3c5f49b8100a9a37ac4d7b1f1d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET username = $1\n WHERE (id = $2)\n " + }, "87fd169e19ba231c6cf131ad2841d5c3b95adde53e5ed4000f8e7d54c0e87320": { "describe": { "columns": [], @@ -3478,98 +3650,6 @@ }, "query": "\n DELETE FROM project_types\n WHERE name = $1\n " }, - "886cc346f5ecc958018f7cab7dc3db9f8766fcdc7b16d686504ddcb6c5dde0b0": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 6, - "type_info": "Timestamptz" - }, - { - "name": "role", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 8, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 9, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 12, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT u.id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE u.github_id = $1\n " - }, "8a4d05106f27de01a1a8ebf5266727b71fa5b6a34b48cd9ba3996fe8ec1ab78a": { "describe": { "columns": [ @@ -4035,6 +4115,208 @@ }, "query": "\n SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3\n " }, + "9b2d1298e02d75dd3c56a7813ec2cbfc899361f678d0af41aba9f0ad08fe7f2d": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 18, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "slug", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 25, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 26, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body, flame_anvil_project,\n flame_anvil_user\n FROM mods\n WHERE id = ANY($1)\n " + }, + "9bbb52954fccebc8aa467618dcb2c224722c8d816ec6803bcd8711778bd56199": { + "describe": { + "columns": [ + { + "name": "flame_anvil_project", + "ordinal": 0, + "type_info": "Int4" + }, + { + "name": "flame_anvil_key", + "ordinal": 1, + "type_info": "Varchar" + } + ], + "nullable": [ + true, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT m.flame_anvil_project, u.flame_anvil_key\n FROM mods m\n INNER JOIN users u ON m.flame_anvil_user = u.id\n WHERE m.id = $1\n " + }, "9c8f3f9503b5bb52e05bbc8a8eee7f640ab7d6b04a59ec111ce8b23e886911de": { "describe": { "columns": [], @@ -4238,104 +4520,6 @@ }, "query": "\n DELETE FROM states\n WHERE expires < CURRENT_DATE\n " }, - "a91e7409e72211acf36cdcc4ee3395ef350acbf7be401e190dfbabdf60ebe155": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "github_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "role", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 9, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 10, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 13, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - true, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE u.id = ANY($1)\n " - }, "a91fabe9e620bd700362c68631628725419183025c9699f4bd31c22b813b2824": { "describe": { "columns": [ @@ -4748,98 +4932,6 @@ }, "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published, m.approved approved, m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null) gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1 OR s.status = $2\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n " }, - "b4e67474dcef7d8357c3336b154425d1fff9d70757f8393e285883b739f5fadf": { - "describe": { - "columns": [ - { - "name": "github_id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 6, - "type_info": "Timestamptz" - }, - { - "name": "role", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 8, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 9, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 12, - "type_info": "Varchar" - } - ], - "nullable": [ - true, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address\n FROM users u\n WHERE u.id = $1\n " - }, "b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169": { "describe": { "columns": [], @@ -4852,6 +4944,19 @@ }, "query": "\n DELETE FROM users\n WHERE id = $1\n " }, + "b69b18b3451762fc24a1390dd537f612ed066bd285e8237a99fb998ff9d066e9": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET flame_anvil_project = $1\n WHERE (id = $2)\n " + }, "b7b2b5b99340c7601de53cc33dc56af054b50b2fe4d1d212901c958115a42baa": { "describe": { "columns": [], @@ -5550,6 +5655,244 @@ }, "query": "\n DELETE FROM hashes\n WHERE file_id = $1\n " }, + "ce46915d4ce10f3fc2d4328157b06016da838672c8336b3b8d27e09eeec979d3": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "github_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "role", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 9, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 10, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 14, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + true, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE LOWER(u.username) = LOWER($1)\n " + }, + "ce9122387ca0e8f6976810018c79eb692740ad7e60384cab14c4ab85449d4591": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "member_role", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "permissions", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "accepted", + "ordinal": 3, + "type_info": "Bool" + }, + { + "name": "payouts_split", + "ordinal": 4, + "type_info": "Numeric" + }, + { + "name": "user_id", + "ordinal": 5, + "type_info": "Int8" + }, + { + "name": "github_id", + "ordinal": 6, + "type_info": "Int8" + }, + { + "name": "user_name", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 9, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 12, + "type_info": "Timestamptz" + }, + { + "name": "user_role", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 14, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 15, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 19, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,\n u.id user_id, u.github_id github_id, u.name user_name, u.email email,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role, u.badges badges, u.balance balance,\n u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,\n u.payout_address payout_address, u.flame_anvil_key flame_anvil_key\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = $1\n " + }, "cef01012769dcd499a0d16ce65ffc1e94bce362a7246b6a0a38d133afb90d3b6": { "describe": { "columns": [], @@ -5630,6 +5973,242 @@ }, "query": "\n DELETE FROM team_members\n WHERE team_id = $1\n " }, + "d13a0ae0f7600a96353ef0555b2f9bf4e254f5f9d3f243a18c04bdb8b6d0322e": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 18, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "slug", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "short", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "license_name", + "ordinal": 29, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 30, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 31, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 32, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 34, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 35, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 36, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + true, + true, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n 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,\n 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,\n 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n 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,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " + }, "d1866ecc161c3fe3fbe094289510e99b17de563957e1f824c347c1e6ac40c40c": { "describe": { "columns": [ @@ -5906,134 +6485,6 @@ }, "query": "\n SELECT id FROM donation_platforms\n WHERE short = $1\n " }, - "d83c10db0ab21343ab17acad30546ecebeae2347b12c4604532eccf66dd99b7a": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "member_role", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "permissions", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "accepted", - "ordinal": 3, - "type_info": "Bool" - }, - { - "name": "payouts_split", - "ordinal": 4, - "type_info": "Numeric" - }, - { - "name": "user_id", - "ordinal": 5, - "type_info": "Int8" - }, - { - "name": "github_id", - "ordinal": 6, - "type_info": "Int8" - }, - { - "name": "user_name", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "email", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "avatar_url", - "ordinal": 9, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "bio", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "created", - "ordinal": 12, - "type_info": "Timestamptz" - }, - { - "name": "user_role", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "badges", - "ordinal": 14, - "type_info": "Int8" - }, - { - "name": "balance", - "ordinal": 15, - "type_info": "Numeric" - }, - { - "name": "payout_wallet", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "payout_wallet_type", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "payout_address", - "ordinal": 18, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - true, - true, - true, - false, - true, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split,\n u.id user_id, u.github_id github_id, u.name user_name, u.email email,\n u.avatar_url avatar_url, u.username username, u.bio bio,\n u.created created, u.role user_role, u.badges badges, u.balance balance,\n u.payout_wallet payout_wallet, u.payout_wallet_type payout_wallet_type,\n u.payout_address payout_address\n FROM team_members tm\n INNER JOIN users u ON u.id = tm.user_id\n WHERE tm.team_id = $1\n " - }, "d8b4e7e382c77a05395124d5a6a27cccb687d0e2c31b76d49b03aa364d099d42": { "describe": { "columns": [], @@ -6253,6 +6704,19 @@ }, "query": "\n DELETE FROM files\n WHERE files.id = $1\n " }, + "e42d3a64ae4d88b73136a319fe79a8b070c193707e3560d18deca478662d8d90": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET flame_anvil_user = NULL\n WHERE (team_id = $1 AND flame_anvil_user = $2 )\n " + }, "e48c85a2b2e11691afae3799aa126bdd8b7338a973308bbab2760c18bb9cb0b7": { "describe": { "columns": [], @@ -6266,6 +6730,125 @@ }, "query": "\n UPDATE versions\n SET featured = $1\n WHERE (id = $2)\n " }, + "e5a485770edb23ed77c56cb0bbd7ed28f8789c740c194ff23c44eafab78d440c": { + "describe": { + "columns": [ + { + "name": "github_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "email", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "avatar_url", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "bio", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "created", + "ordinal": 6, + "type_info": "Timestamptz" + }, + { + "name": "role", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "badges", + "ordinal": 8, + "type_info": "Int8" + }, + { + "name": "balance", + "ordinal": 9, + "type_info": "Numeric" + }, + { + "name": "payout_wallet", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "payout_wallet_type", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "payout_address", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_key", + "ordinal": 13, + "type_info": "Varchar" + } + ], + "nullable": [ + true, + true, + true, + true, + false, + true, + false, + false, + false, + false, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE u.id = $1\n " + }, + "e5de3b33893b6b48a7fee0e3f20e371e56fdfd71640662aacc15fe3bf747b3a1": { + "describe": { + "columns": [ + { + "name": "exists", + "ordinal": 0, + "type_info": "Bool" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + } + }, + "query": "\n SELECT EXISTS(\n SELECT 1 FROM team_members\n INNER JOIN users u on team_members.user_id = u.id AND u.flame_anvil_key IS NOT NULL\n WHERE team_id = $1 AND user_id = $2 AND accepted = TRUE\n )\n " + }, "e673006d1355fa91ba5739d7cf569eec5e1ec501f7b1dc2b431f0b1c25ac07d5": { "describe": { "columns": [], @@ -6530,230 +7113,6 @@ }, "query": "\n SELECT name FROM project_types pt\n INNER JOIN mods ON mods.project_type = pt.id\n WHERE mods.id = $1\n " }, - "ef86b7759f479cb04d6aaf02688315bb5674e6eadade41cdd11947943356bea8": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 23, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 24, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "short", - "ordinal": 28, - "type_info": "Varchar" - }, - { - "name": "license_name", - "ordinal": 29, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 30, - "type_info": "Varchar" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 32, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 34, - "type_info": "TextArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n 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,\n 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,\n 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n 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,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " - }, "f0db9d8606ccc2196a9cfafe0e7090dab42bf790f25e0469b8947fac1cf043d5": { "describe": { "columns": [ @@ -6932,169 +7291,25 @@ }, "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " }, - "fd0bcd40aa2ac11266777e586d6438950cd869b7f73a1545af5ef484f9495a5e": { + "fdfe36dcb85347a3a8228b5d5fc2d017b9baa307b5ae0ae9deaafab9dcdcb74a": { "describe": { "columns": [ { - "name": "id", + "name": "follower_id", "ordinal": 0, "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 23, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 24, - "type_info": "Varchar" } ], "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true + false ], "parameters": { "Left": [ - "Int8Array" + "Int8" ] } }, - "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body\n FROM mods\n WHERE id = ANY($1)\n " + "query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n " }, "feaaae02e9d35fbfb9f83d3b653902b58489f498c493b6b0ed73ad4e1f863468": { "describe": { diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index 1f3b4ad07..af8ec62fc 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -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, pub moderation_message: Option, pub moderation_message_body: Option, + pub flame_anvil_project: Option, + pub flame_anvil_user: Option, } 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::>() @@ -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, diff --git a/src/database/models/team_item.rs b/src/database/models/team_item.rs index 1cc7aadad..02a9e6f6a 100644 --- a/src/database/models/team_item.rs +++ b/src/database/models/team_item.rs @@ -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(()) } diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index 5b36c96ab..cdfb1f89b 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -18,6 +18,7 @@ pub struct User { pub payout_wallet: Option, pub payout_wallet_type: Option, pub payout_address: Option, + pub flame_anvil_key: Option, } 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::>() diff --git a/src/main.rs b/src/main.rs index 792a051c9..15b59fda7 100644 --- a/src/main.rs +++ b/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) diff --git a/src/models/projects.rs b/src/models/projects.rs index a5f850ae5..b9ba81d19 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -89,6 +89,11 @@ pub struct Project { /// A string of URLs to visual content featuring the project pub gallery: Vec, + + /// The project linked from FlameAnvil to sync with + pub flame_anvil_project: Option, + /// The user_id of the team member whose token + pub flame_anvil_user: Option, } impl From for Project { @@ -153,6 +158,8 @@ impl From for Project { created: x.created, }) .collect(), + flame_anvil_project: m.flame_anvil_project, + flame_anvil_user: m.flame_anvil_user.map(|x| x.into()), } } } diff --git a/src/models/teams.rs b/src/models/teams.rs index 1b2d8f69e..173c801de 100644 --- a/src/models/teams.rs +++ b/src/models/teams.rs @@ -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 diff --git a/src/models/users.rs b/src/models/users.rs index 8073c0e76..b923c869f 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -47,6 +47,7 @@ pub struct User { pub role: Role, pub badges: Badges, pub payout_data: Option, + pub has_flame_anvil_key: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -140,6 +141,7 @@ impl From for User { role: Role::from_string(&data.role), badges: data.badges, payout_data: None, + has_flame_anvil_key: None, } } } diff --git a/src/queue/flameanvil.rs b/src/queue/flameanvil.rs new file mode 100644 index 000000000..696afbc63 --- /dev/null +++ b/src/queue/flameanvil.rs @@ -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, + release_type: String, + // TODO: relations? +} + +pub struct FlameAnvilQueue { + mod_loaders: Vec, + minecraft_versions: Vec, + last_updated: DateTime, +} + +pub struct UploadFile { + pub loaders: Vec, + pub game_versions: Vec, + 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, + game_versions: &[GameVersion], + ) -> Vec { + 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, + file_name: String, + mime_type: String, + ) -> Result { + 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::>(); + + 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::() + .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::>(), + game_version_types.json::>(), + ) + .await?; + + let mod_loader_types = game_version_types + .iter() + .filter(|x| x.slug == *"modloader") + .map(|x| x.id) + .collect::>(); + let minecraft_types = game_version_types + .iter() + .filter(|x| x.slug.starts_with("minecraft")) + .map(|x| x.id) + .collect::>(); + + let mod_loaders = game_versions + .iter() + .filter(|x| mod_loader_types.contains(&x.game_version_type_id)) + .cloned() + .collect::>(); + let minecraft_versions = game_versions + .iter() + .filter(|x| minecraft_types.contains(&x.game_version_type_id)) + .cloned() + .collect::>(); + + self.mod_loaders = mod_loaders; + self.minecraft_versions = minecraft_versions; + + Ok(()) + } +} diff --git a/src/queue/mod.rs b/src/queue/mod.rs index f2d7abb4c..f410a6ef0 100644 --- a/src/queue/mod.rs +++ b/src/queue/mod.rs @@ -1,2 +1,3 @@ pub mod download; +pub mod flameanvil; pub mod payouts; diff --git a/src/queue/payouts.rs b/src/queue/payouts.rs index 0ac919c48..97963d218 100644 --- a/src/queue/payouts.rs +++ b/src/queue/payouts.rs @@ -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::().await.ok() { + if let Ok(res) = res.json::().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::().await.ok() { + if let Ok(res) = res.json::().await { if let Some(data) = res.items.first() { if (fee - data.payout_item_fee.value) > Decimal::ZERO diff --git a/src/routes/auth.rs b/src/routes/auth.rs index cc3964ba5..43d81b98f 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -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?; diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index a972570d2..0b8d3dd33 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -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, file_host: Data>, + flame_anvil_queue: Data>>, ) -> Result { 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, uploaded_files: &mut Vec, ) -> Result { // 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?; diff --git a/src/routes/projects.rs b/src/routes/projects.rs index 6e86bedcf..6561ee9af 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -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>, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "::serde_with::rust::double_option" + )] + pub flame_anvil_user: Option>, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "::serde_with::rust::double_option" + )] + pub flame_anvil_project: Option>, } #[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 { diff --git a/src/routes/teams.rs b/src/routes/teams.rs index 4282bf755..c31285187 100644 --- a/src/routes/teams.rs +++ b/src/routes/teams.rs @@ -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("")) diff --git a/src/routes/users.rs b/src/routes/users.rs index d0801948f..cf46d4bbd 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -167,6 +167,13 @@ pub struct EditUser { )] #[validate] pub payout_data: Option>, + #[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>, } #[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 { diff --git a/src/routes/v1/mods.rs b/src/routes/v1/mods.rs index 9054a7e99..40378eb06 100644 --- a/src/routes/v1/mods.rs +++ b/src/routes/v1/mods.rs @@ -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, file_host: Data>, + flame_anvil_queue: Data>>, ) -> Result { 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; diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index a8a2370e9..172f87914 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -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, - file_host: Data>, + file_host: Data>, + flame_anvil_queue: Data>>, ) -> Result { 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, uploaded_files: &mut Vec, ) -> Result { 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, - file_host: Data>, + file_host: Data>, + flame_anvil_queue: Data>>, ) -> Result { 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, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, file_host: &dyn FileHost, + flame_anvil_queue: &Mutex, uploaded_files: &mut Vec, version_id: models::VersionId, ) -> Result { @@ -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, version_files: &mut Vec, dependencies: &mut Vec, @@ -633,6 +671,12 @@ pub async fn upload_file( all_game_versions: Vec, ignore_primary: bool, force_primary: bool, + version_display_name: String, + version_changelog: String, + version_type: String, + flame_anvil_queue: &Mutex, + flame_anvil_project: Option, + flame_anvil_key: Option, 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, }); diff --git a/src/util/auth.rs b/src/util/auth.rs index 52556cda6..a17738493 100644 --- a/src/util/auth.rs +++ b/src/util/auth.rs @@ -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), }