From 06bb6f7bfff96e8e30e7f66c36c245174c95331e Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Fri, 29 Jan 2021 20:35:29 -0700 Subject: [PATCH] Dependencies, fix panic on version get, Filtered Versions route (#153) --- .../20210129224854_dependency-types.sql | 3 + sqlx-data.json | 524 +++++++++--------- src/database/models/version_item.rs | 176 ++++-- src/models/mods.rs | 50 +- src/routes/mod_creation.rs | 2 +- src/routes/version_creation.rs | 16 +- src/routes/versions.rs | 58 +- 7 files changed, 492 insertions(+), 337 deletions(-) create mode 100644 migrations/20210129224854_dependency-types.sql diff --git a/migrations/20210129224854_dependency-types.sql b/migrations/20210129224854_dependency-types.sql new file mode 100644 index 000000000..5fd065aa7 --- /dev/null +++ b/migrations/20210129224854_dependency-types.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE dependencies + ADD COLUMN dependency_type varchar(255) NOT NULL DEFAULT 'required'; \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 3bcebd85c..81222d988 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -332,19 +332,6 @@ "nullable": [] } }, - "16871e66d8762452be3ca0c80f4733f2db49980205fbf7cb6f9829cdd99cdb65": { - "query": "\n INSERT INTO dependencies (dependent_id, dependency_id)\n VALUES ($1, $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [] - } - }, "16b3ac53ef5e94f51ab39484add21e2f76d49015917dc877560607a31f5537e9": { "query": "\n UPDATE users\n SET email = $1\n WHERE (id = $2)\n ", "describe": { @@ -687,110 +674,6 @@ ] } }, - "219369194dd52ff431f84c9d92c8aa2be4ce0927a1990f6b5fd84152327b26dd": { - "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 rc.channel release_channel, 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.url, ' ,') files,\n STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes\n FROM versions v\n INNER JOIN release_channels rc on v.release_channel = rc.id\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 WHERE v.id = $1\n GROUP BY v.id, rc.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": "release_channel", - "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" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - false, - null, - null, - null, - null - ] - } - }, "2439ae8db5ae81aad03351e9a2e19259ef033110ed1c2d71bc0a643104ca32d0": { "query": "\n UPDATE versions\n SET downloads = downloads + 1\n WHERE id = $1\n ", "describe": { @@ -803,110 +686,6 @@ "nullable": [] } }, - "244ee3f0f9b0229a9e97ff9f7a2808b4d6ee6d24be008606db1198a54a33c468": { - "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 rc.channel release_channel, 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.url, ' ,') files,\n STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes\n FROM versions v\n INNER JOIN release_channels rc on v.release_channel = rc.id\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 WHERE v.id IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY v.id, rc.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": "release_channel", - "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" - } - ], - "parameters": { - "Left": [ - "Int8Array" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - false, - null, - null, - null, - null - ] - } - }, "24e5daad907eec54505274f93952d5c20f4bbdd3f771eb0a2fdfa6324768df39": { "query": "\n SELECT short, name FROM licenses\n WHERE id = $1\n ", "describe": { @@ -1268,6 +1047,20 @@ ] } }, + "381974f80a890a59f89c46b0c709e4511c0216eb8059ee47bb1e1456caf68fd7": { + "query": "\n INSERT INTO dependencies (dependent_id, dependency_id, dependency_type)\n VALUES ($1, $2, $3)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Varchar" + ] + }, + "nullable": [] + } + }, "3831c1b321e47690f1f54597506a0d43362eda9540c56acb19c06532bba50b01": { "query": "\n SELECT id, user_id, role, permissions, accepted\n FROM team_members\n WHERE team_id = $1\n ", "describe": { @@ -1627,6 +1420,116 @@ "nullable": [] } }, + "529f4c62eb8e98909ea7ec504f86647f105b1717990dd0c422919234060dc30f": { + "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 rc.channel release_channel, 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.url, ' ,') files,\n STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes,\n STRING_AGG(DISTINCT d.dependency_id || ', ' || d.dependency_type, ' ,') dependencies\n FROM versions v\n INNER JOIN release_channels rc on v.release_channel = rc.id\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 IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY v.id, rc.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": "release_channel", + "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": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + null, + null, + null, + null, + null + ] + } + }, "5322e7791400b47640f897f3e32d9a0f2915da7d71a34c4e6bf6bd648cfaaa6e": { "query": "\n DELETE FROM files\n WHERE files.id = $1\n ", "describe": { @@ -1786,6 +1689,28 @@ "nullable": [] } }, + "5ff8fd471ff62f86aa95e52cee2723b31ec3d7fc53c3ef1454df40eef0ceff53": { + "query": "\n SELECT version.id FROM (\n SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v\n INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n INNER JOIN game_versions gv on gvv.game_version_id = gv.id AND (cardinality($2::varchar[]) = 0 OR gv.version = ANY($2::varchar[]))\n INNER JOIN loaders_versions lv ON lv.version_id = v.id\n INNER JOIN loaders l on lv.loader_id = l.id AND (cardinality($3::varchar[]) = 0 OR l.loader = ANY($3::varchar[]))\n WHERE v.mod_id = $1\n ) AS version\n ORDER BY version.date_published ASC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8", + "VarcharArray", + "VarcharArray" + ] + }, + "nullable": [ + false + ] + } + }, "6131d32a65f5e04775308386812f25c6d8464582678536a392a4a3737667f363": { "query": "\n SELECT id, short, name FROM licenses\n ", "describe": { @@ -2206,26 +2131,6 @@ ] } }, - "78c8b561e37e3aed48d3a4108ce7fd81866c6835ea91517ffc90c30e1284246e": { - "query": "\n SELECT id FROM versions\n WHERE mod_id = $1\n ORDER BY date_published ASC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false - ] - } - }, "796f057ea8eb5b01d3eedeee9840fb37464ea567f32871953fb07e14ed86af1c": { "query": "SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2)", "describe": { @@ -3573,19 +3478,6 @@ ] } }, - "c82eb1b059b62444ab1d17e5a0bd7ef8acea4b05c6f3576c07d20c4ca7635a11": { - "query": "\n INSERT INTO dependencies (dependent_id, dependency_id)\n VALUES ($1, $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - }, - "nullable": [] - } - }, "c9d63ed46799db7c30a7e917d97a5d4b2b78b0234cce49e136fa57526b38c1ca": { "query": "\n SELECT EXISTS(SELECT 1 FROM versions WHERE id = $1)\n ", "describe": { @@ -3606,6 +3498,116 @@ ] } }, + "ca5e6d56640069ea9b1c32881d59be8eb1c2ee06ececf223ad378d0422a6f198": { + "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 rc.channel release_channel, 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.url, ' ,') files,\n STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes,\n STRING_AGG(DISTINCT d.dependency_id || ', ' || d.dependency_type, ' ,') dependencies\n FROM versions v\n INNER JOIN release_channels rc on v.release_channel = rc.id\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, rc.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": "release_channel", + "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 + ] + } + }, "cb57ae673f1a7e50cc319efddb9bdc82e2251596bcf85aea52e8def343e423b8": { "query": "\n INSERT INTO hashes (file_id, algorithm, hash)\n VALUES ($1, $2, $3)\n ", "describe": { @@ -4232,6 +4234,20 @@ ] } }, + "fbd55f89e9bd8c67605f67944cd585abf8a475b83f0b926d7dbcb26478df4da0": { + "query": "\n INSERT INTO dependencies (dependent_id, dependency_id, dependency_type)\n VALUES ($1, $2, $3)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Varchar" + ] + }, + "nullable": [] + } + }, "fc12e683844642245dae7ffad7aff29f2b65c7441be7f22e319da468e7f3d323": { "query": "\n SELECT v.mod_id, v.author_id, v.name, v.version_number,\n v.changelog, v.changelog_url, v.date_published, v.downloads,\n v.release_channel, v.featured\n FROM versions v\n WHERE v.id = $1\n ", "describe": { diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index bb3ae2d3b..fa32e9baf 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -10,7 +10,7 @@ pub struct VersionBuilder { pub version_number: String, pub changelog: String, pub files: Vec, - pub dependencies: Vec, + pub dependencies: Vec<(VersionId, String)>, pub game_versions: Vec, pub loaders: Vec, pub release_channel: ChannelId, @@ -107,11 +107,12 @@ impl VersionBuilder { for dependency in self.dependencies { sqlx::query!( " - INSERT INTO dependencies (dependent_id, dependency_id) - VALUES ($1, $2) + INSERT INTO dependencies (dependent_id, dependency_id, dependency_type) + VALUES ($1, $2, $3) ", self.version_id as VersionId, - dependency as VersionId, + dependency.0 as VersionId, + dependency.1, ) .execute(&mut *transaction) .await?; @@ -350,6 +351,8 @@ impl Version { pub async fn get_mod_versions<'a, E>( mod_id: ModId, + game_versions: Option>, + loaders: Option>, exec: E, ) -> Result, sqlx::Error> where @@ -359,11 +362,19 @@ impl Version { let vec = sqlx::query!( " - SELECT id FROM versions - WHERE mod_id = $1 - ORDER BY date_published ASC + SELECT version.id FROM ( + SELECT DISTINCT ON(v.id) v.id, v.date_published FROM versions v + INNER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id + INNER JOIN game_versions gv on gvv.game_version_id = gv.id AND (cardinality($2::varchar[]) = 0 OR gv.version = ANY($2::varchar[])) + INNER JOIN loaders_versions lv ON lv.version_id = v.id + INNER JOIN loaders l on lv.loader_id = l.id AND (cardinality($3::varchar[]) = 0 OR l.loader = ANY($3::varchar[])) + WHERE v.mod_id = $1 + ) AS version + ORDER BY version.date_published ASC ", mod_id as ModId, + &game_versions.unwrap_or_default(), + &loaders.unwrap_or_default(), ) .fetch_many(exec) .try_filter_map(|e| async { Ok(e.right().map(|v| VersionId(v.id))) }) @@ -468,7 +479,8 @@ impl Version { rc.channel release_channel, 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.url, ' ,') files, - STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes + STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes, + STRING_AGG(DISTINCT d.dependency_id || ', ' || d.dependency_type, ' ,') dependencies FROM versions v INNER JOIN release_channels rc on v.release_channel = rc.id LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id @@ -477,6 +489,7 @@ impl Version { 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, rc.id; ", @@ -490,13 +503,57 @@ impl Version { v.hashes.unwrap_or_default().split(" ,").for_each(|f| { let hash: Vec<&str> = f.split(", ").collect(); - hashes.push(( - FileId(hash[2].parse().unwrap_or(0)), - hash[0].to_string(), - hash[1].to_string().into_bytes(), - )); + + if hash.len() >= 3 { + hashes.push(( + FileId(hash[2].parse().unwrap_or(0)), + hash[0].to_string(), + hash[1].to_string().into_bytes(), + )); + } }); + let mut files = Vec::new(); + + v.files.unwrap_or_default().split(" ,").for_each(|f| { + let file: Vec<&str> = f.split(", ").collect(); + + if file.len() >= 4 { + 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()); + } + } + + files.push(QueryFile { + id: file_id, + url: file[3].to_string(), + filename: file[1].to_string(), + hashes: file_hashes, + primary: file[3].parse().unwrap_or(false), + }) + } + }); + + let mut dependencies = Vec::new(); + + v.dependencies + .unwrap_or_default() + .split(" ,") + .for_each(|f| { + let dependency: Vec<&str> = f.split(", ").collect(); + + if dependency.len() >= 2 { + dependencies.push(( + VersionId(dependency[0].parse().unwrap_or(0)), + dependency[1].to_string(), + )) + } + }); + Ok(Some(QueryVersion { id: VersionId(v.id), mod_id: ModId(v.mod_id), @@ -508,30 +565,7 @@ impl Version { date_published: v.date_published, downloads: v.downloads, release_channel: v.release_channel, - files: v - .files - .unwrap_or_default() - .split(" ,") - .map(|f| { - let file: Vec<&str> = f.split(", ").collect(); - 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()); - } - } - - QueryFile { - id: file_id, - url: file[3].to_string(), - filename: file[1].to_string(), - hashes: file_hashes, - primary: file[3].parse().unwrap_or(false), - } - }) - .collect(), + files, game_versions: v .game_versions .unwrap_or_default() @@ -545,6 +579,7 @@ impl Version { .map(|x| x.to_string()) .collect(), featured: v.featured, + dependencies, })) } else { Ok(None) @@ -568,7 +603,8 @@ impl Version { rc.channel release_channel, 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.url, ' ,') files, - STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes + STRING_AGG(DISTINCT h.algorithm || ', ' || encode(h.hash, 'escape') || ', ' || h.file_id, ' ,') hashes, + STRING_AGG(DISTINCT d.dependency_id || ', ' || d.dependency_type, ' ,') dependencies FROM versions v INNER JOIN release_channels rc on v.release_channel = rc.id LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id @@ -577,6 +613,7 @@ impl Version { 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 IN (SELECT * FROM UNNEST($1::bigint[])) GROUP BY v.id, rc.id; ", @@ -589,7 +626,45 @@ impl Version { v.hashes.unwrap_or_default().split(" ,").for_each(|f| { let hash : Vec<&str> = f.split(", ").collect(); - hashes.push((FileId(hash[2].parse().unwrap_or(0)), hash[0].to_string(), hash[1].to_string().into_bytes())); + + if hashes.len() >= 3 { + hashes.push((FileId(hash[2].parse().unwrap_or(0)), hash[0].to_string(), hash[1].to_string().into_bytes())); + } + }); + + let mut files = Vec::new(); + + v.files.unwrap_or_default().split(" ,").for_each(|f| { + let file : Vec<&str> = f.split(", ").collect(); + + if file.len() >= 4 { + 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()); + } + } + + files.push(QueryFile { + id: file_id, + url: file[3].to_string(), + filename: file[1].to_string(), + hashes: file_hashes, + primary: file[3].parse().unwrap_or(false) + }) + } + }); + + let mut dependencies = Vec::new(); + + v.dependencies.unwrap_or_default().split(" ,").for_each(|f| { + let dependency: Vec<&str> = f.split(", ").collect(); + + if dependency.len() >= 2 { + dependencies.push((VersionId(dependency[0].parse().unwrap_or(0)), dependency[1].to_string())) + } }); QueryVersion { @@ -603,29 +678,11 @@ impl Version { date_published: v.date_published, downloads: v.downloads, release_channel: v.release_channel, - files: v.files.unwrap_or_default() - .split(" ,").map(|f| { - let file : Vec<&str> = f.split(", ").collect(); - 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()); - } - } - - QueryFile { - id: file_id, - url: file[3].to_string(), - filename: file[1].to_string(), - hashes: file_hashes, - primary: file[3].parse().unwrap_or(false) - } - }).collect(), + files, 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, } })) }) @@ -669,6 +726,7 @@ pub struct QueryVersion { pub game_versions: Vec, pub loaders: Vec, pub featured: bool, + pub dependencies: Vec<(VersionId, String)>, } pub struct QueryFile { diff --git a/src/models/mods.rs b/src/models/mods.rs index a15633061..66dffe12c 100644 --- a/src/models/mods.rs +++ b/src/models/mods.rs @@ -208,7 +208,7 @@ pub struct Version { /// A list of files available for download for this version. pub files: Vec, /// A list of mods that this version depends on. - pub dependencies: Vec, + pub dependencies: Vec, /// A list of versions of Minecraft that this version of the mod supports. pub game_versions: Vec, /// The loaders that this version works on @@ -229,6 +229,16 @@ pub struct VersionFile { pub primary: bool, } +/// A dependency which describes what versions are required, break support, or are optional to the +/// version's functionality +#[derive(Serialize, Deserialize, Clone)] +pub struct Dependency { + /// The filename of the file. + pub version_id: VersionId, + /// Whether the file is the primary file of a version + pub dependency_type: DependencyType, +} + #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "lowercase")] pub enum VersionType { @@ -258,6 +268,44 @@ impl VersionType { } } +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +pub enum DependencyType { + Required, + Optional, + Incompatible, +} + +impl std::fmt::Display for DependencyType { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + DependencyType::Required => write!(fmt, "required"), + DependencyType::Optional => write!(fmt, "optional"), + DependencyType::Incompatible => write!(fmt, "incompatible"), + } + } +} + +impl DependencyType { + // These are constant, so this can remove unneccessary allocations (`to_string`) + pub fn as_str(&self) -> &'static str { + match self { + DependencyType::Required => "required", + DependencyType::Optional => "optional", + DependencyType::Incompatible => "incompatible", + } + } + + pub fn from_str(string: &str) -> DependencyType { + match string { + "required" => DependencyType::Required, + "optional" => DependencyType::Optional, + "incompatible" => DependencyType::Incompatible, + _ => DependencyType::Required, + } + } +} + /// A specific version of Minecraft #[derive(Serialize, Deserialize, Clone)] #[serde(transparent)] diff --git a/src/routes/mod_creation.rs b/src/routes/mod_creation.rs index 09a012ea1..45c0f3ec8 100644 --- a/src/routes/mod_creation.rs +++ b/src/routes/mod_creation.rs @@ -611,7 +611,7 @@ async fn create_initial_version( let dependencies = version_data .dependencies .iter() - .map(|x| (*x).into()) + .map(|x| ((x.version_id).into(), x.dependency_type.to_string())) .collect::>(); let version = models::version_item::VersionBuilder { diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index 974a67c40..520660052 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -3,7 +3,7 @@ use crate::database::models; use crate::database::models::version_item::{VersionBuilder, VersionFileBuilder}; use crate::file_hosting::FileHost; use crate::models::mods::{ - GameVersion, ModId, ModLoader, Version, VersionFile, VersionId, VersionType, + Dependency, GameVersion, ModId, ModLoader, Version, VersionFile, VersionId, VersionType, }; use crate::models::teams::Permissions; use crate::routes::mod_creation::{CreateError, UploadedFile}; @@ -21,7 +21,7 @@ pub struct InitialVersionData { pub version_number: String, pub version_title: String, pub version_body: Option, - pub dependencies: Vec, + pub dependencies: Vec, pub game_versions: Vec, pub release_channel: VersionType, pub loaders: Vec, @@ -221,6 +221,12 @@ async fn version_create_inner( loaders.push(id); } + let dependencies = version_create_data + .dependencies + .iter() + .map(|x| ((x.version_id).into(), x.dependency_type.to_string())) + .collect::>(); + version_builder = Some(VersionBuilder { version_id: version_id.into(), mod_id: version_create_data.mod_id.unwrap().into(), @@ -232,11 +238,7 @@ async fn version_create_inner( .clone() .unwrap_or_else(|| "".to_string()), files: Vec::new(), - dependencies: version_create_data - .dependencies - .iter() - .map(|x| (*x).into()) - .collect::>(), + dependencies, game_versions, loaders, release_channel, diff --git a/src/routes/versions.rs b/src/routes/versions.rs index 33846f1e8..0c0fe49a4 100644 --- a/src/routes/versions.rs +++ b/src/routes/versions.rs @@ -2,6 +2,7 @@ use super::ApiError; use crate::auth::get_user_from_headers; use crate::file_hosting::FileHost; use crate::models; +use crate::models::mods::{Dependency, DependencyType}; use crate::models::teams::Permissions; use crate::{database, Pepper}; use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse}; @@ -10,14 +11,17 @@ use sqlx::PgPool; use std::borrow::Borrow; use std::sync::Arc; -// TODO: this needs filtering, and a better response type -// Currently it only gives a list of ids, which have to be -// requested manually. This route could give a list of the -// ids as well as the supported versions and loaders, or -// other info that is needed for selecting the right version. +#[derive(Serialize, Deserialize)] +pub struct VersionListFilters { + pub game_versions: Option>, + pub loaders: Option>, + pub featured: Option, +} + #[get("version")] pub async fn version_list( info: web::Path<(models::ids::ModId,)>, + web::Query(filters): web::Query, pool: web::Data, ) -> Result { let id = info.into_inner().0.into(); @@ -32,14 +36,29 @@ pub async fn version_list( .exists; if mod_exists.unwrap_or(false) { - let mod_data = database::models::Version::get_mod_versions(id, &**pool) + let version_ids = database::models::Version::get_mod_versions( + id, + filters.game_versions, + filters.loaders, + &**pool, + ) + .await + .map_err(|e| ApiError::DatabaseError(e.into()))?; + + let versions = database::models::Version::get_many_full(version_ids, &**pool) .await .map_err(|e| ApiError::DatabaseError(e.into()))?; - let response = mod_data - .into_iter() - .map(|v| v.into()) - .collect::>(); + let mut response = Vec::new(); + for version in versions { + if let Some(featured) = filters.featured { + if featured { + response.push(convert_version(version)) + } + } else { + response.push(convert_version(version)) + } + } Ok(HttpResponse::Ok().json(response)) } else { @@ -132,7 +151,14 @@ fn convert_version(data: database::models::version_item::QueryVersion) -> models } }) .collect(), - dependencies: Vec::new(), // TODO: dependencies + dependencies: data + .dependencies + .into_iter() + .map(|d| Dependency { + version_id: d.0.into(), + dependency_type: DependencyType::from_str(&*d.1), + }) + .collect(), game_versions: data .game_versions .into_iter() @@ -152,7 +178,7 @@ pub struct EditVersion { pub version_number: Option, pub changelog: Option, pub version_type: Option, - pub dependencies: Option>, + pub dependencies: Option>, pub game_versions: Option>, pub loaders: Option>, pub featured: Option, @@ -272,15 +298,17 @@ pub async fn version_edit( .map_err(|e| ApiError::DatabaseError(e.into()))?; for dependency in dependencies { - let dependency_id: database::models::ids::VersionId = dependency.clone().into(); + let dependency_id: database::models::ids::VersionId = + dependency.version_id.clone().into(); sqlx::query!( " - INSERT INTO dependencies (dependent_id, dependency_id) - VALUES ($1, $2) + INSERT INTO dependencies (dependent_id, dependency_id, dependency_type) + VALUES ($1, $2, $3) ", id as database::models::ids::VersionId, dependency_id as database::models::ids::VersionId, + dependency.dependency_type.as_str() ) .execute(&mut *transaction) .await