From 5bb188a822499613dcfdd4bf54bca2092eba1e43 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Thu, 29 Dec 2022 17:20:50 -0700 Subject: [PATCH] Project Colors (#512) * Inital tests * Finish project colors * Run fmt + clippy + prepare * Fix dp+rp fmting --- .idea/sqldialects.xml | 6 - Cargo.lock | 270 ++- Cargo.toml | 3 + migrations/20221227010515_project-colors.sql | 2 + sqlx-data.json | 2088 +++++++++--------- src/database/models/project_item.rs | 22 +- src/models/projects.rs | 4 + src/routes/mod.rs | 7 + src/routes/project_creation.rs | 23 +- src/routes/projects.rs | 10 +- src/search/indexing/local_import.rs | 3 +- src/search/mod.rs | 2 + src/util/ext.rs | 5 +- src/util/img.rs | 19 + src/util/mod.rs | 1 + src/util/webhook.rs | 19 +- 16 files changed, 1422 insertions(+), 1062 deletions(-) delete mode 100644 .idea/sqldialects.xml create mode 100644 migrations/20221227010515_project-colors.sql create mode 100644 src/util/img.rs diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index 6df4889b0..000000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 77398bf0c..938439ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.5.4", "object", "rustc-demangle", ] @@ -464,6 +464,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + [[package]] name = "bitflags" version = "1.3.2" @@ -515,6 +521,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" + [[package]] name = "byteorder" version = "1.4.3" @@ -634,6 +646,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color-thief" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6460d760cf38ce67c9e0318f896538820acc54f2d0a3bfc5b2c557211066c98" +dependencies = [ + "rgb", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "concurrent-queue" version = "1.2.4" @@ -725,6 +752,30 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.6" @@ -744,6 +795,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -999,6 +1056,21 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "exr" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb5f255b5980bb0c8cf676b675d1a99be40f316881444f44e0462eaf5df5ded" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide 0.6.2", + "smallvec", + "threadpool", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -1015,7 +1087,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.4", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin 0.9.4", ] [[package]] @@ -1186,8 +1271,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", ] [[package]] @@ -1215,6 +1312,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad6a9459c9c30b177b925162351f97e7d967c7ea8bab3b8352805327daf45554" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1430,6 +1536,25 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +[[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1525,6 +1650,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.60" @@ -1549,6 +1683,7 @@ dependencies = [ "bytes", "censor", "chrono", + "color-thief", "dashmap", "dotenvy", "env_logger", @@ -1556,6 +1691,7 @@ dependencies = [ "futures-timer", "hex", "hmac 0.11.0", + "image", "itertools 0.10.5", "lazy_static", "log", @@ -1598,6 +1734,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "lexical-core" version = "0.7.6" @@ -1764,6 +1906,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -1804,6 +1955,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.5" @@ -1816,6 +1976,15 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -1876,6 +2045,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -2119,6 +2299,18 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide 0.6.2", +] + [[package]] name = "polling" version = "2.4.0" @@ -2220,6 +2412,28 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2317,6 +2531,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "rgb" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.16.20" @@ -2326,7 +2549,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -2455,6 +2678,12 @@ dependencies = [ "windows-sys 0.36.1", ] +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2802,6 +3031,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +dependencies = [ + "lock_api", +] + [[package]] name = "sqlformat" version = "0.2.0" @@ -2994,6 +3232,26 @@ dependencies = [ "syn", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tiff" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.1.44" @@ -3474,6 +3732,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "wepoll-ffi" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index b4765bac8..1d20f4fb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,3 +69,6 @@ rust_decimal = { version = "1.26", features = ["serde-with-float", "serde-with-s sentry = "0.29.1" sentry-actix = "0.29.1" + +image = "0.24.5" +color-thief = "0.2.2" diff --git a/migrations/20221227010515_project-colors.sql b/migrations/20221227010515_project-colors.sql new file mode 100644 index 000000000..8928d6565 --- /dev/null +++ b/migrations/20221227010515_project-colors.sql @@ -0,0 +1,2 @@ +-- Add migration script here +ALTER TABLE mods ADD COLUMN color integer NULL; \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 16d319375..63266bdc1 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -102,6 +102,18 @@ }, "query": "\n UPDATE mods\n SET moderation_message_body = NULL\n WHERE (id = $1)\n " }, + "04e5ecb14c526000e9098efb65861f6125e6fcc88f39d6ad811ac8504d229de1": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET icon_url = NULL, color = NULL\n WHERE (id = $1)\n " + }, "05baeb26d9856218e5c6f8856a96788b2a7ac3536ff9412a50552cef1d561a1e": { "describe": { "columns": [], @@ -1701,182 +1713,6 @@ }, "query": "\n SELECT f.version_id version_id\n FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v on f.version_id = v.id AND v.status != ANY($1)\n INNER JOIN mods m on v.mod_id = m.id\n WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4)\n " }, - "3b898f215cf98cbe9204f93b04be659790d2278610da186304bbf056533d5c55": { - "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": "published", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 10, - "type_info": "Varchar" - }, - { - "name": "requested_status", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "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": "Varchar" - }, - { - "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" - }, - { - "name": "webhook_sent", - "ordinal": 26, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - false, - false, - true, - false, - true, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - true, - true, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, published,\n updated, approved, status, requested_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, webhook_sent\n FROM mods\n WHERE id = $1\n " - }, "3bdcbfa5abe43cc9b4f996f147277a7f6921cca00f82cad0ef5d85032c761a36": { "describe": { "columns": [], @@ -2114,17 +1950,187 @@ }, "query": "\n SELECT EXISTS(SELECT 1 FROM hashes h\n WHERE h.algorithm = $2 AND h.hash = $1)\n " }, - "436dbf448697436ec90c30f44b27c92ec626601e7a7a9edb4d11bd916741b60f": { + "42d0a51c5a2eec6860a904b7753c4907b1b03fb5f081f07149367222bcdc62fb": { "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": "published", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 8, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "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": "Varchar" + }, + { + "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" + }, + { + "name": "webhook_sent", + "ordinal": 26, + "type_info": "Bool" + }, + { + "name": "color", + "ordinal": 27, + "type_info": "Int4" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false, + true + ], "parameters": { "Left": [ "Int8" ] } }, - "query": "\n UPDATE mods\n SET icon_url = NULL\n WHERE (id = $1)\n " + "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, published,\n updated, approved, status, requested_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, webhook_sent, color\n FROM mods\n WHERE id = $1\n " }, "447350097928db863d47d756354cd52668f52f7156dd7f3673a826f7b9aca2fd": { "describe": { @@ -3151,6 +3157,20 @@ }, "query": "\n DELETE FROM versions WHERE id = $1\n " }, + "73d77f11f97a9073f601119c6eb450ea08ae1d2df1a27ba9af1efa972ed9a836": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int4", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET icon_url = $1, color = $2\n WHERE (id = $3)\n " + }, "742f20f422361971c21b72c629c57a6c3870d8d6c41577496907290db5994f12": { "describe": { "columns": [], @@ -3458,6 +3478,194 @@ }, "query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY updated ASC\n LIMIT $2;\n " }, + "7c3ab073109e0fd0a01a0c02a9a2b04050d76874520c59c574d10b071c5e88ab": { + "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": "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": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "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": "Varchar" + }, + { + "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" + }, + { + "name": "webhook_sent", + "ordinal": 27, + "type_info": "Bool" + }, + { + "name": "color", + "ordinal": 28, + "type_info": "Int4" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, published,\n updated, approved, status, requested_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, webhook_sent, color\n FROM mods\n WHERE id = ANY($1)\n " + }, "7c61fee015231f0a97c25d24f2c6be24821e39e330ab82344ad3b985d0d2aaea": { "describe": { "columns": [ @@ -3827,179 +4035,6 @@ }, "query": "\n DELETE FROM loaders\n WHERE loader = $1\n " }, - "8fd5d332e9cd2f760f956bf4936350f29df414552643bcfb352ca8a8a0b98439": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - } - }, - "query": "\n UPDATE mods\n SET icon_url = $1\n WHERE (id = $2)\n " - }, - "9225b9e3b3262227b26d8da5aa1423495ca0f9052f992a11440d0333b177c702": { - "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": "published", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "team_id", - "ordinal": 10, - "type_info": "Int8" - }, - { - "name": "license", - "ordinal": 11, - "type_info": "Varchar" - }, - { - "name": "slug", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "categories", - "ordinal": 18, - "type_info": "VarcharArray" - }, - { - "name": "additional_categories", - "ordinal": 19, - "type_info": "VarcharArray" - }, - { - "name": "loaders", - "ordinal": 20, - "type_info": "VarcharArray" - }, - { - "name": "versions", - "ordinal": 21, - "type_info": "VarcharArray" - }, - { - "name": "gallery", - "ordinal": 22, - "type_info": "VarcharArray" - }, - { - "name": "featured_gallery", - "ordinal": 23, - "type_info": "VarcharArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - false, - false, - false, - false, - false, - null, - null, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "TextArray", - "TextArray", - "Text" - ] - } - }, - "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, m.status status_name,\n cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_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 and mg.featured is false) gallery,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_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 AND v.status != ANY($1)\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 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 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 m.status = ANY($2)\n GROUP BY m.id, cs.id, ss.id, pt.id, u.id;\n " - }, "9348309884811e8b22f33786ae7c0f259f37f3c90e545f00761a641570107160": { "describe": { "columns": [ @@ -4187,237 +4222,6 @@ }, "query": "\n SELECT u.stripe_customer_id\n FROM users u\n WHERE u.id = $1\n " }, - "9f6fda46555ce4466b69cabecfa5e167918e54b61257e9b0f5c4fdb7cdbdf4d9": { - "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": "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": "Varchar" - }, - { - "name": "requested_status", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "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": "Varchar" - }, - { - "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": "client_side_type", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "flame_anvil_project", - "ordinal": 28, - "type_info": "Int4" - }, - { - "name": "flame_anvil_user", - "ordinal": 29, - "type_info": "Int8" - }, - { - "name": "webhook_sent", - "ordinal": 30, - "type_info": "Bool" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "VarcharArray" - }, - { - "name": "additional_categories", - "ordinal": 32, - "type_info": "VarcharArray" - }, - { - "name": "versions", - "ordinal": 33, - "type_info": "Jsonb" - }, - { - "name": "gallery", - "ordinal": 34, - "type_info": "Jsonb" - }, - { - "name": "donations", - "ordinal": 35, - "type_info": "Jsonb" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - true, - false, - true, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - true, - true, - false, - null, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8", - "TextArray" - ] - } - }, - "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.published published,\n m.updated updated, m.approved approved, m.status status, m.requested_status requested_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 cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent webhook_sent,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,\n JSONB_AGG(DISTINCT jsonb_build_object('image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering)) filter (where mg.image_url is not null) gallery,\n JSONB_AGG(DISTINCT jsonb_build_object('platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', 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 side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.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 AND v.status = ANY($2)\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, cs.id, ss.id, m.id;\n " - }, "a0148ff25855202e7bb220b6a2bc9220a95e309fb0dae41d9a05afa86e6b33af": { "describe": { "columns": [], @@ -4659,6 +4463,243 @@ }, "query": "\n SELECT id FROM categories\n WHERE category = $1 AND project_type = $2\n " }, + "abdd57a514fc34bca239384c16c7a18f1faffcf1db9616ef0576fc2b1691a16e": { + "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": "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": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "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": "Varchar" + }, + { + "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": "client_side_type", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 28, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 29, + "type_info": "Int8" + }, + { + "name": "webhook_sent", + "ordinal": 30, + "type_info": "Bool" + }, + { + "name": "color", + "ordinal": 31, + "type_info": "Int4" + }, + { + "name": "categories", + "ordinal": 32, + "type_info": "VarcharArray" + }, + { + "name": "additional_categories", + "ordinal": 33, + "type_info": "VarcharArray" + }, + { + "name": "versions", + "ordinal": 34, + "type_info": "Jsonb" + }, + { + "name": "gallery", + "ordinal": 35, + "type_info": "Jsonb" + }, + { + "name": "donations", + "ordinal": 36, + "type_info": "Jsonb" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + false, + true, + null, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8Array", + "TextArray" + ] + } + }, + "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.published published,\n m.updated updated, m.approved approved, m.status status, m.requested_status requested_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 cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent, m.color,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,\n JSONB_AGG(DISTINCT jsonb_build_object('image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering)) filter (where mg.image_url is not null) gallery,\n JSONB_AGG(DISTINCT jsonb_build_object('platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', 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 side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.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 AND v.status = ANY($2)\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, cs.id, ss.id, m.id;\n " + }, "ac2d17b7d7147b14f072c15ffa214c14f32f27ffa6a3c2b2a5f80f3ad49ca5e9": { "describe": { "columns": [ @@ -4932,188 +4973,6 @@ }, "query": "\n SELECT tm.id id, tm.role member_role, tm.permissions permissions, tm.accepted accepted, tm.payouts_split payouts_split, tm.ordering ordering,\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 ORDER BY tm.ordering\n " }, - "b660307d4e4027223c6b7297237ee57e5d82c40423707c1fb0f3b8b45737a353": { - "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": "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": "Varchar" - }, - { - "name": "requested_status", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "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": "Varchar" - }, - { - "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" - }, - { - "name": "webhook_sent", - "ordinal": 27, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - true, - false, - true, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - true, - true, - false - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, published,\n updated, approved, status, requested_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, webhook_sent\n FROM mods\n WHERE id = ANY($1)\n " - }, "b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169": { "describe": { "columns": [], @@ -5395,6 +5254,204 @@ }, "query": "\n DELETE FROM game_versions_versions gvv\n WHERE gvv.joining_version_id = $1\n " }, + "bf3cc4f6b22db45df42fa8b1c5b26505ebdb312d14cf657891b9ec4dff8bc55c": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Int4", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Int4", + "Int4", + "Varchar", + "Varchar", + "Text", + "Int4", + "Int4" + ] + } + }, + "query": "\n INSERT INTO mods (\n id, team_id, title, description, body,\n published, downloads, icon_url, issues_url,\n source_url, wiki_url, status, requested_status, discord_url,\n client_side, server_side, license_url, license,\n slug, project_type, color\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8, $9,\n $10, $11, $12, $13, $14,\n $15, $16, $17, $18,\n LOWER($19), $20, $21\n )\n " + }, + "bf4afeda41a54e09a80a4cc505d1fbb72124c442ebaca731a291f022524daf1a": { + "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": "published", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 8, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "team_id", + "ordinal": 10, + "type_info": "Int8" + }, + { + "name": "license", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "color", + "ordinal": 14, + "type_info": "Int4" + }, + { + "name": "client_side_type", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "categories", + "ordinal": 19, + "type_info": "VarcharArray" + }, + { + "name": "additional_categories", + "ordinal": 20, + "type_info": "VarcharArray" + }, + { + "name": "loaders", + "ordinal": 21, + "type_info": "VarcharArray" + }, + { + "name": "versions", + "ordinal": 22, + "type_info": "VarcharArray" + }, + { + "name": "gallery", + "ordinal": 23, + "type_info": "VarcharArray" + }, + { + "name": "featured_gallery", + "ordinal": 24, + "type_info": "VarcharArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + false, + true, + false, + false, + false, + false, + null, + null, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "TextArray", + "TextArray", + "Text" + ] + } + }, + "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, m.status status_name, m.color color,\n cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_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 and mg.featured is false) gallery,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null and mg.featured is true) featured_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 AND v.status != ANY($1)\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 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 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 m.status = ANY($2)\n GROUP BY m.id, cs.id, ss.id, pt.id, u.id;\n " + }, "bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": { "describe": { "columns": [ @@ -5701,237 +5758,6 @@ }, "query": "\n UPDATE versions\n SET status = requested_status\n WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL\n " }, - "c9686c5c7dc5a9496fdf3c9b7b5a05c64abf77bb5d525a54c697c2c87a95fc57": { - "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": "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": "Varchar" - }, - { - "name": "requested_status", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "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": "Varchar" - }, - { - "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": "client_side_type", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "flame_anvil_project", - "ordinal": 28, - "type_info": "Int4" - }, - { - "name": "flame_anvil_user", - "ordinal": 29, - "type_info": "Int8" - }, - { - "name": "webhook_sent", - "ordinal": 30, - "type_info": "Bool" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "VarcharArray" - }, - { - "name": "additional_categories", - "ordinal": 32, - "type_info": "VarcharArray" - }, - { - "name": "versions", - "ordinal": 33, - "type_info": "Jsonb" - }, - { - "name": "gallery", - "ordinal": 34, - "type_info": "Jsonb" - }, - { - "name": "donations", - "ordinal": 35, - "type_info": "Jsonb" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - true, - false, - true, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - true, - true, - false, - null, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8Array", - "TextArray" - ] - } - }, - "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.published published,\n m.updated updated, m.approved approved, m.status status, m.requested_status requested_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 cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,\n JSONB_AGG(DISTINCT jsonb_build_object('image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering)) filter (where mg.image_url is not null) gallery,\n JSONB_AGG(DISTINCT jsonb_build_object('platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', 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 side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.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 AND v.status = ANY($2)\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, cs.id, ss.id, m.id;\n " - }, "c9d63ed46799db7c30a7e917d97a5d4b2b78b0234cce49e136fa57526b38c1ca": { "describe": { "columns": [ @@ -6507,6 +6333,243 @@ }, "query": "\n SELECT id, user_id, role, permissions, accepted, payouts_split, ordering\n FROM team_members\n WHERE (team_id = $1 AND user_id = $2)\n " }, + "df35aee011aa176f5511729afd68b493ac70b997168cb502b5fd7e4224da27bb": { + "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": "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": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "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": "Varchar" + }, + { + "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": "client_side_type", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 28, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 29, + "type_info": "Int8" + }, + { + "name": "webhook_sent", + "ordinal": 30, + "type_info": "Bool" + }, + { + "name": "color", + "ordinal": 31, + "type_info": "Int4" + }, + { + "name": "categories", + "ordinal": 32, + "type_info": "VarcharArray" + }, + { + "name": "additional_categories", + "ordinal": 33, + "type_info": "VarcharArray" + }, + { + "name": "versions", + "ordinal": 34, + "type_info": "Jsonb" + }, + { + "name": "gallery", + "ordinal": 35, + "type_info": "Jsonb" + }, + { + "name": "donations", + "ordinal": 36, + "type_info": "Jsonb" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + false, + true, + null, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8", + "TextArray" + ] + } + }, + "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.published published,\n m.updated updated, m.approved approved, m.status status, m.requested_status requested_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 cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent webhook_sent, m.color,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions,\n JSONB_AGG(DISTINCT jsonb_build_object('image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering)) filter (where mg.image_url is not null) gallery,\n JSONB_AGG(DISTINCT jsonb_build_object('platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', 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 side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.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 AND v.status = ANY($2)\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, cs.id, ss.id, m.id;\n " + }, "df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed": { "describe": { "columns": [], @@ -7128,37 +7191,6 @@ }, "query": "\n DELETE FROM dependencies WHERE mod_dependency_id = $1\n " }, - "edd50de303f84695508a1067683ce881283bba5342aff36b7d03dc9638a6f9f3": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Varchar", - "Varchar", - "Varchar", - "Timestamptz", - "Int4", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Int4", - "Int4", - "Varchar", - "Varchar", - "Text", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods (\n id, team_id, title, description, body,\n published, downloads, icon_url, issues_url,\n source_url, wiki_url, status, requested_status, discord_url,\n client_side, server_side, license_url, license,\n slug, project_type\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8, $9,\n $10, $11, $12, $13, $14,\n $15, $16, $17, $18,\n LOWER($19), $20\n )\n " - }, "ef3d43d3424824eed67370f10cc0672581a95a169bf404022cbe3cac0415d99c": { "describe": { "columns": [ diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index be147e55e..36c57ba2e 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -100,6 +100,7 @@ pub struct ProjectBuilder { pub slug: Option, pub donation_urls: Vec, pub gallery_items: Vec, + pub color: Option, } impl ProjectBuilder { @@ -137,6 +138,7 @@ impl ProjectBuilder { flame_anvil_project: None, flame_anvil_user: None, webhook_sent: false, + color: self.color, }; project_struct.insert(&mut *transaction).await?; @@ -213,6 +215,7 @@ pub struct Project { pub flame_anvil_project: Option, pub flame_anvil_user: Option, pub webhook_sent: bool, + pub color: Option, } impl Project { @@ -227,14 +230,14 @@ impl Project { published, downloads, icon_url, issues_url, source_url, wiki_url, status, requested_status, discord_url, client_side, server_side, license_url, license, - slug, project_type + slug, project_type, color ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, - LOWER($19), $20 + LOWER($19), $20, $21 ) ", self.id as ProjectId, @@ -256,7 +259,8 @@ impl Project { self.license_url.as_ref(), &self.license, self.slug.as_ref(), - self.project_type as ProjectTypeId + self.project_type as ProjectTypeId, + self.color.map(|x| x as i32) ) .execute(&mut *transaction) .await?; @@ -279,7 +283,7 @@ impl Project { issues_url, source_url, wiki_url, discord_url, license_url, team_id, client_side, server_side, license, slug, moderation_message, moderation_message_body, flame_anvil_project, - flame_anvil_user, webhook_sent + flame_anvil_user, webhook_sent, color FROM mods WHERE id = $1 ", @@ -321,6 +325,7 @@ impl Project { flame_anvil_project: row.flame_anvil_project, flame_anvil_user: row.flame_anvil_user.map(UserId), webhook_sent: row.webhook_sent, + color: row.color.map(|x| x as u32), })) } else { Ok(None) @@ -346,7 +351,7 @@ impl Project { issues_url, source_url, wiki_url, discord_url, license_url, team_id, client_side, server_side, license, slug, moderation_message, moderation_message_body, flame_anvil_project, - flame_anvil_user, webhook_sent + flame_anvil_user, webhook_sent, color FROM mods WHERE id = ANY($1) ", @@ -388,6 +393,7 @@ impl Project { flame_anvil_project: m.flame_anvil_project, flame_anvil_user: m.flame_anvil_user.map(UserId), webhook_sent: m.webhook_sent, + color: m.color.map(|x| x as u32), })) }) .try_collect::>() @@ -666,7 +672,7 @@ impl Project { m.updated updated, m.approved approved, m.status status, m.requested_status requested_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, - cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent webhook_sent, + cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent webhook_sent, m.color, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories, JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions, @@ -725,6 +731,7 @@ impl Project { flame_anvil_project: m.flame_anvil_project, flame_anvil_user: m.flame_anvil_user.map(UserId), webhook_sent: m.webhook_sent, + color: m.color.map(|x| x as u32), }, project_type: m.project_type_name, categories: m.categories.unwrap_or_default(), @@ -794,7 +801,7 @@ impl Project { m.updated updated, m.approved approved, m.status status, m.requested_status requested_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, - cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent, + cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, m.webhook_sent, m.color, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories, JSONB_AGG(DISTINCT jsonb_build_object('id', v.id, 'date_published', v.date_published)) filter (where v.id is not null) versions, @@ -856,6 +863,7 @@ impl Project { flame_anvil_project: m.flame_anvil_project, flame_anvil_user: m.flame_anvil_user.map(UserId), webhook_sent: m.webhook_sent, + color: m.color.map(|x| x as u32), }, project_type: m.project_type_name, categories: m.categories.unwrap_or_default(), diff --git a/src/models/projects.rs b/src/models/projects.rs index a37bd2468..7f4efbca2 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -97,6 +97,9 @@ pub struct Project { pub flame_anvil_project: Option, /// The user_id of the team member whose token pub flame_anvil_user: Option, + + /// The color of the project (picked from icon) + pub color: Option, } impl From for Project { @@ -181,6 +184,7 @@ impl From for Project { .collect(), flame_anvil_project: m.flame_anvil_project, flame_anvil_user: m.flame_anvil_user.map(|x| x.into()), + color: m.color, } } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f4d346c78..d9021358d 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -30,6 +30,7 @@ pub use self::index::index_get; pub use self::not_found::not_found; use crate::file_hosting::FileHostingError; use actix_web::web; +use image::ImageError; pub fn v2_config(cfg: &mut web::ServiceConfig) { cfg.service( @@ -230,6 +231,8 @@ pub enum ApiError { DiscordError(String), #[error("Error while decoding Base62: {0}")] Decoding(#[from] crate::models::ids::DecodingError), + #[error("Image Parsing Error: {0}")] + ImageError(#[from] ImageError), } impl actix_web::ResponseError for ApiError { @@ -280,6 +283,9 @@ impl actix_web::ResponseError for ApiError { actix_web::http::StatusCode::FAILED_DEPENDENCY } ApiError::Decoding(..) => actix_web::http::StatusCode::BAD_REQUEST, + ApiError::ImageError(..) => { + actix_web::http::StatusCode::BAD_REQUEST + } } } @@ -304,6 +310,7 @@ impl actix_web::ResponseError for ApiError { ApiError::Payments(..) => "payments_error", ApiError::DiscordError(..) => "discord_error", ApiError::Decoding(..) => "decoding_error", + ApiError::ImageError(..) => "invalid_image", }, description: &self.to_string(), }, diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index 4ac247ced..bc5abf7e4 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -18,6 +18,7 @@ use actix_web::web::Data; use actix_web::{post, HttpRequest, HttpResponse}; use chrono::Utc; use futures::stream::StreamExt; +use image::ImageError; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sqlx::postgres::PgPool; @@ -65,6 +66,8 @@ pub enum CreateError { Unauthorized(#[from] AuthenticationError), #[error("Authentication Error: {0}")] CustomAuthenticationError(String), + #[error("Image Parsing Error: {0}")] + ImageError(#[from] ImageError), } impl actix_web::ResponseError for CreateError { @@ -95,6 +98,7 @@ impl actix_web::ResponseError for CreateError { CreateError::SlugCollision => StatusCode::BAD_REQUEST, CreateError::ValidationError(..) => StatusCode::BAD_REQUEST, CreateError::FileValidationError(..) => StatusCode::BAD_REQUEST, + CreateError::ImageError(..) => StatusCode::BAD_REQUEST, } } @@ -120,6 +124,7 @@ impl actix_web::ResponseError for CreateError { CreateError::SlugCollision => "invalid_input", CreateError::ValidationError(..) => "invalid_input", CreateError::FileValidationError(..) => "invalid_input", + CreateError::ImageError(..) => "invalid_image", }, description: &self.to_string(), }) @@ -468,7 +473,7 @@ pub async fn project_create_inner( )) })?; - let mut icon_url = None; + let mut icon_data = None; while let Some(item) = payload.next().await { let mut field: Field = item.map_err(CreateError::MultipartError)?; @@ -482,13 +487,13 @@ pub async fn project_create_inner( super::version_creation::get_name_ext(&content_disposition)?; if name == "icon" { - if icon_url.is_some() { + if icon_data.is_some() { return Err(CreateError::InvalidInput(String::from( "Projects can only have one icon", ))); } // Upload the icon to the cdn - icon_url = Some( + icon_data = Some( process_icon_upload( uploaded_files, project_id, @@ -731,7 +736,7 @@ pub async fn project_create_inner( title: project_create_data.title, description: project_create_data.description, body: project_create_data.body, - icon_url, + icon_url: icon_data.clone().map(|x| x.0), issues_url: project_create_data.issues_url, source_url: project_create_data.source_url, wiki_url: project_create_data.wiki_url, @@ -759,6 +764,7 @@ pub async fn project_create_inner( ordering: x.ordering, }) .collect(), + color: icon_data.and_then(|x| x.1), }; let now = Utc::now(); @@ -803,6 +809,7 @@ pub async fn project_create_inner( gallery: gallery_urls, flame_anvil_project: None, flame_anvil_user: None, + color: project_builder.color, }; let _project_id = project_builder.insert(&mut *transaction).await?; @@ -911,9 +918,9 @@ async fn process_icon_upload( project_id: ProjectId, file_extension: &str, file_host: &dyn FileHost, - mut field: actix_multipart::Field, + mut field: Field, cdn_url: &str, -) -> Result { +) -> Result<(String, Option), CreateError> { if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) { @@ -924,6 +931,8 @@ async fn process_icon_upload( ) .await?; + let color = crate::util::img::get_color_from_img(&data)?; + let hash = sha1::Sha1::from(&data).hexdigest(); let upload_data = file_host .upload_file( @@ -938,7 +947,7 @@ async fn process_icon_upload( file_name: upload_data.file_name.clone(), }); - Ok(format!("{}/{}", cdn_url, upload_data.file_name)) + Ok((format!("{}/{}", cdn_url, upload_data.file_name), color)) } else { Err(CreateError::InvalidIconFormat(file_extension.to_string())) } diff --git a/src/routes/projects.rs b/src/routes/projects.rs index cd21bab20..ed1ad6ec9 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -1288,6 +1288,9 @@ pub async fn project_icon_edit( "Icons must be smaller than 256KiB", ) .await?; + + let color = crate::util::img::get_color_from_img(&bytes)?; + let hash = sha1::Sha1::from(&bytes).hexdigest(); let project_id: ProjectId = project_item.id.into(); let upload_data = file_host @@ -1303,10 +1306,11 @@ pub async fn project_icon_edit( sqlx::query!( " UPDATE mods - SET icon_url = $1 - WHERE (id = $2) + SET icon_url = $1, color = $2 + WHERE (id = $3) ", format!("{}/{}", cdn_url, upload_data.file_name), + color.map(|x| x as i32), project_item.id as database::models::ids::ProjectId, ) .execute(&mut *transaction) @@ -1379,7 +1383,7 @@ pub async fn delete_project_icon( sqlx::query!( " UPDATE mods - SET icon_url = NULL + SET icon_url = NULL, color = NULL WHERE (id = $1) ", project_item.id as database::models::ids::ProjectId, diff --git a/src/search/indexing/local_import.rs b/src/search/indexing/local_import.rs index 3b47db4ec..5a7e55d76 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -16,7 +16,7 @@ pub async fn index_local( " SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows, m.icon_url icon_url, m.published published, m.approved approved, m.updated updated, - m.team_id team_id, m.license license, m.slug slug, m.status status_name, + m.team_id team_id, m.license license, m.slug slug, m.status status_name, m.color color, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories, ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories, @@ -96,6 +96,7 @@ pub async fn index_local( gallery, display_categories, open_source, + color: m.color.map(|x| x as u32), } })) }) diff --git a/src/search/mod.rs b/src/search/mod.rs index b78d7d499..d3d173dd4 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -98,6 +98,7 @@ pub struct UploadSearchProject { /// Unix timestamp of the last major modification pub modified_timestamp: i64, pub open_source: bool, + pub color: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -132,6 +133,7 @@ pub struct ResultSearchProject { pub client_side: String, pub server_side: String, pub gallery: Vec, + pub color: Option, } impl Document for UploadSearchProject { diff --git a/src/util/ext.rs b/src/util/ext.rs index a51a233ba..fec68eef8 100644 --- a/src/util/ext.rs +++ b/src/util/ext.rs @@ -2,12 +2,9 @@ pub fn get_image_content_type(extension: &str) -> Option<&'static str> { match extension { "bmp" => Some("image/bmp"), "gif" => Some("image/gif"), - "jpeg" | "jpg" | "jpe" => Some("image/jpeg"), + "jpeg" | "jpg" => Some("image/jpeg"), "png" => Some("image/png"), - "svg" | "svgz" => Some("image/svg+xml"), "webp" => Some("image/webp"), - "rgb" => Some("image/x-rgb"), - "mp4" => Some("video/mp4"), _ => None, } } diff --git a/src/util/img.rs b/src/util/img.rs new file mode 100644 index 000000000..1dd12f38d --- /dev/null +++ b/src/util/img.rs @@ -0,0 +1,19 @@ +use color_thief::ColorFormat; +use image::imageops::FilterType; +use image::{EncodableLayout, ImageError}; + +pub fn get_color_from_img(data: &[u8]) -> Result, ImageError> { + let image = + image::load_from_memory(data)?.resize(256, 256, FilterType::Nearest); + let color = color_thief::get_palette( + image.to_rgb8().as_bytes(), + ColorFormat::Rgb, + 10, + 2, + ) + .ok() + .and_then(|x| x.get(0).copied()) + .map(|x| (x.r as u32) << 16 | (x.g as u32) << 8 | (x.b as u32)); + + Ok(color) +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 8d648cbf6..46456c617 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,7 @@ pub mod auth; pub mod env; pub mod ext; pub mod guards; +pub mod img; pub mod routes; pub mod validate; pub mod webhook; diff --git a/src/util/webhook.rs b/src/util/webhook.rs index e26fe75cf..de19418b4 100644 --- a/src/util/webhook.rs +++ b/src/util/webhook.rs @@ -157,7 +157,13 @@ pub async fn send_discord_webhook( _ => 1049805243866681424, }; - let mut x = loader.clone(); + let mut x = if loader == "datapack" { + "Data Pack" + } else { + loader + } + .to_string(); + formatted_loaders.push_str(&format!( "<:{loader}:{emoji_id}> {}{x}\n", x.remove(0).to_uppercase() @@ -190,6 +196,13 @@ pub async fn send_discord_webhook( project_type = "datapack".to_string(); } + let mut display_project_type = match &*project_type { + "datapack" => "data pack", + "resourcepack" => "resource pack", + _ => &*project_type, + } + .to_string(); + let embed = DiscordEmbed { author: Some(DiscordEmbedAuthor { name: project.username.clone(), @@ -224,8 +237,8 @@ pub async fn send_discord_webhook( .map(|x| DiscordEmbedImage { url: Some(x) }), footer: Some(DiscordEmbedFooter { text: format!( - "{}{project_type} on Modrinth", - project_type.remove(0).to_uppercase() + "{}{display_project_type} on Modrinth", + display_project_type.remove(0).to_uppercase() ), icon_url: Some( "https://cdn-raw.modrinth.com/modrinth-new.png".to_string(),