diff --git a/Cargo.lock b/Cargo.lock index 6efc9b9d4..9ee8335aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,6 +340,12 @@ dependencies = [ "syn", ] +[[package]] +name = "async_once" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" + [[package]] name = "atoi" version = "0.4.0" @@ -524,6 +530,42 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "cached" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aadf76ddea74bab35ebeb8f1eb115b9bc04eaee42d8acc0d5f477dee6b176c9a" +dependencies = [ + "async-trait", + "async_once", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.12.1", + "lazy_static", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce0f37f9b77c6b93cdf3f060c89adca303d2ab052cacb3c3d1ab543e8cecd2f" +dependencies = [ + "cached_proc_macro_types", + "darling", + "quote", + "syn", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" + [[package]] name = "castaway" version = "0.1.2" @@ -1410,6 +1452,7 @@ dependencies = [ "base64", "bitflags", "bytes", + "cached", "dashmap", "dotenv", "env_logger", @@ -2776,9 +2819,21 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", + "tokio-macros", "winapi", ] +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-native-tls" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 2891d4323..0e04082ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,3 +62,5 @@ sqlx = { version = "0.5.11", features = ["runtime-actix-rustls", "postgres", "ti bytes = "1.1.0" dashmap = "5.2.0" + +cached = "0.34.0" \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index ad45ae6cc..eee0d5276 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -746,26 +746,6 @@ ] } }, - "26a4ea4c0bbe4aea0d4db17bb7f80e6b6336ee7d4df503915cd6494ab72c05f2": { - "query": "\n SELECT l.loader loader\n FROM loaders_versions lv\n INNER JOIN loaders l on lv.loader_id = l.id\n WHERE lv.version_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "loader", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - } - }, "27a35fca63dfc3801f95958604f0ac27afd81800e2dc981382d6f923c4415d32": { "query": "\n DELETE FROM notifications_actions\n WHERE notification_id = ANY($1)\n ", "describe": { @@ -1181,38 +1161,6 @@ "nullable": [] } }, - "47161ce6a3ccb92e795786d05db9c12377f4bf03d3ffcd798866fea8c46322da": { - "query": "\n SELECT h.algorithm algorithm, encode(h.hash, 'escape') hash, h.file_id file_id\n FROM files f\n INNER JOIN hashes h ON h.file_id = f.id\n WHERE f.version_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "algorithm", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "hash", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "file_id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - null, - false - ] - } - }, "48294a4e0c594e80fff8d14a705aa7282f55e47cf3772e77f1d4bf4849008b60": { "query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n ", "describe": { @@ -1844,6 +1792,116 @@ "nullable": [] } }, + "5ed947341b512407530f2d6c0687d0cfc8daa2ad24fbd2e2a9ad6a2c1d226456": { + "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured,\n STRING_AGG(DISTINCT gv.version, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders,\n STRING_AGG(DISTINCT f.id || ' |||| ' || f.filename || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url, ' ~~~~ ') files,\n STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes,\n STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || COALESCE(d.dependency_file_name, ' ') || ' |||| ' || d.dependency_type, ' ~~~~ ') dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = $1\n GROUP BY v.id;\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "mod_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "author_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "version_name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "version_number", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "changelog", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "changelog_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "date_published", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 9, + "name": "version_type", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "featured", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "game_versions", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "loaders", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "files", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "hashes", + "type_info": "Text" + }, + { + "ordinal": 15, + "name": "dependencies", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + null, + null, + null, + null, + null + ] + } + }, "5ee2dc5cda9bfc0395da5a4ebf234093e9b8135db5e4a0258b00fa16fb825faa": { "query": "\n SELECT name FROM project_types\n ", "describe": { @@ -1964,79 +2022,113 @@ ] } }, - "6326543f7cbd0ce03bbfe234ee82ca1b61d411589dc2d61753598679942cfdd8": { - "query": "\n SELECT md.url url, dp.id platform_id, dp.name dp_name, dp.short short\n FROM mods_donations md\n INNER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n WHERE md.joining_mod_id = $1\n ", + "63bf0b49bbc5133d34f69348a16e51fe9a7dba338c232ca2d047e2ba221c3713": { + "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured,\n STRING_AGG(DISTINCT gv.version, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders,\n STRING_AGG(DISTINCT f.id || ' |||| ' || f.filename || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url, ' ~~~~ ') files,\n STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes,\n STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || COALESCE(d.dependency_file_name, ' ') || ' |||| ' || d.dependency_type, ' ~~~~ ') dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.date_published ASC;\n ", "describe": { "columns": [ { "ordinal": 0, - "name": "url", - "type_info": "Varchar" + "name": "id", + "type_info": "Int8" }, { "ordinal": 1, - "name": "platform_id", + "name": "mod_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "author_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "version_name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "version_number", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "changelog", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "changelog_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "date_published", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "downloads", "type_info": "Int4" }, { - "ordinal": 2, - "name": "dp_name", + "ordinal": 9, + "name": "version_type", "type_info": "Varchar" }, { - "ordinal": 3, - "name": "short", - "type_info": "Varchar" + "ordinal": 10, + "name": "featured", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "game_versions", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "loaders", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "files", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "hashes", + "type_info": "Text" + }, + { + "ordinal": 15, + "name": "dependencies", + "type_info": "Text" } ], "parameters": { "Left": [ - "Int8" + "Int8Array" ] }, "nullable": [ false, false, false, - false - ] - } - }, - "6347536d5bf9c2c9bd830fbdc03457df7eafcb2d58a4846589316e5bc72a2020": { - "query": "\n SELECT dependency_id, mod_dependency_id, dependency_file_name, dependency_type\n FROM dependencies\n WHERE dependent_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "dependency_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "mod_dependency_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "dependency_file_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "dependency_type", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ + false, + false, + false, true, - true, - true, - false + false, + false, + false, + false, + null, + null, + null, + null, + null ] } }, @@ -2956,26 +3048,6 @@ ] } }, - "94ca18bf5244b0add2e6a12edfdc8d67159eed8c5afdf690f9b702faed249a4c": { - "query": "\n SELECT gv.version game_version\n FROM game_versions_versions gvv\n INNER JOIN game_versions gv on gvv.game_version_id = gv.id\n WHERE gvv.joining_version_id = $1\n ORDER BY gv.created\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "game_version", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - } - }, "96587afb05c9d308d665e76dabab497b39cc7993b6fececed7dc9677d159abb3": { "query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE slug = $1)\n ", "describe": { @@ -3016,6 +3088,224 @@ ] } }, + "97c21dc36e62b0e94f63e76a0e2bd87357f192531c4eeb5678549d8607a4a0bc": { + "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.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 STRING_AGG(DISTINCT c.category, ' ~~~~ ') categories, STRING_AGG(DISTINCT v.id::text, ' ~~~~ ') versions,\n STRING_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ') || ' |||| ' || mg.created, ' ~~~~ ') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || md.url || ' |||| ' || dp.short || ' |||| ' || dp.name, ' ~~~~ ') 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 ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 12, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 18, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "moderation_message_body", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "status_name", + "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "client_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "server_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 27, + "name": "short", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "license_name", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "project_type_name", + "type_info": "Varchar" + }, + { + "ordinal": 30, + "name": "categories", + "type_info": "Text" + }, + { + "ordinal": 31, + "name": "versions", + "type_info": "Text" + }, + { + "ordinal": 32, + "name": "gallery", + "type_info": "Text" + }, + { + "ordinal": 33, + "name": "donations", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + null, + null, + null, + null + ] + } + }, "98653aee768ce2e5493178a89acfe377a9bab715d8b1e612883690d45b676123": { "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,\n 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 STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions,\n STRING_AGG(DISTINCT mg.image_url, ',') 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 = $2 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n ", "describe": { @@ -3189,50 +3479,6 @@ "nullable": [] } }, - "9f1f1039e8e360092e046b219fe6861368f5b4a338041d426ef689981f0cb9df": { - "query": "\n SELECT id, filename, is_primary, url, size\n FROM files\n WHERE version_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "filename", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "is_primary", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "url", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "size", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false - ] - } - }, "a39ce28b656032f862b205cffa393a76b989f4803654a615477a94fda5f57354": { "query": "\n DELETE FROM states\n WHERE id = $1\n ", "describe": { @@ -3269,50 +3515,6 @@ "nullable": [] } }, - "a6b3425fa78c6acac0693752a605d577cb04f507f9091fcc472af9f9ec41ebef": { - "query": "\n SELECT image_url, featured, title, description, created\n FROM mods_gallery\n WHERE mod_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "image_url", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "featured", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "created", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - true, - true, - true, - false - ] - } - }, "a7a5fd1dfa3da7f476525f0b404dd0d609483154e0e91c781319f5c596b87b9e": { "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, 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 ", "describe": { @@ -3658,6 +3860,224 @@ ] } }, + "aafbe87cb07227b382130f4f9b0ce2220972c14adf070c31010fd1623ceffdd3": { + "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.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 STRING_AGG(DISTINCT c.category, ' ~~~~ ') categories, STRING_AGG(DISTINCT v.id::text, ' ~~~~ ') versions,\n STRING_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ') || ' |||| ' || mg.created, ' ~~~~ ') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || md.url || ' |||| ' || dp.short || ' |||| ' || dp.name, ' ~~~~ ') 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 ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 12, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 18, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "moderation_message_body", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "status_name", + "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "client_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "server_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 27, + "name": "short", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "license_name", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "project_type_name", + "type_info": "Varchar" + }, + { + "ordinal": 30, + "name": "categories", + "type_info": "Text" + }, + { + "ordinal": 31, + "name": "versions", + "type_info": "Text" + }, + { + "ordinal": 32, + "name": "gallery", + "type_info": "Text" + }, + { + "ordinal": 33, + "name": "donations", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + null, + null, + null, + null + ] + } + }, "ac2d17b7d7147b14f072c15ffa214c14f32f27ffa6a3c2b2a5f80f3ad49ca5e9": { "query": "\n SELECT id FROM users\n WHERE LOWER(username) = LOWER($1)\n ", "describe": { @@ -3892,200 +4312,6 @@ "nullable": [] } }, - "bb93df1953b22d06f0ad8bb6da5a448684679250855ec0621b1ddd599438d669": { - "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.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 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 WHERE m.id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "project_type", - "type_info": "Int4" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "follows", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "icon_url", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "body_url", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "published", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "status", - "type_info": "Int4" - }, - { - "ordinal": 12, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "license_url", - "type_info": "Varchar" - }, - { - "ordinal": 17, - "name": "team_id", - "type_info": "Int8" - }, - { - "ordinal": 18, - "name": "client_side", - "type_info": "Int4" - }, - { - "ordinal": 19, - "name": "server_side", - "type_info": "Int4" - }, - { - "ordinal": 20, - "name": "license", - "type_info": "Int4" - }, - { - "ordinal": 21, - "name": "slug", - "type_info": "Varchar" - }, - { - "ordinal": 22, - "name": "moderation_message", - "type_info": "Varchar" - }, - { - "ordinal": 23, - "name": "moderation_message_body", - "type_info": "Varchar" - }, - { - "ordinal": 24, - "name": "status_name", - "type_info": "Varchar" - }, - { - "ordinal": 25, - "name": "client_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 26, - "name": "server_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 27, - "name": "short", - "type_info": "Varchar" - }, - { - "ordinal": 28, - "name": "license_name", - "type_info": "Varchar" - }, - { - "ordinal": 29, - "name": "project_type_name", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false - ] - } - }, "bbfb47ae2c972734785df6b7c3e62077dc544ef4ccf8bb89e9c22c2f50a933c1": { "query": "\n DELETE FROM report_types\n WHERE name = $1\n ", "describe": { @@ -5155,26 +5381,6 @@ "nullable": [] } }, - "dd616640be8807405d0d98a55f8fa23a50186a6a87a64bcb872750c9f9404763": { - "query": "\n SELECT c.category category\n FROM mods_categories mc\n INNER JOIN categories c ON mc.joining_category_id = c.id\n WHERE mc.joining_mod_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "category", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - } - }, "e29da865af4a0a110275b9756394546a3bb88bff40e18c66029651f515caed98": { "query": "\n SELECT f.id id FROM files f\n WHERE f.version_id = $1\n ", "describe": { @@ -5258,86 +5464,6 @@ "nullable": [] } }, - "e64c33dce1f23d32497a69ecfdac8d4ced262335b82d32d8353456322a0a7484": { - "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured\n FROM versions v\n WHERE v.id = $1\n GROUP BY v.id;\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "mod_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "author_id", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "version_name", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "version_number", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "changelog", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "changelog_url", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "date_published", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 9, - "name": "version_type", - "type_info": "Varchar" - }, - { - "ordinal": 10, - "name": "featured", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - false - ] - } - }, "e673006d1355fa91ba5739d7cf569eec5e1ec501f7b1dc2b431f0b1c25ac07d5": { "query": "\n DELETE FROM game_versions\n WHERE version = $1\n ", "describe": { @@ -5902,25 +6028,5 @@ null ] } - }, - "fd00809bd75662a8f21d812fd00071b4208f88186d9f86badece97c9c95ad3b9": { - "query": "\n SELECT id\n FROM versions\n WHERE mod_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - } } } \ No newline at end of file diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index 7e3ef5d96..f16dc4f5e 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -601,69 +601,43 @@ impl Project { executor: E, ) -> Result, sqlx::error::Error> where - E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, + E: sqlx::Executor<'a, Database = sqlx::Postgres>, { - let (project, versions, categories, gallery, donations) = futures::join!( - sqlx::query!( - " - 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.body body, m.body_url body_url, m.published published, - m.updated updated, 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 - FROM mods m - INNER JOIN project_types pt ON pt.id = m.project_type - INNER JOIN statuses s ON s.id = m.status - INNER JOIN side_types cs ON m.client_side = cs.id - INNER JOIN side_types ss ON m.server_side = ss.id - INNER JOIN licenses l ON m.license = l.id - WHERE m.id = $1 - ", - id as ProjectId, - ).fetch_optional(executor), - sqlx::query!( - " - SELECT id - FROM versions - WHERE mod_id = $1 - ", - id as ProjectId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT c.category category - FROM mods_categories mc - INNER JOIN categories c ON mc.joining_category_id = c.id - WHERE mc.joining_mod_id = $1 - ", - id as ProjectId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT image_url, featured, title, description, created - FROM mods_gallery - WHERE mod_id = $1 - ", - id as ProjectId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT md.url url, dp.id platform_id, dp.name dp_name, dp.short short - FROM mods_donations md - INNER JOIN donation_platforms dp ON md.joining_platform_id = dp.id - WHERE md.joining_mod_id = $1 - ", - id as ProjectId, - ).fetch_all(executor) - ); - - if let Some(m) = project? { - let project_id = ProjectId(m.id); + let result = sqlx::query!( + " + 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.body body, m.body_url body_url, m.published published, + m.updated updated, 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, + STRING_AGG(DISTINCT c.category, ' ~~~~ ') categories, STRING_AGG(DISTINCT v.id::text, ' ~~~~ ') versions, + STRING_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ') || ' |||| ' || mg.created, ' ~~~~ ') gallery, + STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || md.url || ' |||| ' || dp.short || ' |||| ' || dp.name, ' ~~~~ ') donations + FROM mods m + INNER JOIN project_types pt ON pt.id = m.project_type + INNER JOIN statuses s ON s.id = m.status + INNER JOIN side_types cs ON m.client_side = cs.id + INNER JOIN side_types ss ON m.server_side = ss.id + INNER JOIN licenses l ON m.license = l.id + LEFT JOIN mods_donations md ON md.joining_mod_id = m.id + LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id + LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id + LEFT JOIN categories c ON mc.joining_category_id = c.id + LEFT JOIN versions v ON v.mod_id = m.id + LEFT JOIN mods_gallery mg ON mg.mod_id = m.id + WHERE m.id = $1 + GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id; + ", + id as ProjectId, + ) + .fetch_optional(executor) + .await?; + if let Some(m) = result { Ok(Some(QueryProject { inner: Project { - id: project_id, + id: ProjectId(m.id), project_type: ProjectTypeId(m.project_type), team_id: TeamId(m.team_id), title: m.title.clone(), @@ -689,46 +663,74 @@ impl Project { moderation_message_body: m.moderation_message_body, }, project_type: m.project_type_name, - categories: categories? - .into_iter() - .map(|x| x.category) - .collect(), - versions: versions? - .into_iter() - .map(|x| VersionId(x.id)) - .collect(), - donation_urls: donations? - .into_iter() - .map(|x| DonationUrl { - project_id, - platform_id: DonationPlatformId(x.platform_id), - platform_short: x.short, - platform_name: x.dp_name, - url: x.url, + categories: m + .categories + .map(|x| x.split(" ~~~~ ").map(|x| x.to_string()).collect()) + .unwrap_or_default(), + versions: m + .versions + .map(|x| { + x.split(" ~~~~ ") + .map(|x| VersionId(x.parse().unwrap_or_default())) + .collect() }) - .collect(), - gallery_items: gallery? - .into_iter() - .map(|x| GalleryItem { - project_id, - image_url: x.image_url, - featured: x.featured.unwrap_or(false), - title: x.title, - description: x.description, - created: x.created, + .unwrap_or_default(), + donation_urls: m + .donations + .unwrap_or_default() + .split(" ~~~~ ") + .map(|d| { + let strings: Vec<&str> = d.split(" |||| ").collect(); + + if strings.len() >= 3 { + Some(DonationUrl { + project_id: id, + platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)), + platform_short: strings[2].to_string(), + platform_name: strings[3].to_string(), + url: strings[1].to_string(), + }) + } else { + None + } }) + .flatten() .collect(), - status: crate::models::projects::ProjectStatus::from_str( - &m.status_name, - ), + gallery_items: m + .gallery + .unwrap_or_default() + .split(" ~~~~ ") + .map(|d| { + let strings: Vec<&str> = d.split(" |||| ").collect(); + + if strings.len() >= 5 { + Some(GalleryItem { + project_id: id, + image_url: strings[0].to_string(), + featured: strings[1].parse().unwrap_or(false), + title: if strings[2] == " " { + None + } else { + Some(strings[2].to_string()) + }, + description: if strings[3] == " " { + None + } else { + Some(strings[3].to_string()) + }, + created: OffsetDateTime::parse(strings[4], time::Format::Rfc3339).unwrap_or_else(|_| OffsetDateTime::now_utc()) + }) + } else { + None + } + }) + .flatten() + .collect(), + status: crate::models::projects::ProjectStatus::from_str(&m.status_name), license_id: m.short, license_name: m.license_name, - client_side: crate::models::projects::SideType::from_str( - &m.client_side_type, - ), - server_side: crate::models::projects::SideType::from_str( - &m.server_side_type, - ), + client_side: crate::models::projects::SideType::from_str(&m.client_side_type), + server_side: crate::models::projects::SideType::from_str(&m.server_side_type), })) } else { Ok(None) @@ -740,13 +742,125 @@ impl Project { exec: E, ) -> Result, sqlx::Error> where - E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, + E: sqlx::Executor<'a, Database = sqlx::Postgres>, { - futures::future::try_join_all( - project_ids.into_iter().map(|id| Self::get_full(id, exec)), + use futures::TryStreamExt; + + let project_ids_parsed: Vec = project_ids.into_iter().map(|x| x.0).collect(); + sqlx::query!( + " + 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.body body, m.body_url body_url, m.published published, + m.updated updated, 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, + STRING_AGG(DISTINCT c.category, ' ~~~~ ') categories, STRING_AGG(DISTINCT v.id::text, ' ~~~~ ') versions, + STRING_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ') || ' |||| ' || mg.created, ' ~~~~ ') gallery, + STRING_AGG(DISTINCT md.joining_platform_id || ' |||| ' || md.url || ' |||| ' || dp.short || ' |||| ' || dp.name, ' ~~~~ ') donations + FROM mods m + INNER JOIN project_types pt ON pt.id = m.project_type + INNER JOIN statuses s ON s.id = m.status + INNER JOIN side_types cs ON m.client_side = cs.id + INNER JOIN side_types ss ON m.server_side = ss.id + INNER JOIN licenses l ON m.license = l.id + LEFT JOIN mods_donations md ON md.joining_mod_id = m.id + LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id + LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id + LEFT JOIN categories c ON mc.joining_category_id = c.id + LEFT JOIN versions v ON v.mod_id = m.id + LEFT JOIN mods_gallery mg ON mg.mod_id = m.id + WHERE m.id = ANY($1) + GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id; + ", + &project_ids_parsed ) - .await - .map(|x| x.into_iter().flatten().collect()) + .fetch_many(exec) + .try_filter_map(|e| async { + Ok(e.right().map(|m| { + let id = m.id; + QueryProject { + inner: Project { + id: ProjectId(id), + project_type: ProjectTypeId(m.project_type), + team_id: TeamId(m.team_id), + title: m.title.clone(), + description: m.description.clone(), + downloads: m.downloads, + body_url: m.body_url.clone(), + icon_url: m.icon_url.clone(), + published: m.published, + updated: m.updated, + issues_url: m.issues_url.clone(), + source_url: m.source_url.clone(), + wiki_url: m.wiki_url.clone(), + license_url: m.license_url.clone(), + discord_url: m.discord_url.clone(), + client_side: SideTypeId(m.client_side), + status: StatusId(m.status), + server_side: SideTypeId(m.server_side), + license: LicenseId(m.license), + slug: m.slug.clone(), + body: m.body.clone(), + follows: m.follows, + moderation_message: m.moderation_message, + moderation_message_body: m.moderation_message_body, + }, + project_type: m.project_type_name, + categories: m.categories.map(|x| x.split(" ~~~~ ").map(|x| x.to_string()).collect()).unwrap_or_default(), + versions: m.versions.map(|x| x.split(" ~~~~ ").map(|x| VersionId(x.parse().unwrap_or_default())).collect()).unwrap_or_default(), + gallery_items: m + .gallery + .unwrap_or_default() + .split(" ~~~~ ") + .map(|d| { + let strings: Vec<&str> = d.split(" |||| ").collect(); + + if strings.len() >= 5 { + Some(GalleryItem { + project_id: ProjectId(id), + image_url: strings[0].to_string(), + featured: strings[1].parse().unwrap_or(false), + title: if strings[2] == " " { None } else { Some(strings[2].to_string()) }, + description: if strings[3] == " " { None } else { Some(strings[3].to_string()) }, + created: OffsetDateTime::parse(strings[4], time::Format::Rfc3339).unwrap_or_else(|_| OffsetDateTime::now_utc()) + }) + } else { + None + } + }) + .flatten() + .collect(), + donation_urls: m + .donations + .unwrap_or_default() + .split(" ~~~~ ") + .map(|d| { + let strings: Vec<&str> = d.split(" |||| ").collect(); + + if strings.len() >= 3 { + Some(DonationUrl { + project_id: ProjectId(id), + platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)), + platform_short: strings[2].to_string(), + platform_name: strings[3].to_string(), + url: strings[1].to_string(), + }) + } else { + None + } + }) + .flatten() + .collect(), + status: crate::models::projects::ProjectStatus::from_str(&m.status_name), + license_id: m.short, + license_name: m.license_name, + client_side: crate::models::projects::SideType::from_str(&m.client_side_type), + server_side: crate::models::projects::SideType::from_str(&m.server_side_type), + }})) + }) + .try_collect::>() + .await } } #[derive(Clone, Debug)] diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index d9356ae5a..340fdbde8 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -608,80 +608,53 @@ impl Version { executor: E, ) -> Result, sqlx::error::Error> where - E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, + E: sqlx::Executor<'a, Database = sqlx::Postgres>, { - let (version, game_versions, loaders, files, hashes, dependencies) = futures::join!( - sqlx::query!( - " - SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number, - v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads, - v.version_type version_type, v.featured featured - FROM versions v - WHERE v.id = $1 - GROUP BY v.id; - ", - id as VersionId, - ) - .fetch_optional(executor), - sqlx::query!( - " - SELECT gv.version game_version - FROM game_versions_versions gvv - INNER JOIN game_versions gv on gvv.game_version_id = gv.id - WHERE gvv.joining_version_id = $1 - ORDER BY gv.created - ", - id as VersionId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT l.loader loader - FROM loaders_versions lv - INNER JOIN loaders l on lv.loader_id = l.id - WHERE lv.version_id = $1 - ", - id as VersionId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT id, filename, is_primary, url, size - FROM files - WHERE version_id = $1 - ", - id as VersionId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT h.algorithm algorithm, encode(h.hash, 'escape') hash, h.file_id file_id - FROM files f - INNER JOIN hashes h ON h.file_id = f.id - WHERE f.version_id = $1 - ", - id as VersionId, - ).fetch_all(executor), - sqlx::query!( - " - SELECT dependency_id, mod_dependency_id, dependency_file_name, dependency_type - FROM dependencies - WHERE dependent_id = $1 - ", - id as VersionId, - ).fetch_all(executor), - ); + let result = sqlx::query!( + " + SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number, + v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads, + v.version_type version_type, v.featured featured, + STRING_AGG(DISTINCT gv.version, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders, + STRING_AGG(DISTINCT f.id || ' |||| ' || f.filename || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url, ' ~~~~ ') files, + STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes, + STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || COALESCE(d.dependency_file_name, ' ') || ' |||| ' || d.dependency_type, ' ~~~~ ') dependencies + FROM versions v + LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id + LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id + LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id + LEFT OUTER JOIN loaders l on lv.loader_id = l.id + LEFT OUTER JOIN files f on v.id = f.version_id + LEFT OUTER JOIN hashes h on f.id = h.file_id + LEFT OUTER JOIN dependencies d on v.id = d.dependent_id + WHERE v.id = $1 + GROUP BY v.id; + ", + id as VersionId, + ) + .fetch_optional(executor) + .await?; - if let Some(v) = version? { - let mut hashes_map: HashMap>> = - HashMap::new(); + if let Some(v) = result { + let hashes: Vec<(FileId, String, Vec)> = v + .hashes + .unwrap_or_default() + .split(" ~~~~ ") + .map(|f| { + let hash: Vec<&str> = f.split(" |||| ").collect(); - for hash in hashes? { - let entry = hashes_map - .entry(FileId(hash.file_id)) - .or_insert_with(HashMap::new); - - if let Some(raw_hash) = hash.hash { - entry.insert(hash.algorithm, raw_hash.into_bytes()); - } - } + if hash.len() >= 3 { + Some(( + FileId(hash[2].parse().unwrap_or(0)), + hash[0].to_string(), + hash[1].to_string().into_bytes(), + )) + } else { + None + } + }) + .flatten() + .collect(); Ok(Some(QueryVersion { id: VersionId(v.id), @@ -693,34 +666,81 @@ impl Version { changelog_url: v.changelog_url, date_published: v.date_published, downloads: v.downloads, - files: files? - .into_iter() - .map(|x| QueryFile { - id: FileId(x.id), - url: x.url, - filename: x.filename, - hashes: hashes_map - .entry(FileId(x.id)) - .or_default() - .clone(), - primary: x.is_primary, - size: x.size as u32, + files: v + .files + .unwrap_or_default() + .split(" ~~~~ ") + .map(|f| { + let file: Vec<&str> = f.split(" |||| ").collect(); + + if file.len() >= 5 { + let file_id = FileId(file[0].parse().unwrap_or(0)); + let mut file_hashes = HashMap::new(); + + for hash in &hashes { + if (hash.0).0 == file_id.0 { + file_hashes.insert(hash.1.clone(), hash.2.clone()); + } + } + + Some(QueryFile { + id: file_id, + url: file[4].to_string(), + filename: file[1].to_string(), + hashes: file_hashes, + primary: file[2].parse().unwrap_or(false), + size: file[3].parse().unwrap_or(0) + }) + } else { + None + } }) + .flatten() .collect(), - game_versions: game_versions? - .into_iter() - .map(|x| x.game_version) + game_versions: v + .game_versions + .unwrap_or_default() + .split(" ~~~~ ") + .map(|x| x.to_string()) + .collect(), + loaders: v + .loaders + .unwrap_or_default() + .split(" ~~~~ ") + .map(|x| x.to_string()) .collect(), - loaders: loaders?.into_iter().map(|x| x.loader).collect(), featured: v.featured, - dependencies: dependencies? - .into_iter() - .map(|x| QueryDependency { - project_id: x.mod_dependency_id.map(ProjectId), - version_id: x.dependency_id.map(VersionId), - file_name: x.dependency_file_name, - dependency_type: x.dependency_type, + dependencies: v + .dependencies + .unwrap_or_default() + .split(" ~~~~ ") + .map(|f| { + let dependency: Vec<&str> = f.split(" |||| ").collect(); + + if dependency.len() >= 4 { + Some(QueryDependency { + project_id: match &*dependency[1] { + "0" => None, + _ => match dependency[1].parse() { + Ok(x) => Some(ProjectId(x)), + Err(_) => None, + }, + }, + version_id: match &*dependency[0] { + "0" => None, + _ => match dependency[0].parse() { + Ok(x) => Some(VersionId(x)), + Err(_) => None, + }, + }, + file_name: if dependency[2] == " " { None } else { Some(dependency[4].to_string())}, + dependency_type: dependency[3].to_string(), + }) + } else { + None + } }) + .flatten() .collect(), version_type: v.version_type, })) @@ -736,11 +756,122 @@ impl Version { where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - futures::future::try_join_all( - version_ids.into_iter().map(|id| Self::get_full(id, exec)), + use futures::stream::TryStreamExt; + + let version_ids_parsed: Vec = version_ids.into_iter().map(|x| x.0).collect(); + sqlx::query!( + " + SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number, + v.changelog changelog, v.changelog_url changelog_url, v.date_published date_published, v.downloads downloads, + v.version_type version_type, v.featured featured, + STRING_AGG(DISTINCT gv.version, ' ~~~~ ') game_versions, STRING_AGG(DISTINCT l.loader, ' ~~~~ ') loaders, + STRING_AGG(DISTINCT f.id || ' |||| ' || f.filename || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url, ' ~~~~ ') files, + STRING_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id, ' ~~~~ ') hashes, + STRING_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || COALESCE(d.dependency_file_name, ' ') || ' |||| ' || d.dependency_type, ' ~~~~ ') dependencies + FROM versions v + LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id + LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id + LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id + LEFT OUTER JOIN loaders l on lv.loader_id = l.id + LEFT OUTER JOIN files f on v.id = f.version_id + LEFT OUTER JOIN hashes h on f.id = h.file_id + LEFT OUTER JOIN dependencies d on v.id = d.dependent_id + WHERE v.id = ANY($1) + GROUP BY v.id + ORDER BY v.date_published ASC; + ", + &version_ids_parsed ) - .await - .map(|x| x.into_iter().flatten().collect()) + .fetch_many(exec) + .try_filter_map(|e| async { + Ok(e.right().map(|v| { + let hashes: Vec<(FileId, String, Vec)> = v.hashes.unwrap_or_default().split(" ~~~~ ").map(|f| { + let hash: Vec<&str> = f.split(" |||| ").collect(); + + if hash.len() >= 3 { + Some(( + FileId(hash[2].parse().unwrap_or(0)), + hash[0].to_string(), + hash[1].to_string().into_bytes(), + )) + } else { + None + } + }).flatten().collect(); + + QueryVersion { + id: VersionId(v.id), + project_id: ProjectId(v.mod_id), + author_id: UserId(v.author_id), + name: v.version_name, + version_number: v.version_number, + changelog: v.changelog, + changelog_url: v.changelog_url, + date_published: v.date_published, + downloads: v.downloads, + files: v.files.unwrap_or_default().split(" ~~~~ ").map(|f| { + let file: Vec<&str> = f.split(" |||| ").collect(); + + if file.len() >= 5 { + let file_id = FileId(file[0].parse().unwrap_or(0)); + let mut file_hashes = HashMap::new(); + + for hash in &hashes { + if (hash.0).0 == file_id.0 { + file_hashes.insert(hash.1.clone(), hash.2.clone()); + } + } + + Some(QueryFile { + id: file_id, + url: file[4].to_string(), + filename: file[1].to_string(), + hashes: file_hashes, + primary: file[2].parse().unwrap_or(false), + size: file[3].parse().unwrap_or(0) + }) + } else { + None + } + }).flatten().collect(), + game_versions: v.game_versions.unwrap_or_default().split(" ~~~~ ").map(|x| x.to_string()).collect(), + loaders: v.loaders.unwrap_or_default().split(" ~~~~ ").map(|x| x.to_string()).collect(), + featured: v.featured, + dependencies: v.dependencies + .unwrap_or_default() + .split(" ~~~~ ") + .map(|f| { + let dependency: Vec<&str> = f.split(" |||| ").collect(); + + if dependency.len() >= 4 { + Some(QueryDependency { + project_id: match &*dependency[1] { + "0" => None, + _ => match dependency[1].parse() { + Ok(x) => Some(ProjectId(x)), + Err(_) => None, + }, + }, + version_id: match &*dependency[0] { + "0" => None, + _ => match dependency[0].parse() { + Ok(x) => Some(VersionId(x)), + Err(_) => None, + }, + }, + file_name: if dependency[2] == " " { None } else { Some(dependency[4].to_string())}, + dependency_type: dependency[3].to_string(), + }) + } else { + None + } + }).flatten().collect(), + version_type: v.version_type + } + })) + }) + .try_collect::>() + .await } } diff --git a/src/routes/projects.rs b/src/routes/projects.rs index 37609361c..9c80f8b4f 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -39,7 +39,7 @@ pub async fn projects_get( pool: web::Data, ) -> Result { let project_ids = - serde_json::from_str::>(&*ids.ids)? + serde_json::from_str::>(&*ids.ids)? .into_iter() .map(|x| x.into()) .collect();