diff --git a/migrations/20221129161609_status-types-changes.sql b/migrations/20221129161609_status-types-changes.sql new file mode 100644 index 000000000..ba9c2de67 --- /dev/null +++ b/migrations/20221129161609_status-types-changes.sql @@ -0,0 +1,23 @@ +-- Add migration script here +ALTER TABLE mods ADD COLUMN updated_status varchar(128) NULL; +ALTER TABLE mods ADD COLUMN requested_status varchar(128) NULL; + +UPDATE mods +SET updated_status = ( + SELECT s.status + FROM statuses s + WHERE s.id = mods.status +); + +ALTER TABLE mods +DROP COLUMN status; + +ALTER TABLE mods +RENAME COLUMN updated_status TO status; + +DROP TABLE statuses; + +ALTER TABLE mods ALTER COLUMN status SET NOT NULL; + +ALTER TABlE versions ADD COLUMN status varchar(128) NOT NULL DEFAULT 'listed'; +ALTER TABLE versions ADD COLUMN requested_status varchar(128) NULL; \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index e376a63e3..73a54b573 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -12,6 +12,20 @@ }, "query": "\n DELETE FROM mods_donations\n WHERE joining_mod_id = $1\n " }, + "03006da8781d9c07d564c6b406221cb0557623abe3242e79a21868482e6d9898": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Timestamptz", + "Int8" + ] + } + }, + "query": "\n UPDATE versions\n SET status = $1, date_published = $2\n WHERE (id = $3)\n " + }, "03209c5bda2d704e688439919a7b3903db6ad7caebf7ddafb3ea52d312d47bfb": { "describe": { "columns": [], @@ -51,6 +65,29 @@ }, "query": "\n SELECT EXISTS(SELECT 1 FROM users WHERE id = $1 AND email IS NULL)\n " }, + "034350830d7b406f8cc5520a80d3fa83056920d8e2decc8a4d88d5016a91673e": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int8", + "Int8", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Int4", + "Varchar", + "Bool", + "Varchar" + ] + } + }, + "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, changelog_url, date_published,\n downloads, version_type, featured, status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9,\n $10, $11, $12\n )\n " + }, "041f499f542ddab1b81bd445d6cabe225b1b2ad3ec7bbc1f755346c016ae06e6": { "describe": { "columns": [], @@ -190,28 +227,6 @@ }, "query": "\n INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n " }, - "091f325c84d0cf6dfe6933567c58fea815912ba613ffb3536466770d71ea613e": { - "describe": { - "columns": [ - { - "name": "version_id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Bytea", - "Text", - "Text" - ] - } - }, - "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\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3\n " - }, "0a1a470c12b84c7e171f0f51e8e541e9abe8bbee17fc441a5054e1dfd5607c05": { "describe": { "columns": [], @@ -245,26 +260,6 @@ }, "query": "\n SELECT SUM(pv.amount) amount\n FROM payouts_values pv\n WHERE pv.user_id = $1\n " }, - "0ca11a32b2860e4f5c3d20892a5be3cb419e084f42ba0f98e09b9995027fcc4e": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n SELECT id FROM statuses\n WHERE status = $1\n " - }, "0dbd0fa9a25416716a047184944d243ed5cb55808c6f300d7335c887f02a7f6e": { "describe": { "columns": [ @@ -558,6 +553,231 @@ }, "query": "\n UPDATE users\n SET avatar_url = $1\n WHERE (id = $2)\n " }, + "1b5bba3116e4ba5b00c19927a4dc0e3688e3cc737610bcaef67129f281cc78af": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "issues_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 19, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 29, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 30, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 31, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 32, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 34, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + 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.body_url body_url, 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN 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 " + }, "1c7b0eb4341af5a7942e52f632cf582561f10b4b6a41a082fb8a60f04ac17c6e": { "describe": { "columns": [ @@ -822,27 +1042,6 @@ }, "query": "\n UPDATE team_members\n SET permissions = $1\n WHERE (team_id = $2 AND user_id = $3)\n " }, - "250e7deb143373fc326bb54cf6f82b9790051329b5beb55f5942673d432e2c24": { - "describe": { - "columns": [ - { - "name": "count", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "\n SELECT COUNT(id)\n FROM mods\n WHERE\n status = ( SELECT id FROM statuses WHERE status = $1 ) OR\n status = ( SELECT id FROM statuses WHERE status = $2 )\n " - }, "25131559cb73a088000ab6379a769233440ade6c7511542da410065190d203fc": { "describe": { "columns": [ @@ -957,6 +1156,29 @@ }, "query": "\n SELECT id, team_id, role, permissions, accepted, payouts_split\n FROM team_members\n WHERE (user_id = $1 AND accepted = TRUE)\n " }, + "27c463b15f160d63c1cf82b08ee965e8408899f254c8a4c53f0e244ba615a1ed": { + "describe": { + "columns": [ + { + "name": "project_id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "TextArray", + "Bytea", + "Text", + "TextArray" + ] + } + }, + "query": "\n SELECT v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id 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 " + }, "28d5825964b0fddc43bd7d6851daf91845b79c9e88c82d5c7d97ae02502d0b4f": { "describe": { "columns": [], @@ -969,194 +1191,6 @@ }, "query": "INSERT INTO banned_users (github_id) VALUES ($1);" }, - "28ef29e10d4a46dae0bbacd276f6efc1abfa5dbc10981257ccb1d82fc2484af2": { - "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": "TextArray" - }, - { - "name": "loaders", - "ordinal": 19, - "type_info": "VarcharArray" - }, - { - "name": "versions", - "ordinal": 20, - "type_info": "VarcharArray" - }, - { - "name": "gallery", - "ordinal": 21, - "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 - ], - "parameters": { - "Left": [ - "Text", - "Text", - "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,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null) gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1 OR s.status = $2\n GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id;\n " - }, - "292da3eec2cc7d7eb635fa123be1b1387e9e91466f007e10101053fdb9874e3f": { - "describe": { - "columns": [ - { - "name": "url", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "version_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 3, - "type_info": "Int8" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Bytea", - "Text", - "Text" - ] - } - }, - "query": "\n SELECT f.url url, f.id id, f.version_id version_id, v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3\n " - }, "29e657d26f0fb24a766f5b5eb6a94d01d1616884d8ca10e91536e974d5b585a6": { "describe": { "columns": [], @@ -1287,6 +1321,27 @@ }, "query": "\n SELECT u.id, u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE u.id = ANY($1)\n " }, + "2d460f25461e95c744c835af5d67f8a7dd2438a46e3033611dfc0edd74fb9180": { + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "TextArray", + "TextArray" + ] + } + }, + "query": "\n SELECT COUNT(v.id)\n FROM versions v\n INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1)\n WHERE v.status = ANY($2)\n " + }, "2f7c011654d15c85dbb614ac01ed5613a6872ea8c172ab38fdaa0eb38a7d6e4f": { "describe": { "columns": [], @@ -1381,39 +1436,28 @@ }, "query": "\n UPDATE mods_gallery\n SET featured = $2\n WHERE id = $1\n " }, - "3b1fba1ce47e94c0301c8a234bc7609e3c62fc02acd1485283c0f824cff2d69b": { + "3b57a94086bb52bea14715799e25ac108c5b47f8b08b176614f5b1c1d243a047": { "describe": { "columns": [ - { - "name": "hash", - "ordinal": 0, - "type_info": "Bytea" - }, - { - "name": "algorithm", - "ordinal": 1, - "type_info": "Varchar" - }, { "name": "version_id", - "ordinal": 2, + "ordinal": 0, "type_info": "Int8" } ], "nullable": [ - false, - false, false ], "parameters": { "Left": [ - "ByteaArray", + "TextArray", + "Bytea", "Text", - "Text" + "TextArray" ] } }, - "query": "\n SELECT h.hash hash, h.algorithm algorithm, f.version_id version_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3\n " + "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 " }, "3bdcbfa5abe43cc9b4f996f147277a7f6921cca00f82cad0ef5d85032c761a36": { "describe": { @@ -1862,73 +1906,73 @@ }, "query": "\n INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id)\n VALUES ($1, $2)\n " }, - "46629cac59f5e86839b58d48239b65fade89598c613607e1db557e7b8b20b937": { + "463da1159ff5265f9f4a53ecac28415328e0f1677f6671f31f24aadb66773900": { "describe": { "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, { "name": "project_type", - "ordinal": 1, + "ordinal": 0, "type_info": "Int4" }, { "name": "title", - "ordinal": 2, + "ordinal": 1, "type_info": "Varchar" }, { "name": "description", - "ordinal": 3, + "ordinal": 2, "type_info": "Varchar" }, { "name": "downloads", - "ordinal": 4, + "ordinal": 3, "type_info": "Int4" }, { "name": "follows", - "ordinal": 5, + "ordinal": 4, "type_info": "Int4" }, { "name": "icon_url", - "ordinal": 6, + "ordinal": 5, "type_info": "Varchar" }, { "name": "body", - "ordinal": 7, + "ordinal": 6, "type_info": "Varchar" }, { "name": "body_url", - "ordinal": 8, + "ordinal": 7, "type_info": "Varchar" }, { "name": "published", - "ordinal": 9, + "ordinal": 8, "type_info": "Timestamptz" }, { "name": "updated", - "ordinal": 10, + "ordinal": 9, "type_info": "Timestamptz" }, { "name": "approved", - "ordinal": 11, + "ordinal": 10, "type_info": "Timestamptz" }, { "name": "status", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "requested_status", "ordinal": 12, - "type_info": "Int4" + "type_info": "Varchar" }, { "name": "issues_url", @@ -1990,55 +2034,15 @@ "ordinal": 24, "type_info": "Varchar" }, - { - "name": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 28, - "type_info": "Varchar" - }, { "name": "flame_anvil_project", - "ordinal": 29, + "ordinal": 25, "type_info": "Int4" }, { "name": "flame_anvil_user", - "ordinal": 30, + "ordinal": 26, "type_info": "Int8" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 32, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 34, - "type_info": "TextArray" } ], "nullable": [ @@ -2047,7 +2051,6 @@ false, false, false, - false, true, false, true, @@ -2060,12 +2063,6 @@ true, true, true, - false, - false, - false, - false, - true, - true, true, false, false, @@ -2073,18 +2070,17 @@ false, true, true, - null, - null, - null, - null + true, + true, + true ], "parameters": { "Left": [ - "Int8Array" + "Int8" ] } }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n 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, m.id;\n " + "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, 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\n FROM mods\n WHERE id = $1\n " }, "4778d2f5994fda2f978fa53e0840c1a9a2582ef0434a5ff7f21706f1dc4edcf4": { "describe": { @@ -2203,27 +2199,6 @@ }, "query": "\n UPDATE mods\n SET server_side = $1\n WHERE (id = $2)\n " }, - "4ac5d28c1d3fa26f0a42460ee1e515c3bd4a849fdff4d58c2a138c415186ab3d": { - "describe": { - "columns": [ - { - "name": "count", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "\n SELECT COUNT(f.id) FROM files f\n INNER JOIN versions v on f.version_id = v.id\n INNER JOIN mods m on v.mod_id = m.id\n WHERE\n status = ( SELECT id FROM statuses WHERE status = $1 ) OR\n status = ( SELECT id FROM statuses WHERE status = $2 )\n " - }, "4b14b5c69f6a0ee4e06e41d7cea425c7c34d6db45895275a2ce8adfa28dc8f72": { "describe": { "columns": [ @@ -2339,6 +2314,41 @@ }, "query": "\n UPDATE mods\n SET slug = LOWER($1)\n WHERE (id = $2)\n " }, + "501c4aec0d0b2b17b86b1923b949b27e8091ff8f9a75fa4a2ce7ecf294241f46": { + "describe": { + "columns": [ + { + "name": "hash", + "ordinal": 0, + "type_info": "Bytea" + }, + { + "name": "algorithm", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "version_id", + "ordinal": 2, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Left": [ + "TextArray", + "ByteaArray", + "Text", + "TextArray" + ] + } + }, + "query": "\n SELECT h.hash hash, h.algorithm algorithm, f.version_id version_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)\n INNER JOIN mods m on v.mod_id = m.id\n WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)\n " + }, "507314fdcacaa3c7751738c9d0baee2b90aec719b6b203f922824eced5ea8369": { "describe": { "columns": [], @@ -2400,80 +2410,6 @@ }, "query": "\n UPDATE versions\n SET version_number = $1\n WHERE (id = $2)\n " }, - "548dac1d87fb7280c065e18577c10590ba727e76a4e52d9e694a6c021d332708": { - "describe": { - "columns": [ - { - "name": "mod_id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "author_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "version_number", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "changelog", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "changelog_url", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "date_published", - "ordinal": 6, - "type_info": "Timestamptz" - }, - { - "name": "downloads", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "version_type", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "featured", - "ordinal": 9, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "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.version_type, v.featured\n FROM versions v\n WHERE v.id = $1\n " - }, "57a38641fe5bdb273190e8d586f46284340b9ff11b6ae3177923631a37bb11eb": { "describe": { "columns": [], @@ -2672,6 +2608,19 @@ }, "query": "\n UPDATE files\n SET is_primary = TRUE\n WHERE (id = $1)\n " }, + "5c5cac91f61b0cd98d2d986e2d22e5a6b220bdd39f98520385f4ea84b3ffeeed": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + } + }, + "query": "\n UPDATE versions\n SET status = $1\n WHERE (id = $2)\n " + }, "5ca43f2fddda27ad857f230a3427087f1e58150949adc6273156718730c10f69": { "describe": { "columns": [], @@ -2685,6 +2634,154 @@ }, "query": "\n UPDATE users\n SET role = $1\n WHERE (id = $2)\n " }, + "5cb877179a4667a5c1d77354f53b010da1cccd4350e47333f16d0b4d22a861ea": { + "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": "TextArray" + }, + { + "name": "loaders", + "ordinal": 19, + "type_info": "VarcharArray" + }, + { + "name": "versions", + "ordinal": 20, + "type_info": "VarcharArray" + }, + { + "name": "gallery", + "ordinal": 21, + "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 + ], + "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 || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null) gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id 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 " + }, "5d7425cfa91e332bf7cc14aa5c300b997e941c49757606f6b906cb5e060d3179": { "describe": { "columns": [], @@ -2858,6 +2955,60 @@ }, "query": "\n SELECT version.id 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 AND gvv.game_version_id IN (SELECT game_version_id FROM game_versions_versions WHERE joining_version_id = $2)\n INNER JOIN loaders_versions lv ON lv.version_id = v.id AND lv.loader_id IN (SELECT loader_id FROM loaders_versions WHERE version_id = $2)\n WHERE v.mod_id = $1\n ) AS version\n ORDER BY version.date_published DESC\n LIMIT 1\n " }, + "66b06ddcd0a4cf01e716331befa393a12631fe6752a7d078bda06b24d50daae2": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET requested_status = $1\n WHERE (id = $2)\n " + }, + "67932e9e1fbf8980449e47c353ed40050bacac5fe5577256b28836cf36f9c1a0": { + "describe": { + "columns": [ + { + "name": "url", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "version_id", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "project_id", + "ordinal": 3, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "TextArray", + "Bytea", + "Text", + "TextArray" + ] + } + }, + "query": "\n SELECT f.url url, f.id id, f.version_id version_id, v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id 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 " + }, "67d021f0776276081d3c50ca97afa6b78b98860bf929009e845e9c00a192e3b5": { "describe": { "columns": [ @@ -3014,6 +3165,26 @@ }, "query": "\n UPDATE files\n SET is_primary = FALSE\n WHERE (version_id = $1)\n " }, + "6e07cc68675d0f583182eaa9f50853fa5996b9f83543fe8b6c2a073cf6a9cb5d": { + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "TextArray" + ] + } + }, + "query": "\n SELECT COUNT(id)\n FROM mods\n WHERE status = ANY($1)\n " + }, "6fd06767f42be894c7a35c6b61f43407c55de43dc77ed02b39062278f3de81e3": { "describe": { "columns": [], @@ -3031,176 +3202,6 @@ }, "query": "\n INSERT INTO team_members (\n id, team_id, user_id, role, permissions, accepted\n )\n VALUES (\n $1, $2, $3, $4, $5, $6\n )\n " }, - "70a6c63bb0162679cad2634d8c7cc0a64b1bbbc678666de3198118a4086cc6b4": { - "describe": { - "columns": [ - { - "name": "project_type", - "ordinal": 0, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 11, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 17, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 18, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 20, - "type_info": "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" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body, flame_anvil_project,\n flame_anvil_user\n FROM mods\n WHERE id = $1\n " - }, "712a846d6b56609599ee7a6603ad921acd2d5da2b3ce0c5b3f3642ed83927542": { "describe": { "columns": [], @@ -3288,28 +3289,6 @@ }, "query": "\n DELETE FROM versions WHERE id = $1\n " }, - "740424ba1af7b0cf85d6031365d2c026e0b0003fb618bb82eeebcdad9599774a": { - "describe": { - "columns": [ - { - "name": "project_id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Bytea", - "Text", - "Text" - ] - } - }, - "query": "\n SELECT v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3\n " - }, "742f20f422361971c21b72c629c57a6c3870d8d6c41577496907290db5994f12": { "describe": { "columns": [], @@ -3491,36 +3470,6 @@ }, "query": "\n SELECT f.version_id version_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n WHERE h.algorithm = $2 AND h.hash = $1\n " }, - "7a3183f77f403d2272665727affb07775a9304cbe1fb8ee7e603d779edb95d03": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Varchar", - "Varchar", - "Varchar", - "Timestamptz", - "Int4", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Int4", - "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, 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,\n $14, $15, $16, $17,\n LOWER($18), $19\n )\n " - }, "7ab21e7613dd88e97cf602e76bff62170c13ceef8104a4ce4cb2d101f8ce4f48": { "describe": { "columns": [], @@ -3554,6 +3503,27 @@ }, "query": "\n SELECT SUM(pv.amount) amount\n FROM payouts_values pv\n WHERE pv.user_id = $1 AND created > NOW() - '1 month'::interval\n " }, + "7c34e28fc3090f5a1c7c55d59905d28f41c1e22dc682cc7c54b9234fc7212e99": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Int8" + ] + } + }, + "query": "\n SELECT id FROM mods\n WHERE status = $1\n ORDER BY updated ASC\n LIMIT $2;\n " + }, "7c61fee015231f0a97c25d24f2c6be24821e39e330ab82344ad3b985d0d2aaea": { "describe": { "columns": [ @@ -3587,38 +3557,6 @@ }, "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, FALSE)\n " }, - "7f4fef104bdff9036c83499268c6b22406f8d7e5502607ed6ff47d5a0979ede2": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "version_number", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "version_type", - "ordinal": 2, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1\n ORDER BY date_published ASC\n " - }, "8129255d25bf0624d83f50558b668ed7b7f9c264e380d276522fc82bc871939b": { "describe": { "columns": [], @@ -3634,27 +3572,6 @@ }, "query": "\n INSERT INTO notifications_actions (\n notification_id, title, action_route, action_route_method\n )\n VALUES (\n $1, $2, $3, $4\n )\n " }, - "844735a1ffd4b17c96e0cc441c85b3d05325523b415c509bbf2860b7e1ca0de3": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - } - }, - "query": "\n SELECT m.id FROM mods m\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE\n WHERE tm.user_id = $1 AND m.status = (SELECT s.id FROM statuses s WHERE s.status = $2)\n ORDER BY m.downloads DESC\n " - }, "868ee76d507cc9e94cd3c2e44770faff127e2b3c5f49b8100a9a37ac4d7b1f1d": { "describe": { "columns": [], @@ -3680,27 +3597,6 @@ }, "query": "\n DELETE FROM project_types\n WHERE name = $1\n " }, - "8a4d05106f27de01a1a8ebf5266727b71fa5b6a34b48cd9ba3996fe8ec1ab78a": { - "describe": { - "columns": [ - { - "name": "count", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "\n SELECT COUNT(DISTINCT u.id)\n FROM users u\n INNER JOIN team_members tm on u.id = tm.user_id AND tm.accepted = TRUE\n INNER JOIN mods m on tm.team_id = m.team_id AND (\n m.status = ( SELECT s.id FROM statuses s WHERE s.status = $1 ) OR\n m.status = ( SELECT s.id FROM statuses s WHERE s.status = $2 )\n )\n " - }, "8a7b2bc070e5e8308e2853ff125bc98f40b22c1d0deeb013dd90ce5768bd0ce8": { "describe": { "columns": [], @@ -3784,7 +3680,7 @@ }, "query": "\n UPDATE users\n SET payout_wallet = $1, payout_wallet_type = $2, payout_address = $3\n WHERE (id = $4)\n " }, - "8ced4b7a1c4f944b918d2d2eefc8007a3fb9565021ca8c44608caccbe1ab9674": { + "8e9127af96108ec5a1da1a75abcaa1e810625d89232a4e7c0dffec77896c87ba": { "describe": { "columns": [ { @@ -3843,28 +3739,38 @@ "type_info": "Bool" }, { - "name": "game_versions", + "name": "status", "ordinal": 11, - "type_info": "TextArray" + "type_info": "Varchar" }, { - "name": "loaders", + "name": "requested_status", "ordinal": 12, - "type_info": "VarcharArray" + "type_info": "Varchar" }, { - "name": "files", + "name": "game_versions", "ordinal": 13, "type_info": "TextArray" }, { - "name": "hashes", + "name": "loaders", "ordinal": 14, + "type_info": "VarcharArray" + }, + { + "name": "files", + "ordinal": 15, + "type_info": "TextArray" + }, + { + "name": "hashes", + "ordinal": 16, "type_info": "TextArray" }, { "name": "dependencies", - "ordinal": 15, + "ordinal": 17, "type_info": "TextArray" } ], @@ -3880,6 +3786,8 @@ false, false, false, + false, + true, null, null, null, @@ -3892,7 +3800,7 @@ ] } }, - "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 ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,\n ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,\n ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) 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 " + "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, v.status status, v.requested_status requested_status,\n ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,\n ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,\n ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) 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 " }, "8f706d78ac4235ea04c59e2c220a4791e1d08fdf287b783b4aaef36fd2445467": { "describe": { @@ -4099,228 +4007,6 @@ }, "query": "\n UPDATE mods\n SET wiki_url = $1\n WHERE (id = $2)\n " }, - "9b231e09e8a1e56cca49bf82f9a605e3d13b9e6a1cfc6357b5f544d3d9a89dfd": { - "describe": { - "columns": [ - { - "name": "url", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "hash", - "ordinal": 1, - "type_info": "Bytea" - }, - { - "name": "algorithm", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "version_id", - "ordinal": 3, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 4, - "type_info": "Int8" - } - ], - "nullable": [ - false, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "ByteaArray", - "Text", - "Text" - ] - } - }, - "query": "\n SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id\n INNER JOIN mods m on v.mod_id = m.id\n INNER JOIN statuses s on m.status = s.id\n WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3\n " - }, - "9b2d1298e02d75dd3c56a7813ec2cbfc899361f678d0af41aba9f0ad08fe7f2d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "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" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body, flame_anvil_project,\n flame_anvil_user\n FROM mods\n WHERE id = ANY($1)\n " - }, "9bbb52954fccebc8aa467618dcb2c224722c8d816ec6803bcd8711778bd56199": { "describe": { "columns": [ @@ -4416,6 +4102,27 @@ }, "query": "\n UPDATE mods\n SET approved = NOW()\n WHERE id = $1 AND approved IS NULL\n " }, + "a1ba3b5cc50b1eb24f5529e06be1439f4a313c4ea8845c2733db752e53f5ae1c": { + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "TextArray", + "TextArray" + ] + } + }, + "query": "\n SELECT COUNT(f.id) FROM files f\n INNER JOIN versions v on f.version_id = v.id AND v.status = ANY($2)\n INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1)\n " + }, "a2c3f1dc8939a0df9cb62e7e751847b7681b96b4016389cf5f39ebd1deff6e5a": { "describe": { "columns": [], @@ -4472,6 +4179,202 @@ }, "query": "\n SELECT mv.mod_id, m.id, COUNT(m.id) times_depended FROM versions mv\n INNER JOIN dependencies d ON d.dependent_id = mv.id\n INNER JOIN versions v ON d.dependency_id = v.id\n INNER JOIN mods m ON v.mod_id = m.id OR d.mod_dependency_id = m.id\n WHERE mv.mod_id = ANY($1)\n group by mv.mod_id, m.id;\n " }, + "a440cb2567825c3cc540c9b0831ee840f6e2a6394e89a851b83fc78220594cf2": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Timestamptz", + "Int8" + ] + } + }, + "query": "\n UPDATE mods\n SET status = $1, approved = $2\n WHERE (id = $3)\n " + }, + "a44e3e69d342d7e60e3e9e25843788011b08f3230564597f100a495cc97b9c0c": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "issues_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 19, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 26, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 27, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + true, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, approved, status, 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\n FROM mods\n WHERE id = ANY($1)\n " + }, "a647c282a276b63f36d2d8a253c32d0f627cea9cab8eb1b32b39875536bdfcbb": { "describe": { "columns": [], @@ -4550,43 +4453,25 @@ }, "query": "\n DELETE FROM states\n WHERE expires < CURRENT_DATE\n " }, - "a91fabe9e620bd700362c68631628725419183025c9699f4bd31c22b813b2824": { + "aaec611bae08eac41c163367dc508208178170de91165095405f1b41e47f5e7f": { "describe": { "columns": [ { - "name": "id", + "name": "count", "ordinal": 0, "type_info": "Int8" - }, - { - "name": "url", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "filename", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "is_primary", - "ordinal": 3, - "type_info": "Bool" } ], "nullable": [ - false, - false, - false, - false + null ], "parameters": { "Left": [ - "Int8" + "TextArray" ] } }, - "query": "\n SELECT files.id, files.url, files.filename, files.is_primary FROM files\n WHERE files.version_id = $1\n " + "query": "\n SELECT COUNT(DISTINCT u.id)\n FROM users u\n INNER JOIN team_members tm on u.id = tm.user_id AND tm.accepted = TRUE\n INNER JOIN mods m on tm.team_id = m.team_id AND m.status = ANY($1)\n " }, "aaec67a66b58dec36339c14000b319aed1b0ebb1324fc85e34d14c6430c26657": { "describe": { @@ -4895,6 +4780,18 @@ }, "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n VALUES ($1, $2)\n " }, + "b971cecafab7046c5952447fd78a6e45856841256d812ce9ae3c07f903c5cc62": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "\n UPDATE mods\n SET status = requested_status\n WHERE status = $1 AND approved < CURRENT_DATE AND requested_status IS NOT NULL\n " + }, "b99e906aa6ca18b9f3f111eae7bf0d360f42385ca99228a844387bf9456a6a31": { "describe": { "columns": [], @@ -4919,6 +4816,98 @@ }, "query": "\n DELETE FROM notifications_actions\n WHERE notification_id = ANY($1)\n " }, + "bb30f376f5ea8ddb66371b937812511c1685871c1efccfca0d190bbb5103eaa7": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "mod_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "author_id", + "ordinal": 2, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "version_number", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "changelog", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "changelog_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "date_published", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "downloads", + "ordinal": 8, + "type_info": "Int4" + }, + { + "name": "version_type", + "ordinal": 9, + "type_info": "Varchar" + }, + { + "name": "featured", + "ordinal": 10, + "type_info": "Bool" + }, + { + "name": "status", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 12, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + true + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,\n v.changelog, v.changelog_url, v.date_published, v.downloads,\n v.version_type, v.featured, v.status, v.requested_status\n FROM versions v\n WHERE v.id = ANY($1)\n ORDER BY v.date_published ASC\n " + }, "bbfb47ae2c972734785df6b7c3e62077dc544ef4ccf8bb89e9c22c2f50a933c1": { "describe": { "columns": [], @@ -5227,27 +5216,6 @@ }, "query": "\n UPDATE versions\n SET version_type = $1\n WHERE (id = $2)\n " }, - "c3dcb5a8b798ea6c0922698a007dbc8ab549f5f85bad780da59163f4d6371238": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text", - "Int8" - ] - } - }, - "query": "\n SELECT id FROM mods\n WHERE status = (\n SELECT id FROM statuses WHERE status = $1\n )\n ORDER BY updated ASC\n LIMIT $2;\n " - }, "c3f594d8d0ffcf5df1b36759cf3088bfaec496c5dfdbf496d3b05f0b122a5d0c": { "describe": { "columns": [], @@ -5439,6 +5407,18 @@ }, "query": "SELECT id, mod_id FROM versions\n WHERE ((version_number = $1 OR id = $3) AND mod_id = $2)" }, + "c8a27a122160a0896914c786deef9e8193eb240501d30d5ffb4129e2103efd3d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "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 " + }, "c9d63ed46799db7c30a7e917d97a5d4b2b78b0234cce49e136fa57526b38c1ca": { "describe": { "columns": [ @@ -5926,86 +5906,6 @@ }, "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " }, - "d5a496a0e17c5784f98ca2067bff996b23bb0a798609c4d4928df8080e4e1758": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "mod_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "author_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "version_number", - "ordinal": 4, - "type_info": "Varchar" - }, - { - "name": "changelog", - "ordinal": 5, - "type_info": "Varchar" - }, - { - "name": "changelog_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "date_published", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "downloads", - "ordinal": 8, - "type_info": "Int4" - }, - { - "name": "version_type", - "ordinal": 9, - "type_info": "Varchar" - }, - { - "name": "featured", - "ordinal": 10, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number,\n v.changelog, v.changelog_url, v.date_published, v.downloads,\n v.version_type, v.featured\n FROM versions v\n WHERE v.id = ANY($1)\n ORDER BY v.date_published ASC\n " - }, "d5b00d6237b04018822db529995f0b001cd1cabf5ca93b4aff37f12c4feb83f6": { "describe": { "columns": [ @@ -6155,28 +6055,6 @@ }, "query": "\n DELETE FROM files\n WHERE files.version_id = $1\n " }, - "dc2a3a07469ce25a9749d9d1c2e7424ac6765a72e8d9dd90eb682664f7cf036f": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int8", - "Int8", - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Timestamptz", - "Int4", - "Varchar", - "Bool" - ] - } - }, - "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, changelog_url, date_published,\n downloads, version_type, featured\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9,\n $10, $11\n )\n " - }, "dc6aa2e7bfd5d5004620ddd4cd6a47ecc56159e1489054e0652d56df802fb5e5": { "describe": { "columns": [], @@ -6466,6 +6344,53 @@ }, "query": "\n SELECT u.github_id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role, u.badges,\n u.balance, u.payout_wallet, u.payout_wallet_type,\n u.payout_address, u.flame_anvil_key\n FROM users u\n WHERE u.id = $1\n " }, + "e5bbcf58b8f4abb91757a7dea8d7151cbeaa79fe3aee6542476c1174e82fbe92": { + "describe": { + "columns": [ + { + "name": "url", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "hash", + "ordinal": 1, + "type_info": "Bytea" + }, + { + "name": "algorithm", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "version_id", + "ordinal": 3, + "type_info": "Int8" + }, + { + "name": "project_id", + "ordinal": 4, + "type_info": "Int8" + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "TextArray", + "ByteaArray", + "Text", + "TextArray" + ] + } + }, + "query": "\n SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h\n INNER JOIN files f ON h.file_id = f.id\n INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1)\n INNER JOIN mods m on v.mod_id = m.id\n WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4)\n " + }, "e5de3b33893b6b48a7fee0e3f20e371e56fdfd71640662aacc15fe3bf747b3a1": { "describe": { "columns": [ @@ -6622,7 +6547,7 @@ "nullable": [], "parameters": { "Left": [ - "Int4", + "Varchar", "Int8" ] } @@ -6649,6 +6574,92 @@ }, "query": "\n SELECT n.id FROM notifications n\n WHERE n.user_id = $1\n " }, + "ec22b034f9f08d85e2bad06e49db2aa4ba66d0d03679543125608914f0373a89": { + "describe": { + "columns": [ + { + "name": "mod_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "author_id", + "ordinal": 1, + "type_info": "Int8" + }, + { + "name": "name", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "version_number", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "changelog", + "ordinal": 4, + "type_info": "Varchar" + }, + { + "name": "changelog_url", + "ordinal": 5, + "type_info": "Varchar" + }, + { + "name": "date_published", + "ordinal": 6, + "type_info": "Timestamptz" + }, + { + "name": "downloads", + "ordinal": 7, + "type_info": "Int4" + }, + { + "name": "version_type", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "featured", + "ordinal": 9, + "type_info": "Bool" + }, + { + "name": "status", + "ordinal": 10, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 11, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "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.version_type, v.featured, v.status, v.requested_status\n FROM versions v\n WHERE v.id = $1\n " + }, "ed1d5d9433bc7f4a360431ecfdd9430c5e58cd6d1c623c187d8661200400b1a4": { "describe": { "columns": [], @@ -6686,229 +6697,36 @@ }, "query": "\n DELETE FROM dependencies WHERE mod_dependency_id = $1\n " }, - "ee6b9377ab72c433b9459e0f86f49abff150206193af553be64e0f82d0a627e8": { + "edd50de303f84695508a1067683ce881283bba5342aff36b7d03dc9638a6f9f3": { "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "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": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 28, - "type_info": "Varchar" - }, - { - "name": "flame_anvil_project", - "ordinal": 29, - "type_info": "Int4" - }, - { - "name": "flame_anvil_user", - "ordinal": 30, - "type_info": "Int8" - }, - { - "name": "categories", - "ordinal": 31, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 32, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 34, - "type_info": "TextArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - true, - true, - null, - null, - null, - null - ], + "columns": [], + "nullable": [], "parameters": { "Left": [ - "Int8" + "Int8", + "Int8", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Int4", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Int4", + "Int4", + "Varchar", + "Varchar", + "Text", + "Int4" ] } }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n 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, m.id;\n " + "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": { @@ -7007,173 +6825,7 @@ }, "query": "\n DELETE FROM team_members\n WHERE user_id = $1\n " }, - "f3a8ad4a802dde0eb9304078e0368066e7d48121dfe73a63b2911b0998840a79": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n SELECT id FROM users\n WHERE LOWER(username) = LOWER($1)\n " - }, - "f453b43772c4d2d9d09dc389eb95482cc75e7f0eaf9dc7ff48cf40f22f1497cc": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - } - }, - "query": "\n UPDATE users\n SET bio = $1\n WHERE (id = $2)\n " - }, - "f6eae06931e9cde0f18e7031bc93c33fa689de4d9676c1a8a3fc14a182d5fb08": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Timestamptz", - "Text" - ] - } - }, - "query": "\n UPDATE users\n SET midas_expires = $1, is_overdue = FALSE\n WHERE (stripe_customer_id = $2)\n " - }, - "f8be3053274b00ee9743e798886696062009c5f681baaf29dfc24cfbbda93742": { - "describe": { - "columns": [ - { - "name": "exists", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))\n " - }, - "f8c00875a7450c74423f9913cc3500898e9fcb6aa7eb8fc2f6fd16dc560773de": { - "describe": { - "columns": [ - { - "name": "short", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Int4" - ] - } - }, - "query": "\n SELECT short, name FROM donation_platforms\n WHERE id = $1\n " - }, - "fb81707d46632f5733222bc3d9c4c5574366db130b130263fcb0eabcf7983b78": { - "describe": { - "columns": [ - { - "name": "count", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - } - }, - "query": "\n SELECT COUNT(v.id)\n FROM versions v\n INNER JOIN mods m on v.mod_id = m.id\n WHERE\n status = ( SELECT id FROM statuses WHERE status = $1 ) OR\n status = ( SELECT id FROM statuses WHERE status = $2 )\n " - }, - "fb955ca41b95120f66c98c0b528b1db10c4be4a55e9641bb104d772e390c9bb7": { - "describe": { - "columns": [ - { - "name": "exists", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT EXISTS(SELECT 1 FROM notifications WHERE id=$1)" - }, - "fcd15905507769ab7f9839d64d1be3ee3f61cd555aee57dace76f8e53e91d344": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " - }, - "fdfe36dcb85347a3a8228b5d5fc2d017b9baa307b5ae0ae9deaafab9dcdcb74a": { - "describe": { - "columns": [ - { - "name": "follower_id", - "ordinal": 0, - "type_info": "Int8" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n " - }, - "feaaae02e9d35fbfb9f83d3b653902b58489f498c493b6b0ed73ad4e1f863468": { + "f2a342f404e3d0c0bb1af153a5f7a8be244fb16302f089cc77d3852c2ef944af": { "describe": { "columns": [ { @@ -7232,28 +6884,38 @@ "type_info": "Bool" }, { - "name": "game_versions", + "name": "status", "ordinal": 11, - "type_info": "TextArray" + "type_info": "Varchar" }, { - "name": "loaders", + "name": "requested_status", "ordinal": 12, - "type_info": "VarcharArray" + "type_info": "Varchar" }, { - "name": "files", + "name": "game_versions", "ordinal": 13, "type_info": "TextArray" }, { - "name": "hashes", + "name": "loaders", "ordinal": 14, + "type_info": "VarcharArray" + }, + { + "name": "files", + "ordinal": 15, + "type_info": "TextArray" + }, + { + "name": "hashes", + "ordinal": 16, "type_info": "TextArray" }, { "name": "dependencies", - "ordinal": 15, + "ordinal": 17, "type_info": "TextArray" } ], @@ -7269,6 +6931,8 @@ false, false, false, + false, + true, null, null, null, @@ -7281,6 +6945,430 @@ ] } }, - "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 ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,\n ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,\n ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) 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 " + "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, v.status status, v.requested_status requested_status,\n ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files,\n ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes,\n ARRAY_AGG(DISTINCT COALESCE(d.dependency_id, 0) || ' |||| ' || COALESCE(d.mod_dependency_id, 0) || ' |||| ' || d.dependency_type || ' |||| ' || COALESCE(d.dependency_file_name, ' ')) filter (where d.dependency_type is not null) 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 " + }, + "f3a8ad4a802dde0eb9304078e0368066e7d48121dfe73a63b2911b0998840a79": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "\n SELECT id FROM users\n WHERE LOWER(username) = LOWER($1)\n " + }, + "f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "version_number", + "ordinal": 1, + "type_info": "Varchar" + }, + { + "name": "version_type", + "ordinal": 2, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false, + false + ], + "parameters": { + "Left": [ + "Int8", + "TextArray" + ] + } + }, + "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY date_published ASC\n " + }, + "f453b43772c4d2d9d09dc389eb95482cc75e7f0eaf9dc7ff48cf40f22f1497cc": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + } + }, + "query": "\n UPDATE users\n SET bio = $1\n WHERE (id = $2)\n " + }, + "f6eae06931e9cde0f18e7031bc93c33fa689de4d9676c1a8a3fc14a182d5fb08": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Timestamptz", + "Text" + ] + } + }, + "query": "\n UPDATE users\n SET midas_expires = $1, is_overdue = FALSE\n WHERE (stripe_customer_id = $2)\n " + }, + "f831fad03729b3aea44a3b46ebd13abf50ee1f26f02512f65b8f66492b229533": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "requested_status", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "issues_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 18, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 19, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 21, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 29, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 30, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 31, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 32, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 34, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + 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.body_url body_url, 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,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN 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 " + }, + "f8be3053274b00ee9743e798886696062009c5f681baaf29dfc24cfbbda93742": { + "describe": { + "columns": [ + { + "name": "exists", + "ordinal": 0, + "type_info": "Bool" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "\n SELECT EXISTS(SELECT 1 FROM mods WHERE slug = LOWER($1))\n " + }, + "f8c00875a7450c74423f9913cc3500898e9fcb6aa7eb8fc2f6fd16dc560773de": { + "describe": { + "columns": [ + { + "name": "short", + "ordinal": 0, + "type_info": "Varchar" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Varchar" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Left": [ + "Int4" + ] + } + }, + "query": "\n SELECT short, name FROM donation_platforms\n WHERE id = $1\n " + }, + "fa1b92b15cc108fa046998f789c8b259e0226e7dac16c635927ca74abc78cea9": { + "describe": { + "columns": [ + { + "name": "exists", + "ordinal": 0, + "type_info": "Bool" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + } + }, + "query": "SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 WHERE m.id = $1)" + }, + "fb955ca41b95120f66c98c0b528b1db10c4be4a55e9641bb104d772e390c9bb7": { + "describe": { + "columns": [ + { + "name": "exists", + "ordinal": 0, + "type_info": "Bool" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT EXISTS(SELECT 1 FROM notifications WHERE id=$1)" + }, + "fcd15905507769ab7f9839d64d1be3ee3f61cd555aee57dace76f8e53e91d344": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int8", + "Int4" + ] + } + }, + "query": "\n INSERT INTO mods_categories (joining_mod_id, joining_category_id, is_additional)\n VALUES ($1, $2, TRUE)\n " + }, + "fdfe36dcb85347a3a8228b5d5fc2d017b9baa307b5ae0ae9deaafab9dcdcb74a": { + "describe": { + "columns": [ + { + "name": "follower_id", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT follower_id FROM mod_follows\n WHERE mod_id = $1\n " } } \ No newline at end of file diff --git a/src/database/models/mod.rs b/src/database/models/mod.rs index 6b81174f1..d1df5538b 100644 --- a/src/database/models/mod.rs +++ b/src/database/models/mod.rs @@ -18,9 +18,7 @@ pub use project_item::Project; pub use team_item::Team; pub use team_item::TeamMember; pub use user_item::User; -pub use version_item::FileHash; pub use version_item::Version; -pub use version_item::VersionFile; #[derive(Error, Debug)] pub enum DatabaseError { @@ -32,28 +30,6 @@ pub enum DatabaseError { Other(String), } -impl ids::StatusId { - pub async fn get_id<'a, E>( - status: &crate::models::projects::ProjectStatus, - exec: E, - ) -> Result, DatabaseError> - where - E: sqlx::Executor<'a, Database = sqlx::Postgres>, - { - let result = sqlx::query!( - " - SELECT id FROM statuses - WHERE status = $1 - ", - status.as_str() - ) - .fetch_optional(exec) - .await?; - - Ok(result.map(|r| ids::StatusId(r.id))) - } -} - impl ids::SideTypeId { pub async fn get_id<'a, E>( side: &crate::models::projects::SideType, diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index f56e40db6..fba9f7c5e 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -1,5 +1,6 @@ use super::ids::*; use crate::database::models::convert_postgres_date; +use crate::models::projects::ProjectStatus; use chrono::{DateTime, Utc}; #[derive(Clone, Debug)] @@ -89,7 +90,8 @@ pub struct ProjectBuilder { pub categories: Vec, pub additional_categories: Vec, pub initial_versions: Vec, - pub status: StatusId, + pub status: ProjectStatus, + pub requested_status: Option, pub client_side: SideTypeId, pub server_side: SideTypeId, pub license: String, @@ -115,6 +117,7 @@ impl ProjectBuilder { updated: Utc::now(), approved: None, status: self.status, + requested_status: self.requested_status, downloads: 0, follows: 0, icon_url: self.icon_url, @@ -190,7 +193,8 @@ pub struct Project { pub published: DateTime, pub updated: DateTime, pub approved: Option>, - pub status: StatusId, + pub status: ProjectStatus, + pub requested_status: Option, pub downloads: i32, pub follows: i32, pub icon_url: Option, @@ -219,16 +223,16 @@ impl Project { INSERT INTO mods ( id, team_id, title, description, body, published, downloads, icon_url, issues_url, - source_url, wiki_url, status, discord_url, + source_url, wiki_url, status, requested_status, discord_url, client_side, server_side, license_url, license, slug, project_type ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, - $10, $11, $12, $13, - $14, $15, $16, $17, - LOWER($18), $19 + $10, $11, $12, $13, $14, + $15, $16, $17, $18, + LOWER($19), $20 ) ", self.id as ProjectId, @@ -242,7 +246,8 @@ impl Project { self.issues_url.as_ref(), self.source_url.as_ref(), self.wiki_url.as_ref(), - self.status.0, + self.status.as_str(), + self.requested_status.map(|x| x.as_str()), self.discord_url.as_ref(), self.client_side as SideTypeId, self.server_side as SideTypeId, @@ -268,7 +273,7 @@ impl Project { " SELECT project_type, title, description, downloads, follows, icon_url, body, body_url, published, - updated, approved, status, + updated, approved, status, requested_status, 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, @@ -299,7 +304,10 @@ impl Project { license_url: row.license_url, discord_url: row.discord_url, client_side: SideTypeId(row.client_side), - status: StatusId(row.status), + status: ProjectStatus::from_str(&row.status), + requested_status: row + .requested_status + .map(|x| ProjectStatus::from_str(&x)), server_side: SideTypeId(row.server_side), license: row.license, slug: row.slug, @@ -331,7 +339,7 @@ impl Project { " SELECT id, project_type, title, description, downloads, follows, icon_url, body, body_url, published, - updated, approved, status, + updated, approved, status, requested_status, 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, @@ -360,7 +368,12 @@ impl Project { license_url: m.license_url, discord_url: m.discord_url, client_side: SideTypeId(m.client_side), - status: StatusId(m.status), + status: ProjectStatus::from_str( + &m.status, + ), + requested_status: m.requested_status.map(|x| ProjectStatus::from_str( + &x, + )), server_side: SideTypeId(m.server_side), license: m.license, slug: m.slug, @@ -370,7 +383,7 @@ impl Project { moderation_message_body: m.moderation_message_body, approved: m.approved, flame_anvil_project: m.flame_anvil_project, - flame_anvil_user: m.flame_anvil_user.map(UserId) + flame_anvil_user: m.flame_anvil_user.map(UserId), })) }) .try_collect::>() @@ -646,29 +659,29 @@ impl Project { " 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.approved approved, m.status status, + 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, - s.status status_name, 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, + 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, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions, ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery, ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) 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 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 versions v ON v.mod_id = m.id AND v.status = ANY($2) 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, m.id; + GROUP BY pt.id, cs.id, ss.id, m.id; ", id as ProjectId, + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_listed()).map(|x| x.to_string()).collect::>() ) .fetch_optional(executor) .await?; @@ -709,7 +722,10 @@ impl Project { license_url: m.license_url.clone(), discord_url: m.discord_url.clone(), client_side: SideTypeId(m.client_side), - status: StatusId(m.status), + status: ProjectStatus::from_str(&m.status), + requested_status: m + .requested_status + .map(|x| ProjectStatus::from_str(&x)), server_side: SideTypeId(m.server_side), license: m.license.clone(), slug: m.slug.clone(), @@ -802,9 +818,6 @@ impl Project { } }) .collect(), - status: crate::models::projects::ProjectStatus::from_str( - &m.status_name, - ), client_side: crate::models::projects::SideType::from_str( &m.client_side_type, ), @@ -832,29 +845,29 @@ impl Project { " 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.approved approved, m.status status, + 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, - s.status status_name, 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, + 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, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions, ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery, ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) 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 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 versions v ON v.mod_id = m.id AND v.status = ANY($2) 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, m.id; + GROUP BY pt.id, cs.id, ss.id, m.id; ", - &project_ids_parsed + &project_ids_parsed, + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_listed()).map(|x| x.to_string()).collect::>() ) .fetch_many(exec) .try_filter_map(|e| async { @@ -897,7 +910,12 @@ impl Project { license_url: m.license_url.clone(), discord_url: m.discord_url.clone(), client_side: SideTypeId(m.client_side), - status: StatusId(m.status), + status: ProjectStatus::from_str( + &m.status, + ), + requested_status: m.requested_status.map(|x| ProjectStatus::from_str( + &x, + )), server_side: SideTypeId(m.server_side), license: m.license.clone(), slug: m.slug.clone(), @@ -978,7 +996,6 @@ impl Project { } }) .collect(), - status: crate::models::projects::ProjectStatus::from_str(&m.status_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), }})) @@ -997,7 +1014,6 @@ pub struct QueryProject { pub versions: Vec, pub donation_urls: Vec, pub gallery_items: Vec, - pub status: crate::models::projects::ProjectStatus, pub client_side: crate::models::projects::SideType, pub server_side: crate::models::projects::SideType, } diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index cdfb1f89b..fc0b54e42 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -255,34 +255,6 @@ impl User { } pub async fn get_projects<'a, E>( - user_id: UserId, - status: &str, - exec: E, - ) -> Result, sqlx::Error> - where - E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, - { - use futures::stream::TryStreamExt; - - let projects = sqlx::query!( - " - SELECT m.id FROM mods m - INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.accepted = TRUE - WHERE tm.user_id = $1 AND m.status = (SELECT s.id FROM statuses s WHERE s.status = $2) - ORDER BY m.downloads DESC - ", - user_id as UserId, - status, - ) - .fetch_many(exec) - .try_filter_map(|e| async { Ok(e.right().map(|m| ProjectId(m.id))) }) - .try_collect::>() - .await?; - - Ok(projects) - } - - pub async fn get_projects_private<'a, E>( user_id: UserId, exec: E, ) -> Result, sqlx::Error> diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index 61e692774..0819e46b2 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -1,6 +1,7 @@ use super::ids::*; use super::DatabaseError; use crate::database::models::convert_postgres_date; +use crate::models::projects::VersionStatus; use chrono::{DateTime, Utc}; use std::cmp::Ordering; use std::collections::HashMap; @@ -18,6 +19,8 @@ pub struct VersionBuilder { pub loaders: Vec, pub version_type: String, pub featured: bool, + pub status: VersionStatus, + pub requested_status: Option, } pub struct DependencyBuilder { @@ -149,6 +152,8 @@ impl VersionBuilder { downloads: 0, featured: self.featured, version_type: self.version_type, + status: self.status, + requested_status: self.requested_status, }; version.insert(&mut *transaction).await?; @@ -237,6 +242,7 @@ impl VersionBuilder { } } +#[derive(Clone)] pub struct Version { pub id: VersionId, pub project_id: ProjectId, @@ -249,6 +255,8 @@ pub struct Version { pub downloads: i32, pub version_type: String, pub featured: bool, + pub status: VersionStatus, + pub requested_status: Option, } impl Version { @@ -261,13 +269,13 @@ impl Version { INSERT INTO versions ( id, mod_id, author_id, name, version_number, changelog, changelog_url, date_published, - downloads, version_type, featured + downloads, version_type, featured, status ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, - $10, $11 + $10, $11, $12 ) ", self.id as VersionId, @@ -280,7 +288,8 @@ impl Version { self.date_published, self.downloads, &self.version_type, - self.featured + self.featured, + self.status.as_str() ) .execute(&mut *transaction) .await?; @@ -361,37 +370,6 @@ impl Version { .execute(&mut *transaction) .await?; - let files = sqlx::query!( - " - SELECT files.id, files.url, files.filename, files.is_primary FROM files - WHERE files.version_id = $1 - ", - id as VersionId, - ) - .fetch_many(&mut *transaction) - .try_filter_map(|e| async { - Ok(e.right().map(|c| VersionFile { - id: FileId(c.id), - version_id: id, - url: c.url, - filename: c.filename, - primary: c.is_primary, - })) - }) - .try_collect::>() - .await?; - - for file in files { - // TODO: store backblaze id in database so that we can delete the files here - // For now, we can't delete the files since we don't have the backblaze id - log::warn!( - "Can't delete version file id: {} (url: {}, name: {})", - file.id.0, - file.url, - file.filename - ) - } - sqlx::query!( " DELETE FROM hashes @@ -531,7 +509,7 @@ impl Version { " SELECT v.mod_id, v.author_id, v.name, v.version_number, v.changelog, v.changelog_url, v.date_published, v.downloads, - v.version_type, v.featured + v.version_type, v.featured, v.status, v.requested_status FROM versions v WHERE v.id = $1 ", @@ -553,6 +531,10 @@ impl Version { downloads: row.downloads, version_type: row.version_type, featured: row.featured, + status: VersionStatus::from_str(&row.status), + requested_status: row + .requested_status + .map(|x| VersionStatus::from_str(&x)), })) } else { Ok(None) @@ -574,7 +556,7 @@ impl Version { " SELECT v.id, v.mod_id, v.author_id, v.name, v.version_number, v.changelog, v.changelog_url, v.date_published, v.downloads, - v.version_type, v.featured + v.version_type, v.featured, v.status, v.requested_status FROM versions v WHERE v.id = ANY($1) ORDER BY v.date_published ASC @@ -595,6 +577,10 @@ impl Version { downloads: v.downloads, featured: v.featured, version_type: v.version_type, + status: VersionStatus::from_str(&v.status), + requested_status: v + .requested_status + .map(|x| VersionStatus::from_str(&x)), })) }) .try_collect::>() @@ -614,7 +600,7 @@ impl Version { " 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, + v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders, ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files, ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes, @@ -637,15 +623,23 @@ impl Version { if let Some(v) = result { Ok(Some(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, + inner: Version { + 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, + version_type: v.version_type, + featured: v.featured, + status: VersionStatus::from_str(&v.status), + requested_status: v + .requested_status + .map(|x| VersionStatus::from_str(&x)), + }, files: { let hashes: Vec<(FileId, String, Vec)> = v .hashes @@ -737,7 +731,6 @@ impl Version { gv.into_iter().map(|x| x.0).collect() }, loaders: v.loaders.unwrap_or_default(), - featured: v.featured, dependencies: v .dependencies .unwrap_or_default() @@ -773,7 +766,6 @@ impl Version { } }) .collect(), - version_type: v.version_type, })) } else { Ok(None) @@ -795,7 +787,7 @@ impl Version { " 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, + v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, ARRAY_AGG(DISTINCT gv.version || ' |||| ' || gv.created) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders, ARRAY_AGG(DISTINCT f.id || ' |||| ' || f.is_primary || ' |||| ' || f.size || ' |||| ' || f.url || ' |||| ' || f.filename) filter (where f.id is not null) files, ARRAY_AGG(DISTINCT h.algorithm || ' |||| ' || encode(h.hash, 'escape') || ' |||| ' || h.file_id) filter (where h.hash is not null) hashes, @@ -818,15 +810,22 @@ impl Version { .try_filter_map(|e| async { Ok(e.right().map(|v| 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, + inner: Version { + 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, + version_type: v.version_type, + featured: v.featured, + status: VersionStatus::from_str(&v.status), + requested_status: v.requested_status + .map(|x| VersionStatus::from_str(&x)), + }, files: { let hashes: Vec<(FileId, String, Vec)> = v.hashes.unwrap_or_default() .into_iter() @@ -909,7 +908,6 @@ impl Version { .collect() }, loaders: v.loaders.unwrap_or_default(), - featured: v.featured, dependencies: v.dependencies .unwrap_or_default() .into_iter() @@ -944,7 +942,6 @@ impl Version { None } }).collect(), - version_type: v.version_type } )) }) @@ -953,37 +950,13 @@ impl Version { } } -pub struct VersionFile { - pub id: FileId, - pub version_id: VersionId, - pub url: String, - pub filename: String, - pub primary: bool, -} - -pub struct FileHash { - pub file_id: FileId, - pub algorithm: String, - pub hash: Vec, -} - #[derive(Clone)] pub struct QueryVersion { - pub id: VersionId, - pub project_id: ProjectId, - pub author_id: UserId, - pub name: String, - pub version_number: String, - pub changelog: String, - pub changelog_url: Option, - pub date_published: DateTime, - pub downloads: i32, + pub inner: Version, - pub version_type: String, pub files: Vec, pub game_versions: Vec, pub loaders: Vec, - pub featured: bool, pub dependencies: Vec, } diff --git a/src/main.rs b/src/main.rs index a334221f8..eb756cf46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,7 @@ async fn main() -> std::io::Result<()> { } }); + // Deleting old authentication states from the database every 15 minutes let pool_ref = pool.clone(); scheduler.run(std::time::Duration::from_secs(15 * 60), move || { let pool_ref = pool_ref.clone(); @@ -148,6 +149,53 @@ async fn main() -> std::io::Result<()> { } }); + // Changes statuses of scheduled projects/versions + let pool_ref = pool.clone(); + scheduler.run(std::time::Duration::from_secs(60), move || { + let pool_ref = pool_ref.clone(); + info!("Releasing scheduled versions/projects!"); + + async move { + let projects_results = sqlx::query!( + " + UPDATE mods + SET status = requested_status + WHERE status = $1 AND approved < CURRENT_DATE AND requested_status IS NOT NULL + ", + crate::models::projects::ProjectStatus::Scheduled.as_str(), + ) + .execute(&pool_ref) + .await; + + if let Err(e) = projects_results { + warn!( + "Syncing scheduled releases for projects failed: {:?}", + e + ); + } + + let versions_results = sqlx::query!( + " + UPDATE versions + SET status = requested_status + WHERE status = $1 AND date_published < CURRENT_DATE AND requested_status IS NOT NULL + ", + crate::models::projects::VersionStatus::Scheduled.as_str(), + ) + .execute(&pool_ref) + .await; + + if let Err(e) = versions_results { + warn!( + "Syncing scheduled releases for versions failed: {:?}", + e + ); + } + + info!("Finished releasing scheduled versions/projects"); + } + }); + scheduler::schedule_versions(&mut scheduler, pool.clone()); let download_queue = Arc::new(DownloadQueue::new()); @@ -168,13 +216,12 @@ async fn main() -> std::io::Result<()> { } }); - let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new())); - let ip_salt = Pepper { pepper: models::ids::Base62Id(models::ids::random_base62(11)) .to_string(), }; + let payouts_queue = Arc::new(Mutex::new(PayoutsQueue::new())); let flame_anvil_queue = Arc::new(Mutex::new(FlameAnvilQueue::new())); let store = MemoryStore::new(); diff --git a/src/models/projects.rs b/src/models/projects.rs index 614bde272..a9b1fdcbf 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -51,6 +51,9 @@ pub struct Project { /// The status of the project pub status: ProjectStatus, + /// The requested status of this projct + pub requested_status: Option, + /// The rejection data of the project pub moderator_message: Option, @@ -111,7 +114,8 @@ impl From for Project { published: m.published, updated: m.updated, approved: m.approved, - status: data.status, + status: m.status, + requested_status: m.requested_status, moderator_message: if let Some(message) = m.moderation_message { Some(ModeratorMessage { message, @@ -122,7 +126,7 @@ impl From for Project { }, license: License { id: m.license.clone(), - name: match spdx::Expression::parse(&*m.license) { + name: match spdx::Expression::parse(&m.license) { Ok(spdx_expr) => { let mut vec: Vec<&str> = Vec::new(); for node in spdx_expr.iter() { @@ -256,8 +260,11 @@ pub struct DonationLink { /// Rejected - Project is not displayed on search, and not accessible by URL (Temporary state, project can reapply) /// Draft - Project is not displayed on search, and not accessible by URL /// Unlisted - Project is not displayed on search, but accessible by URL +/// Withheld - Same as unlisted, but set by a moderator. Cannot be switched to another type without moderator approval /// Processing - Project is not displayed on search, and not accessible by URL (Temporary state, project under review) -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] +/// Scheduled - Project is scheduled to be released in the future +/// Private - Project is approved, but is not viewable to the public +#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)] #[serde(rename_all = "lowercase")] pub enum ProjectStatus { Approved, @@ -266,6 +273,9 @@ pub enum ProjectStatus { Draft, Unlisted, Processing, + Withheld, + Scheduled, + Private, Unknown, } @@ -284,6 +294,8 @@ impl ProjectStatus { "draft" => ProjectStatus::Draft, "unlisted" => ProjectStatus::Unlisted, "archived" => ProjectStatus::Archived, + "withheld" => ProjectStatus::Withheld, + "private" => ProjectStatus::Private, _ => ProjectStatus::Unknown, } } @@ -296,23 +308,77 @@ impl ProjectStatus { ProjectStatus::Processing => "processing", ProjectStatus::Unknown => "unknown", ProjectStatus::Archived => "archived", + ProjectStatus::Withheld => "withheld", + ProjectStatus::Scheduled => "scheduled", + ProjectStatus::Private => "private", } } + pub fn iterator() -> impl Iterator { + [ + ProjectStatus::Approved, + ProjectStatus::Archived, + ProjectStatus::Rejected, + ProjectStatus::Draft, + ProjectStatus::Unlisted, + ProjectStatus::Processing, + ProjectStatus::Withheld, + ProjectStatus::Scheduled, + ProjectStatus::Private, + ProjectStatus::Unknown, + ] + .iter() + .copied() + } + + // Project pages + info cannot be viewed pub fn is_hidden(&self) -> bool { match self { - ProjectStatus::Approved => false, ProjectStatus::Rejected => true, ProjectStatus::Draft => true, - ProjectStatus::Unlisted => false, ProjectStatus::Processing => true, ProjectStatus::Unknown => true, + ProjectStatus::Scheduled => true, + ProjectStatus::Private => true, + + ProjectStatus::Approved => false, + ProjectStatus::Unlisted => false, ProjectStatus::Archived => false, + ProjectStatus::Withheld => false, } } + // Project can be displayed in search pub fn is_searchable(&self) -> bool { - matches!(self, ProjectStatus::Approved) + matches!(self, ProjectStatus::Approved | ProjectStatus::Archived) + } + + // Project is "Approved" by moderators + pub fn is_approved(&self) -> bool { + matches!( + self, + ProjectStatus::Approved + | ProjectStatus::Archived + | ProjectStatus::Unlisted + | ProjectStatus::Private + ) + } + + // Project status can be requested after moderator approval + pub fn can_be_requested(&self) -> bool { + match self { + ProjectStatus::Approved => true, + ProjectStatus::Archived => true, + ProjectStatus::Unlisted => true, + ProjectStatus::Private => true, + ProjectStatus::Draft => true, + + ProjectStatus::Rejected => false, + ProjectStatus::Processing => false, + ProjectStatus::Unknown => false, + ProjectStatus::Withheld => false, + ProjectStatus::Scheduled => false, + } } } @@ -343,6 +409,10 @@ pub struct Version { pub downloads: u32, /// The type of the release - `Alpha`, `Beta`, or `Release`. pub version_type: VersionType, + /// The status of tne version + pub status: VersionStatus, + /// The requested status of the version (used for scheduling) + pub requested_status: Option, /// A list of files available for download for this version. pub files: Vec, @@ -356,25 +426,29 @@ pub struct Version { impl From for Version { fn from(data: QueryVersion) -> Version { - Version { - id: data.id.into(), - project_id: data.project_id.into(), - author_id: data.author_id.into(), + let v = data.inner; - featured: data.featured, - name: data.name, - version_number: data.version_number, - changelog: data.changelog, - changelog_url: data.changelog_url, - date_published: data.date_published, - downloads: data.downloads as u32, - version_type: match data.version_type.as_str() { + Version { + id: v.id.into(), + project_id: v.project_id.into(), + author_id: v.author_id.into(), + + featured: v.featured, + name: v.name, + version_number: v.version_number, + changelog: v.changelog, + changelog_url: v.changelog_url, + date_published: v.date_published, + downloads: v.downloads as u32, + version_type: match v.version_type.as_str() { "release" => VersionType::Release, "beta" => VersionType::Beta, "alpha" => VersionType::Alpha, _ => VersionType::Release, }, + status: v.status, + requested_status: v.requested_status, files: data .files .into_iter() @@ -417,6 +491,88 @@ impl From for Version { } } +/// A status decides the visibility of a project in search, URLs, and the whole site itself. +/// Listed - Version is displayed on project, and accessible by URL +/// Draft - Version is not displayed on project, and not accessible by URL +/// Unlisted - Version is not displayed on project, and accessible by URL +/// Scheduled - Version is scheduled to be released in the future +#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Debug)] +#[serde(rename_all = "lowercase")] +pub enum VersionStatus { + Listed, + Draft, + Unlisted, + Scheduled, + Unknown, +} + +impl std::fmt::Display for VersionStatus { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(fmt, "{}", self.as_str()) + } +} + +impl VersionStatus { + pub fn from_str(string: &str) -> VersionStatus { + match string { + "listed" => VersionStatus::Listed, + "draft" => VersionStatus::Draft, + "unlisted" => VersionStatus::Unlisted, + _ => VersionStatus::Unknown, + } + } + pub fn as_str(&self) -> &'static str { + match self { + VersionStatus::Listed => "listed", + VersionStatus::Draft => "draft", + VersionStatus::Unlisted => "unlisted", + VersionStatus::Unknown => "unknown", + VersionStatus::Scheduled => "scheduled", + } + } + + pub fn iterator() -> impl Iterator { + [ + VersionStatus::Listed, + VersionStatus::Draft, + VersionStatus::Unlisted, + VersionStatus::Scheduled, + VersionStatus::Unknown, + ] + .iter() + .copied() + } + + // Version pages + info cannot be viewed + pub fn is_hidden(&self) -> bool { + match self { + VersionStatus::Listed => false, + VersionStatus::Unlisted => false, + + VersionStatus::Draft => true, + VersionStatus::Scheduled => true, + VersionStatus::Unknown => true, + } + } + + // Whether version is listed on project / returned in aggregate routes + pub fn is_listed(&self) -> bool { + matches!(self, VersionStatus::Listed) + } + + // Whether a version status can be requested + pub fn can_be_requested(&self) -> bool { + match self { + VersionStatus::Listed => true, + VersionStatus::Draft => true, + VersionStatus::Unlisted => true, + VersionStatus::Scheduled => false, + + VersionStatus::Unknown => false, + } + } +} + /// A single project file, with a url for the file and the file's hash #[derive(Serialize, Deserialize)] pub struct VersionFile { diff --git a/src/routes/maven.rs b/src/routes/maven.rs index 2f8ef8a13..bf67e7cd6 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -2,7 +2,7 @@ use crate::database::models::project_item::QueryProject; use crate::database::models::version_item::{QueryFile, QueryVersion}; use crate::models::projects::{ProjectId, VersionId}; use crate::routes::ApiError; -use crate::util::auth::get_user_from_headers; +use crate::util::auth::{get_user_from_headers, is_authorized_version}; use crate::{database, util::auth::is_authorized}; use actix_web::{get, route, web, HttpRequest, HttpResponse}; use sqlx::PgPool; @@ -58,12 +58,11 @@ pub async fn maven_metadata( pool: web::Data, ) -> Result { let project_id = params.into_inner().0; - let project_data = - database::models::Project::get_full_from_slug_or_project_id( - &project_id, - &**pool, - ) - .await?; + let project_data = database::models::Project::get_from_slug_or_project_id( + &project_id, + &**pool, + ) + .await?; let data = if let Some(data) = project_data { data @@ -81,10 +80,14 @@ pub async fn maven_metadata( " SELECT id, version_number, version_type FROM versions - WHERE mod_id = $1 + WHERE mod_id = $1 AND status = ANY($2) ORDER BY date_published ASC ", - data.inner.id as database::models::ids::ProjectId + data.id as database::models::ids::ProjectId, + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_listed()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_all(&**pool) .await?; @@ -108,7 +111,7 @@ pub async fn maven_metadata( new_versions.push(value); } - let project_id: ProjectId = data.inner.id.into(); + let project_id: ProjectId = data.id.into(); let respdata = Metadata { group_id: "maven.modrinth".to_string(), @@ -122,7 +125,7 @@ pub async fn maven_metadata( versions: Versions { versions: new_versions, }, - last_updated: data.inner.updated.format("%Y%m%d%H%M%S").to_string(), + last_updated: data.updated.format("%Y%m%d%H%M%S").to_string(), }, }; @@ -149,10 +152,13 @@ fn find_file<'a>( _ => return None, }; - let version_id: VersionId = version.id.into(); + let version_id: VersionId = version.inner.id.into(); if file - == format!("{}-{}.{}", &project_id, &version.version_number, fileext) + == format!( + "{}-{}.{}", + &project_id, &version.inner.version_number, fileext + ) || file == format!("{}-{}.{}", &project_id, &version_id, fileext) { version @@ -191,7 +197,7 @@ pub async fn version_file( let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); - if !is_authorized(&project, &user_option, &pool).await? { + if !is_authorized(&project.inner, &user_option, &pool).await? { return Ok(HttpResponse::NotFound().body("")); } @@ -224,8 +230,12 @@ pub async fn version_file( return Ok(HttpResponse::NotFound().body("")); }; - let version_id: VersionId = version.id.into(); - if file == format!("{}-{}.pom", &project_id, &version.version_number) + if !is_authorized_version(&version.inner, &user_option, &pool).await? { + return Ok(HttpResponse::NotFound().body("")); + } + + let version_id: VersionId = version.inner.id.into(); + if file == format!("{}-{}.pom", &project_id, &version.inner.version_number) || file == format!("{}-{}.pom", &project_id, version_id) { let respdata = MavenPom { @@ -276,7 +286,7 @@ pub async fn version_file_sha1( let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); - if !is_authorized(&project, &user_option, &pool).await? { + if !is_authorized(&project.inner, &user_option, &pool).await? { return Ok(HttpResponse::NotFound().body("")); } @@ -338,7 +348,7 @@ pub async fn version_file_sha512( let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); - if !is_authorized(&project, &user_option, &pool).await? { + if !is_authorized(&project.inner, &user_option, &pool).await? { return Ok(HttpResponse::NotFound().body("")); } @@ -371,6 +381,10 @@ pub async fn version_file_sha512( return Ok(HttpResponse::NotFound().body("")); }; + if !is_authorized_version(&version.inner, &user_option, &pool).await? { + return Ok(HttpResponse::NotFound().body("")); + } + Ok(find_file(&project_id, &project, &version, &file) .and_then(|file| file.hashes.get("sha512")) .and_then(|hash_bytes| std::str::from_utf8(hash_bytes).ok()) diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 6eccca292..54184d299 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -67,6 +67,7 @@ pub fn projects_config(cfg: &mut web::ServiceConfig) { .service(projects::delete_gallery_item) .service(projects::project_follow) .service(projects::project_unfollow) + .service(projects::project_schedule) .service(teams::team_members_get_project) .service( web::scope("{project_id}") @@ -95,7 +96,8 @@ pub fn versions_config(cfg: &mut web::ServiceConfig) { .service(versions::version_get) .service(versions::version_delete) .service(version_creation::upload_file_to_version) - .service(versions::version_edit), + .service(versions::version_edit) + .service(versions::version_schedule), ); cfg.service( web::scope("version_file") diff --git a/src/routes/moderation.rs b/src/routes/moderation.rs index 44a09fe55..d800824a5 100644 --- a/src/routes/moderation.rs +++ b/src/routes/moderation.rs @@ -29,9 +29,7 @@ pub async fn get_projects( let project_ids = sqlx::query!( " SELECT id FROM mods - WHERE status = ( - SELECT id FROM statuses WHERE status = $1 - ) + WHERE status = $1 ORDER BY updated ASC LIMIT $2; ", diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index e010c8cda..42762dc22 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -3,6 +3,7 @@ use crate::file_hosting::{FileHost, FileHostingError}; use crate::models::error::ApiError; use crate::models::projects::{ DonationLink, License, ProjectId, ProjectStatus, SideType, VersionId, + VersionStatus, }; use crate::models::users::UserId; use crate::queue::flameanvil::FlameAnvilQueue; @@ -131,6 +132,10 @@ fn default_project_type() -> String { "mod".to_string() } +fn default_requested_status() -> ProjectStatus { + ProjectStatus::Approved +} + #[derive(Serialize, Deserialize, Validate, Clone)] struct ProjectCreateData { #[validate(length(min = 3, max = 64))] @@ -218,6 +223,9 @@ struct ProjectCreateData { #[validate] /// The multipart names of the gallery items to upload pub gallery_items: Option>, + #[serde(default = "default_requested_status")] + /// The status of the mod to be set once it is approved + pub requested_status: ProjectStatus, } #[derive(Serialize, Deserialize, Validate, Clone)] @@ -658,14 +666,12 @@ pub async fn project_create_inner( } } - let status_id = models::StatusId::get_id(&status, &mut *transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput(format!( - "Status {} does not exist.", - status.clone() - )) - })?; + if !project_create_data.requested_status.can_be_requested() { + return Err(CreateError::InvalidInput(String::from( + "Specified requested status is not allowed to be requested", + ))); + } + let client_side_id = models::SideTypeId::get_id( &project_create_data.client_side, &mut *transaction, @@ -741,7 +747,8 @@ pub async fn project_create_inner( categories, additional_categories, initial_versions: versions, - status: status_id, + status, + requested_status: Some(project_create_data.requested_status), client_side: client_side_id, server_side: server_side_id, license: license_id.to_string(), @@ -774,7 +781,8 @@ pub async fn project_create_inner( published: now, updated: now, approved: None, - status: status.clone(), + status, + requested_status: project_builder.requested_status, moderator_message: None, license: License { id: project_create_data.license_id.clone(), @@ -895,7 +903,9 @@ async fn create_initial_version( game_versions, loaders, featured: version_data.featured, + status: VersionStatus::Listed, version_type: version_data.release_channel.to_string(), + requested_status: None, }; Ok(version) diff --git a/src/routes/projects.rs b/src/routes/projects.rs index fdf969c09..b48409073 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -12,7 +12,7 @@ use crate::util::auth::{get_user_from_headers, is_authorized}; use crate::util::routes::read_from_payload; use crate::util::validate::validation_errors_to_string; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; -use chrono::Utc; +use chrono::{DateTime, Utc}; use futures::StreamExt; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -52,7 +52,7 @@ pub async fn projects_get( let projects: Vec<_> = futures::stream::iter(projects_data) .filter_map(|data| async { - if is_authorized(&data, &user_option, &pool).await.ok()? { + if is_authorized(&data.inner, &user_option, &pool).await.ok()? { Some(Project::from(data)) } else { None @@ -81,7 +81,7 @@ pub async fn project_get( let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); if let Some(data) = project_data { - if is_authorized(&data, &user_option, &pool).await? { + if is_authorized(&data.inner, &user_option, &pool).await? { return Ok(HttpResponse::Ok().json(Project::from(data))); } } @@ -159,7 +159,7 @@ pub async fn dependency_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get_full_from_slug_or_project_id( + let result = database::models::Project::get_from_slug_or_project_id( &string, &**pool, ) .await?; @@ -171,7 +171,7 @@ pub async fn dependency_list( return Ok(HttpResponse::NotFound().body("")); } - let id = project.inner.id; + let id = project.id; use futures::stream::TryStreamExt; @@ -333,6 +333,12 @@ pub struct EditProject { skip_serializing_if = "Option::is_none", with = "::serde_with::rust::double_option" )] + pub requested_status: Option>, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "::serde_with::rust::double_option" + )] #[validate(length(max = 2000))] pub moderation_message: Option>, #[serde( @@ -451,12 +457,11 @@ pub async fn project_edit( )); } - if (status == &ProjectStatus::Rejected - || status == &ProjectStatus::Approved) + if (status.is_approved() || !status.can_be_requested()) && !user.role.is_mod() { return Err(ApiError::CustomAuthentication( - "You don't have permission to set this status" + "You don't have permission to set this status!" .to_string(), )); } @@ -502,20 +507,7 @@ pub async fn project_edit( } } - let status_id = database::models::StatusId::get_id( - status, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput( - "No database entry for status provided.".to_string(), - ) - })?; - - if status == &ProjectStatus::Approved - || status == &ProjectStatus::Unlisted - { + if status.is_approved() { sqlx::query!( " UPDATE mods @@ -534,19 +526,49 @@ pub async fn project_edit( SET status = $1 WHERE (id = $2) ", - status_id as database::models::ids::StatusId, + status.as_str(), id as database::models::ids::ProjectId, ) .execute(&mut *transaction) .await?; - if project_item.status.is_searchable() + if project_item.inner.status.is_searchable() && !status.is_searchable() { delete_from_index(id.into(), config).await?; } } + if let Some(requested_status) = &new_project.requested_status { + if !perms.contains(Permissions::EDIT_DETAILS) { + return Err(ApiError::CustomAuthentication( + "You do not have the permissions to edit the requested status of this project!" + .to_string(), + )); + } + + if !requested_status + .map(|x| x.can_be_requested()) + .unwrap_or(true) + { + return Err(ApiError::InvalidInput(String::from( + "Specified status cannot be requested!", + ))); + } + + sqlx::query!( + " + UPDATE mods + SET requested_status = $1 + WHERE (id = $2) + ", + requested_status.map(|x| x.as_str()), + id as database::models::ids::ProjectId, + ) + .execute(&mut *transaction) + .await?; + } + if perms.contains(Permissions::EDIT_DETAILS) { if new_project.categories.is_some() { sqlx::query!( @@ -863,7 +885,7 @@ pub async fn project_edit( license = models::projects::DEFAULT_LICENSE_ID.to_string(); } - spdx::Expression::parse(&*license).map_err(|err| { + spdx::Expression::parse(&license).map_err(|err| { ApiError::InvalidInput(format!( "Invalid SPDX license identifier: {}", err @@ -931,7 +953,7 @@ pub async fn project_edit( if let Some(moderation_message) = &new_project.moderation_message { if !user.role.is_mod() - && project_item.status != ProjectStatus::Approved + && project_item.inner.status != ProjectStatus::Approved { return Err(ApiError::CustomAuthentication( "You do not have the permissions to edit the moderation message of this project!" @@ -956,7 +978,7 @@ pub async fn project_edit( &new_project.moderation_message_body { if !user.role.is_mod() - && project_item.status != ProjectStatus::Approved + && project_item.inner.status != ProjectStatus::Approved { return Err(ApiError::CustomAuthentication( "You do not have the permissions to edit the moderation message body of this project!" @@ -1096,6 +1118,77 @@ pub async fn project_edit( } } +#[derive(Deserialize)] +pub struct SchedulingData { + pub time: DateTime, + pub requested_status: ProjectStatus, +} + +#[post("{id}/schedule")] +pub async fn project_schedule( + req: HttpRequest, + info: web::Path<(String,)>, + pool: web::Data, + scheduling_data: web::Json, +) -> Result { + let user = get_user_from_headers(req.headers(), &**pool).await?; + + if scheduling_data.time < Utc::now() { + return Err(ApiError::InvalidInput( + "You cannot schedule a project to be released in the past!" + .to_string(), + )); + } + + if !scheduling_data.requested_status.can_be_requested() { + return Err(ApiError::InvalidInput( + "Specified requested status cannot be requested!".to_string(), + )); + } + + let string = info.into_inner().0; + let result = database::models::Project::get_from_slug_or_project_id( + &string, &**pool, + ) + .await?; + + if let Some(project_item) = result { + let team_member = database::models::TeamMember::get_from_user_id( + project_item.team_id, + user.id.into(), + &**pool, + ) + .await?; + + if user.role.is_mod() + || team_member + .map(|x| x.permissions.contains(Permissions::EDIT_DETAILS)) + .unwrap_or(false) + { + return Err(ApiError::CustomAuthentication( + "You do not have permission to edit this project's scheduling data!".to_string(), + )); + } + + sqlx::query!( + " + UPDATE mods + SET status = $1, approved = $2 + WHERE (id = $3) + ", + ProjectStatus::Scheduled.as_str(), + scheduling_data.time, + project_item.id as database::models::ids::ProjectId, + ) + .execute(&**pool) + .await?; + + Ok(HttpResponse::NoContent().body("")) + } else { + Ok(HttpResponse::NotFound().body("")) + } +} + #[derive(Serialize, Deserialize)] pub struct Extension { pub ext: String, diff --git a/src/routes/statistics.rs b/src/routes/statistics.rs index 4cd1f6648..92aaf2c5e 100644 --- a/src/routes/statistics.rs +++ b/src/routes/statistics.rs @@ -11,12 +11,12 @@ pub async fn get_stats( " SELECT COUNT(id) FROM mods - WHERE - status = ( SELECT id FROM statuses WHERE status = $1 ) OR - status = ( SELECT id FROM statuses WHERE status = $2 ) + WHERE status = ANY($1) ", - crate::models::projects::ProjectStatus::Approved.as_str(), - crate::models::projects::ProjectStatus::Archived.as_str() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_searchable()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_one(&**pool); @@ -24,13 +24,17 @@ pub async fn get_stats( " SELECT COUNT(v.id) FROM versions v - INNER JOIN mods m on v.mod_id = m.id - WHERE - status = ( SELECT id FROM statuses WHERE status = $1 ) OR - status = ( SELECT id FROM statuses WHERE status = $2 ) + INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1) + WHERE v.status = ANY($2) ", - crate::models::projects::ProjectStatus::Approved.as_str(), - crate::models::projects::ProjectStatus::Archived.as_str() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_searchable()) + .map(|x| x.to_string()) + .collect::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_listed()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_one(&**pool); @@ -39,27 +43,29 @@ pub async fn get_stats( SELECT COUNT(DISTINCT u.id) FROM users u INNER JOIN team_members tm on u.id = tm.user_id AND tm.accepted = TRUE - INNER JOIN mods m on tm.team_id = m.team_id AND ( - m.status = ( SELECT s.id FROM statuses s WHERE s.status = $1 ) OR - m.status = ( SELECT s.id FROM statuses s WHERE s.status = $2 ) - ) + INNER JOIN mods m on tm.team_id = m.team_id AND m.status = ANY($1) ", - crate::models::projects::ProjectStatus::Approved.as_str(), - crate::models::projects::ProjectStatus::Archived.as_str() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_searchable()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_one(&**pool); let files = sqlx::query!( " SELECT COUNT(f.id) FROM files f - INNER JOIN versions v on f.version_id = v.id - INNER JOIN mods m on v.mod_id = m.id - WHERE - status = ( SELECT id FROM statuses WHERE status = $1 ) OR - status = ( SELECT id FROM statuses WHERE status = $2 ) + INNER JOIN versions v on f.version_id = v.id AND v.status = ANY($2) + INNER JOIN mods m on v.mod_id = m.id AND m.status = ANY($1) ", - crate::models::projects::ProjectStatus::Approved.as_str(), - crate::models::projects::ProjectStatus::Archived.as_str() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_searchable()) + .map(|x| x.to_string()) + .collect::>(), + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_listed()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_one(&**pool); diff --git a/src/routes/tags.rs b/src/routes/tags.rs index 52af8c922..9dfc6148a 100644 --- a/src/routes/tags.rs +++ b/src/routes/tags.rs @@ -332,13 +332,13 @@ pub async fn license_text( ) -> Result { let license_id = params.into_inner().0; - if license_id == crate::models::projects::DEFAULT_LICENSE_ID.to_string() { + if license_id == *crate::models::projects::DEFAULT_LICENSE_ID { return Ok(HttpResponse::Ok().json(LicenseText { body: "All rights reserved unless explicitly stated.".to_string(), })); } - if let Some(license) = spdx::license_id(&*license_id) { + if let Some(license) = spdx::license_id(&license_id) { return Ok(HttpResponse::Ok().json(LicenseText { body: license.text().to_string(), })); diff --git a/src/routes/updates.rs b/src/routes/updates.rs index d177c40e7..c0a33c027 100644 --- a/src/routes/updates.rs +++ b/src/routes/updates.rs @@ -6,7 +6,10 @@ use sqlx::PgPool; use crate::database; use crate::models::projects::{Version, VersionType}; -use crate::util::auth::{get_user_from_headers, is_authorized}; +use crate::util::auth::{ + get_user_from_headers, is_authorized, is_authorized_version, +}; +use futures::StreamExt; use super::ApiError; @@ -20,11 +23,10 @@ pub async fn forge_updates( let (id,) = info.into_inner(); - let project = database::models::Project::get_full_from_slug_or_project_id( - &id, &**pool, - ) - .await? - .ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?; + let project = + database::models::Project::get_from_slug_or_project_id(&id, &**pool) + .await? + .ok_or_else(|| ApiError::InvalidInput(ERROR.to_string()))?; let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); @@ -33,16 +35,32 @@ pub async fn forge_updates( } let version_ids = database::models::Version::get_project_versions( - project.inner.id, + project.id, None, Some(vec!["forge".to_string()]), &**pool, ) .await?; - let mut versions = + let versions = database::models::Version::get_many_full(version_ids, &**pool).await?; - versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); + + let mut versions = futures::stream::iter(versions) + .filter_map(|data| async { + if is_authorized_version(&data.inner, &user_option, &pool) + .await + .ok()? + { + Some(data) + } else { + None + } + }) + .collect::>() + .await; + + versions + .sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); #[derive(Serialize)] struct ForgeUpdates { diff --git a/src/routes/users.rs b/src/routes/users.rs index cf46d4bbd..8da3f7668 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -1,7 +1,7 @@ use crate::database::models::User; use crate::file_hosting::FileHost; use crate::models::notifications::Notification; -use crate::models::projects::{Project, ProjectStatus}; +use crate::models::projects::Project; use crate::models::users::{ Badges, RecipientType, RecipientWallet, Role, UserId, }; @@ -91,35 +91,23 @@ pub async fn projects_list( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await.ok(); - let id_option = crate::database::models::User::get_id_from_username_or_id( - &info.into_inner().0, - &**pool, - ) - .await?; + let id_option = + User::get_id_from_username_or_id(&info.into_inner().0, &**pool).await?; if let Some(id) = id_option { let user_id: UserId = id.into(); - let project_data = if let Some(current_user) = user { - if current_user.role.is_mod() || current_user.id == user_id { - User::get_projects_private(id, &**pool).await? - } else { - User::get_projects( - id, - ProjectStatus::Approved.as_str(), - &**pool, - ) - .await? - } - } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool) - .await? - }; + let can_view_private = user + .map(|y| y.role.is_mod() || y.id == user_id) + .unwrap_or(false); + + let project_data = User::get_projects(id, &**pool).await?; let response: Vec<_> = crate::database::Project::get_many_full(project_data, &**pool) .await? .into_iter() + .filter(|x| can_view_private || x.inner.status.is_approved()) .map(Project::from) .collect(); diff --git a/src/routes/v1/moderation.rs b/src/routes/v1/moderation.rs index 8557a3972..95ed2f673 100644 --- a/src/routes/v1/moderation.rs +++ b/src/routes/v1/moderation.rs @@ -20,9 +20,7 @@ pub async fn get_mods( let project_ids = sqlx::query!( " SELECT id FROM mods - WHERE status = ( - SELECT id FROM statuses WHERE status = $1 - ) + WHERE status = $1 ORDER BY updated ASC LIMIT $2; ", diff --git a/src/routes/v1/mods.rs b/src/routes/v1/mods.rs index 40378eb06..d331918ff 100644 --- a/src/routes/v1/mods.rs +++ b/src/routes/v1/mods.rs @@ -108,7 +108,7 @@ pub async fn mods_get( // can't use `map` and `collect` here since `is_authorized` must be async for proj in projects_data { - if is_authorized(&proj, &user_option, &pool).await? { + if is_authorized(&proj.inner, &user_option, &pool).await? { projects.push(crate::models::projects::Project::from(proj)) } } diff --git a/src/routes/v1/users.rs b/src/routes/v1/users.rs index df11e65ca..2f1a0b1f4 100644 --- a/src/routes/v1/users.rs +++ b/src/routes/v1/users.rs @@ -1,6 +1,6 @@ use crate::database::models::User; use crate::models::ids::UserId; -use crate::models::projects::{ProjectId, ProjectStatus}; +use crate::models::projects::ProjectId; use crate::routes::ApiError; use crate::util::auth::get_user_from_headers; use actix_web::web; @@ -24,26 +24,19 @@ pub async fn mods_list( if let Some(id) = id_option { let user_id: UserId = id.into(); - let project_data = if let Some(current_user) = user { - if current_user.role.is_mod() || current_user.id == user_id { - User::get_projects_private(id, &**pool).await? - } else { - User::get_projects( - id, - ProjectStatus::Approved.as_str(), - &**pool, - ) - .await? - } - } else { - User::get_projects(id, ProjectStatus::Approved.as_str(), &**pool) - .await? - }; + let can_view_private = user + .map(|y| y.role.is_mod() || y.id == user_id) + .unwrap_or(false); - let response = project_data - .into_iter() - .map(|v| v.into()) - .collect::>(); + let project_data = User::get_projects(id, &**pool).await?; + + let response: Vec<_> = + crate::database::Project::get_many(project_data, &**pool) + .await? + .into_iter() + .filter(|x| can_view_private || x.status.is_approved()) + .map(|x| x.id.into()) + .collect::>(); Ok(HttpResponse::Ok().json(response)) } else { diff --git a/src/routes/v1/versions.rs b/src/routes/v1/versions.rs index 8bfff3193..461fc68dd 100644 --- a/src/routes/v1/versions.rs +++ b/src/routes/v1/versions.rs @@ -92,14 +92,16 @@ pub async fn version_list( .filter(|version| { filters .featured - .map(|featured| featured == version.featured) + .map(|featured| featured == version.inner.featured) .unwrap_or(true) }) .map(Version::from) .map(convert_to_legacy) .collect::>(); - versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); + versions.sort_by(|a, b| { + b.inner.date_published.cmp(&a.inner.date_published) + }); // Attempt to populate versions with "auto featured" versions if response.is_empty() diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index b8da9ed4f..d6de13a49 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -7,7 +7,7 @@ use crate::file_hosting::FileHost; use crate::models::pack::PackFileHash; use crate::models::projects::{ Dependency, DependencyType, GameVersion, Loader, ProjectId, Version, - VersionFile, VersionId, VersionType, + VersionFile, VersionId, VersionStatus, VersionType, }; use crate::models::teams::Permissions; use crate::queue::flameanvil::{FlameAnvilQueue, UploadFile}; @@ -59,6 +59,7 @@ pub struct InitialVersionData { pub loaders: Vec, pub featured: bool, pub primary_file: Option, + pub status: VersionStatus, } #[derive(Serialize, Deserialize, Clone)] @@ -160,6 +161,12 @@ async fn version_create_inner( )) })?; + if !version_create_data.status.can_be_requested() { + return Err(CreateError::InvalidInput( + "Status specified cannot be requested".to_string(), + )); + } + let project_id: models::ProjectId = version_create_data.project_id.unwrap().into(); @@ -274,6 +281,8 @@ async fn version_create_inner( loaders, version_type: version_create_data.release_channel.to_string(), featured: version_create_data.featured, + status: version_create_data.status, + requested_status: None, }); continue; @@ -415,6 +424,8 @@ async fn version_create_inner( date_published: Utc::now(), downloads: 0, version_type: version_data.release_channel, + status: builder.status, + requested_status: builder.requested_status, files: builder .files .iter() @@ -449,8 +460,6 @@ async fn version_create_inner( Ok(HttpResponse::Ok().json(response)) } -// TODO: file deletion, listing, etc - // under /api/v1/version/{version_id} #[post("{version_id}/file")] pub async fn upload_file_to_version( @@ -551,7 +560,7 @@ async fn upload_file_to_version_inner( )); } - let project_id = ProjectId(version.project_id.0 as u64); + let project_id = ProjectId(version.inner.project_id.0 as u64); let project_type = sqlx::query!( " @@ -559,7 +568,7 @@ async fn upload_file_to_version_inner( INNER JOIN mods ON mods.project_type = pt.id WHERE mods.id = $1 ", - version.project_id as models::ProjectId, + version.inner.project_id as models::ProjectId, ) .fetch_one(&mut *transaction) .await? @@ -628,9 +637,9 @@ async fn upload_file_to_version_inner( all_game_versions.clone(), true, false, - version.name.clone(), - version.changelog.clone(), - version.version_type.clone(), + version.inner.name.clone(), + version.inner.changelog.clone(), + version.inner.version_type.clone(), flame_anvil_queue, None, None, diff --git a/src/routes/version_file.rs b/src/routes/version_file.rs index 691caab40..ce0cee01f 100644 --- a/src/routes/version_file.rs +++ b/src/routes/version_file.rs @@ -35,14 +35,20 @@ pub async fn get_version_from_hash( SELECT f.version_id version_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v on f.version_id = v.id + INNER JOIN versions v on f.version_id = v.id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hash.as_bytes(), algorithm.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_optional(&**pool) .await?; @@ -83,14 +89,14 @@ pub async fn download_version( " SELECT f.url url, f.id id, f.version_id version_id, v.mod_id project_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v ON v.id = f.version_id + INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), hash.as_bytes(), algorithm.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), ) .fetch_optional(&mut *transaction) .await?; @@ -234,14 +240,20 @@ pub async fn get_update_from_hash( " SELECT v.mod_id project_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v ON v.id = f.version_id + INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = $1 AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = $2 AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), hash.as_bytes(), algorithm.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator() + .filter(|x| x.is_hidden()) + .map(|x| x.to_string()) + .collect::>(), ) .fetch_optional(&**pool) .await?; @@ -306,14 +318,14 @@ pub async fn get_versions_from_hashes( " SELECT h.hash hash, h.algorithm algorithm, f.version_id version_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v ON v.id = f.version_id + INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), hashes_parsed.as_slice(), file_data.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), ) .fetch_all(&**pool) .await?; @@ -333,7 +345,7 @@ pub async fn get_versions_from_hashes( versions_data .clone() .into_iter() - .find(|x| x.id.0 == row.version_id) + .find(|x| x.inner.id.0 == row.version_id) .map(|v| { if let Ok(parsed_hash) = String::from_utf8(row.hash) { Ok(( @@ -369,14 +381,14 @@ pub async fn download_files( " SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v ON v.id = f.version_id + INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), hashes_parsed.as_slice(), file_data.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), ) .fetch_all(&mut *transaction) .await?; @@ -423,14 +435,14 @@ pub async fn update_files( " SELECT f.url url, h.hash hash, h.algorithm algorithm, f.version_id version_id, v.mod_id project_id FROM hashes h INNER JOIN files f ON h.file_id = f.id - INNER JOIN versions v ON v.id = f.version_id + INNER JOIN versions v ON v.id = f.version_id AND v.status != ANY($1) INNER JOIN mods m on v.mod_id = m.id - INNER JOIN statuses s on m.status = s.id - WHERE h.algorithm = $2 AND h.hash = ANY($1::bytea[]) AND s.status != $3 + WHERE h.algorithm = $3 AND h.hash = ANY($2::bytea[]) AND m.status != ANY($4) ", + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), hashes_parsed.as_slice(), update_data.algorithm, - models::projects::ProjectStatus::Rejected.to_string() + &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), ) .fetch_all(&mut *transaction) .await?; @@ -482,7 +494,7 @@ pub async fn update_files( let mut response = HashMap::new(); for version in versions { - let hash = version_ids.get(&version.id); + let hash = version_ids.get(&version.inner.id); if let Some(hash) = hash { if let Ok(parsed_hash) = String::from_utf8(hash.clone()) { @@ -491,7 +503,8 @@ pub async fn update_files( models::projects::Version::from(version), ); } else { - let version_id: models::projects::VersionId = version.id.into(); + let version_id: models::projects::VersionId = + version.inner.id.into(); return Err(ApiError::Database(DatabaseError::Other(format!( "Could not parse hash for version {}", diff --git a/src/routes/versions.rs b/src/routes/versions.rs index 9bdb71a63..bdbe815a3 100644 --- a/src/routes/versions.rs +++ b/src/routes/versions.rs @@ -1,11 +1,15 @@ use super::ApiError; use crate::database; use crate::models; -use crate::models::projects::{Dependency, Version}; +use crate::models::projects::{Dependency, Version, VersionStatus}; use crate::models::teams::Permissions; -use crate::util::auth::{get_user_from_headers, is_authorized}; +use crate::util::auth::{ + get_user_from_headers, is_authorized, is_authorized_version, +}; use crate::util::validate::validation_errors_to_string; -use actix_web::{delete, get, patch, web, HttpRequest, HttpResponse}; +use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; +use chrono::{DateTime, Utc}; +use futures::StreamExt; use serde::{Deserialize, Serialize}; use sqlx::PgPool; use validator::Validate; @@ -26,7 +30,7 @@ pub async fn version_list( ) -> Result { let string = info.into_inner().0; - let result = database::models::Project::get_full_from_slug_or_project_id( + let result = database::models::Project::get_from_slug_or_project_id( &string, &**pool, ) .await?; @@ -38,7 +42,7 @@ pub async fn version_list( return Ok(HttpResponse::NotFound().body("")); } - let id = project.inner.id; + let id = project.id; let version_ids = database::models::Version::get_project_versions( id, @@ -58,19 +62,27 @@ pub async fn version_list( database::models::Version::get_many_full(version_ids, &**pool) .await?; - let mut response = versions - .iter() - .cloned() - .filter(|version| { - filters - .featured - .map(|featured| featured == version.featured) - .unwrap_or(true) + let mut response = futures::stream::iter(versions.clone()) + .filter_map(|data| async { + if is_authorized_version(&data.inner, &user_option, &pool) + .await + .ok()? + && filters + .featured + .map(|featured| featured == data.inner.featured) + .unwrap_or(true) + { + Some(Version::from(data)) + } else { + None + } }) - .map(Version::from) - .collect::>(); + .collect::>() + .await; - versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); + versions.sort_by(|a, b| { + b.inner.date_published.cmp(&a.inner.date_published) + }); // Attempt to populate versions with "auto featured" versions if response.is_empty() @@ -131,6 +143,7 @@ pub struct VersionIds { #[get("versions")] pub async fn versions_get( + req: HttpRequest, web::Query(ids): web::Query, pool: web::Data, ) -> Result { @@ -142,15 +155,28 @@ pub async fn versions_get( let versions_data = database::models::Version::get_many_full(version_ids, &**pool).await?; - let versions = versions_data - .into_iter() - .map(Version::from) - .collect::>(); + let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); + + let versions: Vec<_> = futures::stream::iter(versions_data) + .filter_map(|data| async { + if is_authorized_version(&data.inner, &user_option, &pool) + .await + .ok()? + { + Some(Version::from(data)) + } else { + None + } + }) + .collect() + .await; + Ok(HttpResponse::Ok().json(versions)) } #[get("{version_id}")] pub async fn version_get( + req: HttpRequest, info: web::Path<(models::ids::VersionId,)>, pool: web::Data, ) -> Result { @@ -158,11 +184,17 @@ pub async fn version_get( let version_data = database::models::Version::get_full(id.into(), &**pool).await?; + let user_option = get_user_from_headers(req.headers(), &**pool).await.ok(); + if let Some(data) = version_data { - Ok(HttpResponse::Ok().json(models::projects::Version::from(data))) - } else { - Ok(HttpResponse::NotFound().body("")) + if is_authorized_version(&data.inner, &user_option, &pool).await? { + return Ok( + HttpResponse::Ok().json(models::projects::Version::from(data)) + ); + } } + + Ok(HttpResponse::NotFound().body("")) } #[derive(Serialize, Deserialize, Validate)] @@ -187,6 +219,7 @@ pub struct EditVersion { pub featured: Option, pub primary_file: Option<(String, String)>, pub downloads: Option, + pub status: Option, } #[patch("{id}")] @@ -209,14 +242,14 @@ pub async fn version_edit( if let Some(version_item) = result { let project_item = database::models::Project::get_full( - version_item.project_id, + version_item.inner.project_id, &**pool, ) .await?; let team_member = database::models::TeamMember::get_from_user_id_version( - version_item.id, + version_item.inner.id, user.id.into(), &**pool, ) @@ -310,7 +343,7 @@ pub async fn version_edit( for dependency in builders { dependency - .insert(version_item.id, &mut transaction) + .insert(version_item.inner.id, &mut transaction) .await?; } } @@ -481,7 +514,7 @@ pub async fn version_edit( .execute(&mut *transaction) .await?; - let diff = *downloads - (version_item.downloads as u32); + let diff = *downloads - (version_item.inner.downloads as u32); sqlx::query!( " @@ -490,7 +523,28 @@ pub async fn version_edit( WHERE (id = $2) ", diff as i32, - version_item.project_id as database::models::ids::ProjectId, + version_item.inner.project_id + as database::models::ids::ProjectId, + ) + .execute(&mut *transaction) + .await?; + } + + if let Some(status) = &new_version.status { + if !status.can_be_requested() { + return Err(ApiError::InvalidInput( + "The requested status cannot be set!".to_string(), + )); + } + + sqlx::query!( + " + UPDATE versions + SET status = $1 + WHERE (id = $2) + ", + status.as_str(), + id as database::models::ids::VersionId, ) .execute(&mut *transaction) .await?; @@ -508,6 +562,76 @@ pub async fn version_edit( } } +#[derive(Deserialize)] +pub struct SchedulingData { + pub time: DateTime, + pub requested_status: VersionStatus, +} + +#[post("{id}/schedule")] +pub async fn version_schedule( + req: HttpRequest, + info: web::Path<(models::ids::VersionId,)>, + pool: web::Data, + scheduling_data: web::Json, +) -> Result { + let user = get_user_from_headers(req.headers(), &**pool).await?; + + if scheduling_data.time < Utc::now() { + return Err(ApiError::InvalidInput( + "You cannot schedule a version to be released in the past!" + .to_string(), + )); + } + + if !scheduling_data.requested_status.can_be_requested() { + return Err(ApiError::InvalidInput( + "Specified requested status cannot be requested!".to_string(), + )); + } + + let string = info.into_inner().0; + let result = + database::models::Version::get_full(string.into(), &**pool).await?; + + if let Some(version_item) = result { + let team_member = + database::models::TeamMember::get_from_user_id_version( + version_item.inner.id, + user.id.into(), + &**pool, + ) + .await?; + + if user.role.is_mod() + || team_member + .map(|x| x.permissions.contains(Permissions::EDIT_DETAILS)) + .unwrap_or(false) + { + return Err(ApiError::CustomAuthentication( + "You do not have permission to edit this version's scheduling data!".to_string(), + )); + } + + sqlx::query!( + " + UPDATE versions + SET status = $1, date_published = $2 + WHERE (id = $3) + ", + VersionStatus::Scheduled.as_str(), + scheduling_data.time, + version_item.inner.id as database::models::ids::VersionId, + ) + .execute(&**pool) + .await?; + + Ok(HttpResponse::NoContent().body("")) + } else { + Ok(HttpResponse::NotFound().body("")) + } +} + #[delete("{version_id}")] pub async fn version_delete( req: HttpRequest, diff --git a/src/search/indexing/local_import.rs b/src/search/indexing/local_import.rs index 85ed90e73..c79f91cb3 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -10,13 +10,14 @@ pub async fn index_local( pool: PgPool, ) -> Result, IndexingError> { info!("Indexing local projects!"); + Ok( 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.published published, m.approved approved, m.updated updated, - m.team_id team_id, m.license license, m.slug slug, - s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username, + m.team_id team_id, m.license license, m.slug slug, m.status status_name, + cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders, ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions, @@ -24,23 +25,22 @@ pub async fn index_local( FROM mods m LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id - LEFT OUTER JOIN versions v ON v.mod_id = m.id + LEFT OUTER JOIN versions v ON v.mod_id = m.id AND v.status != ANY($1) LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id - INNER JOIN statuses s ON s.id = m.status INNER JOIN project_types pt ON pt.id = m.project_type INNER JOIN side_types cs ON m.client_side = cs.id INNER JOIN side_types ss ON m.server_side = ss.id INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE INNER JOIN users u ON tm.user_id = u.id - WHERE s.status = $1 OR s.status = $2 - GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id; + WHERE m.status = ANY($2) + GROUP BY m.id, cs.id, ss.id, pt.id, u.id; ", - crate::models::projects::ProjectStatus::Approved.as_str(), - crate::models::projects::ProjectStatus::Archived.as_str(), + &*crate::models::projects::VersionStatus::iterator().filter(|x| x.is_hidden()).map(|x| x.to_string()).collect::>(), + &*crate::models::projects::ProjectStatus::iterator().filter(|x| x.is_searchable()).map(|x| x.to_string()).collect::>(), crate::models::teams::OWNER_ROLE, ) .fetch_many(&pool) @@ -72,7 +72,7 @@ pub async fn index_local( let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into(); - let license = match m.license.split(" ").next() { + let license = match m.license.split(' ').next() { Some(license) => license.to_string(), None => m.license, }; diff --git a/src/util/auth.rs b/src/util/auth.rs index a17738493..7f85f1dfe 100644 --- a/src/util/auth.rs +++ b/src/util/auth.rs @@ -1,6 +1,5 @@ use crate::database; -use crate::database::models; -use crate::database::models::project_item::QueryProject; +use crate::database::{models, Project, Version}; use crate::models::users::{Role, User, UserId, UserPayoutData}; use crate::routes::ApiError; use actix_web::http::header::HeaderMap; @@ -132,7 +131,7 @@ where } pub async fn is_authorized( - project_data: &QueryProject, + project_data: &Project, user_option: &Option, pool: &web::Data, ) -> Result { @@ -147,7 +146,7 @@ pub async fn is_authorized( let project_exists = sqlx::query!( "SELECT EXISTS(SELECT 1 FROM team_members WHERE team_id = $1 AND user_id = $2)", - project_data.inner.team_id as database::models::ids::TeamId, + project_data.team_id as database::models::ids::TeamId, user_id as database::models::ids::UserId, ) .fetch_one(&***pool) @@ -158,5 +157,37 @@ pub async fn is_authorized( } } } + + Ok(authorized) +} + +pub async fn is_authorized_version( + version_data: &Version, + user_option: &Option, + pool: &web::Data, +) -> Result { + let mut authorized = !version_data.status.is_hidden(); + + if let Some(user) = &user_option { + if !authorized { + if user.role.is_mod() { + authorized = true; + } else { + let user_id: models::ids::UserId = user.id.into(); + + let version_exists = sqlx::query!( + "SELECT EXISTS(SELECT 1 FROM mods m INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 WHERE m.id = $1)", + version_data.project_id as database::models::ids::ProjectId, + user_id as database::models::ids::UserId, + ) + .fetch_one(&***pool) + .await? + .exists; + + authorized = version_exists.unwrap_or(false); + } + } + } + Ok(authorized) }