diff --git a/migrations/20210727160151_gallery_featuring_rejection_rename.sql b/migrations/20210727160151_gallery_featuring_rejection_rename.sql new file mode 100644 index 000000000..6945bdd2a --- /dev/null +++ b/migrations/20210727160151_gallery_featuring_rejection_rename.sql @@ -0,0 +1,7 @@ +ALTER TABLE mods + RENAME COLUMN rejection_reason TO moderation_message; +ALTER TABLE mods + RENAME COLUMN rejection_body TO moderation_message_body; + +ALTER TABLE mods_gallery + ADD COLUMN featured boolean default false; \ No newline at end of file diff --git a/sqlx-data.json b/sqlx-data.json index 6c338fd55..cbffc00e4 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -151,6 +151,18 @@ "nullable": [] } }, + "04dcb2565608e296502694efc0c59bc77c41175ef65c830f2fef745773f18c86": { + "query": "\n UPDATE mods\n SET moderation_message_body = NULL\n WHERE (id = $1)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + } + }, "06c2d67bcbc95baa4b7e5865ec9adec7f068c1dfd3f859c29465b8d8a40343e0": { "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\n ", "describe": { @@ -1223,6 +1235,164 @@ "nullable": [] } }, + "3f49cbdd66ccfbb175cf824585a054eab4a0e5c0dfa934a4880ec8b6f3c39ac2": { + "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body\n FROM mods\n WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 12, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 18, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "moderation_message_body", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true + ] + } + }, "413762398111e04074a2d8a1e4e03ed362b9167d397947f8d14e5ae330e3de0b": { "query": "\n UPDATE versions\n SET downloads = downloads + 1\n WHERE id = $1\n ", "describe": { @@ -1596,18 +1766,6 @@ "nullable": [] } }, - "547230de9a5ea9b6ee326e6a35cd1016d67afdda3d7ee382c2f2b7832be875e9": { - "query": "\n UPDATE mods\n SET rejection_body = NULL\n WHERE (id = $1)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - } - }, "5564434408e4b88ff1bdd14e0d32a35136e5ee0c837655fbde7d3ca9182dc25b": { "query": "\n SELECT tm.id, tm.team_id, tm.user_id, tm.role, tm.permissions, tm.accepted FROM mods m\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND user_id = $2 AND accepted = TRUE\n WHERE m.id = $1\n ", "describe": { @@ -1672,6 +1830,18 @@ "nullable": [] } }, + "57a38641fe5bdb273190e8d586f46284340b9ff11b6ae3177923631a37bb11eb": { + "query": "\n UPDATE mods\n SET moderation_message = NULL\n WHERE (id = $1)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + } + }, "57bb3db92e6a8fb8606005be955e2379f13a04f101f91358322a591a860a7f9e": { "query": "\n SELECT id FROM reports\n ORDER BY created ASC\n LIMIT $1;\n ", "describe": { @@ -1692,8 +1862,108 @@ ] } }, - "57c4609659459323d26e5f7d6cb24d1aaea1ca839998c276bcd5d9e9330568a1": { - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published,\n m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions\n 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 INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $2\n INNER JOIN users u ON tm.user_id = u.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n ", + "5a03c653f1ff3339a01422ee4267a66157e6da9a51cc7d9beb0f87d59c3a444c": { + "query": "\n SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n WHERE v.mod_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "dependent_id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "dependency_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "mod_dependency_id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + true, + true + ] + } + }, + "5a13a79ebb1ab975f88b58e6deaba9685fe16e242c0fa4a5eea54f12f9448e6b": { + "query": "\n DELETE FROM reports\n WHERE version_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + } + }, + "5ad1f23da1b6f0f613de3412b928d2677a0359111dab4174e69ef6b0ef78202b": { + "query": "\n SELECT rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created\n FROM reports r\n INNER JOIN report_types rt ON rt.id = r.report_type_id\n WHERE r.id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "mod_id", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "version_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "user_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 5, + "name": "reporter", + "type_info": "Int8" + }, + { + "ordinal": 6, + "name": "created", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + true, + true, + true, + false, + false, + false + ] + } + }, + "5b8ba75c6a3d7a3cb68885e213aa9145cf014ddcd13f3a385fb4c0b9f8f7483a": { + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published,\n m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions,\n STRING_AGG(DISTINCT mg.image_url, ',') gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $2\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n ", "describe": { "columns": [ { @@ -1800,11 +2070,16 @@ "ordinal": 20, "name": "versions", "type_info": "Text" + }, + { + "ordinal": 21, + "name": "gallery", + "type_info": "Text" } ], "parameters": { "Left": [ - "Int8", + "Text", "Text" ] }, @@ -1829,110 +2104,11 @@ false, null, null, + null, null ] } }, - "5a03c653f1ff3339a01422ee4267a66157e6da9a51cc7d9beb0f87d59c3a444c": { - "query": "\n SELECT d.dependent_id, d.dependency_id, d.mod_dependency_id\n FROM versions v\n INNER JOIN dependencies d ON d.dependent_id = v.id\n WHERE v.mod_id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "dependent_id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "dependency_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "mod_dependency_id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - true, - true - ] - } - }, - "5a13a79ebb1ab975f88b58e6deaba9685fe16e242c0fa4a5eea54f12f9448e6b": { - "query": "\n DELETE FROM reports\n WHERE version_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - } - }, - "5ad1f23da1b6f0f613de3412b928d2677a0359111dab4174e69ef6b0ef78202b": { - "query": "\n SELECT rt.name, r.mod_id, r.version_id, r.user_id, r.body, r.reporter, r.created\n FROM reports r\n INNER JOIN report_types rt ON rt.id = r.report_type_id\n WHERE r.id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "mod_id", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "version_id", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "user_id", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "reporter", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "created", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - true, - true, - true, - false, - false, - false - ] - } - }, "5c3b340d278c356b6bc2cd7110e5093a7d1ad982ae0f468f8fff7c54e4e6603a": { "query": "\n SELECT id FROM project_types\n WHERE name = $1\n ", "describe": { @@ -2055,164 +2231,6 @@ ] } }, - "612847a6ba8c88c3f63e5e15f21df7360a0e038baee5b84e6dabaa4a9a1461f7": { - "query": "\n SELECT id, project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n rejection_reason, rejection_body\n FROM mods\n WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "project_type", - "type_info": "Int4" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "follows", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "icon_url", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "body_url", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "published", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "status", - "type_info": "Int4" - }, - { - "ordinal": 12, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "license_url", - "type_info": "Varchar" - }, - { - "ordinal": 17, - "name": "team_id", - "type_info": "Int8" - }, - { - "ordinal": 18, - "name": "client_side", - "type_info": "Int4" - }, - { - "ordinal": 19, - "name": "server_side", - "type_info": "Int4" - }, - { - "ordinal": 20, - "name": "license", - "type_info": "Int4" - }, - { - "ordinal": 21, - "name": "slug", - "type_info": "Varchar" - }, - { - "ordinal": 22, - "name": "rejection_reason", - "type_info": "Varchar" - }, - { - "ordinal": 23, - "name": "rejection_body", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8Array" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true - ] - } - }, "6131d32a65f5e04775308386812f25c6d8464582678536a392a4a3737667f363": { "query": "\n SELECT id, short, name FROM licenses\n ", "describe": { @@ -2996,6 +3014,20 @@ "nullable": [] } }, + "80c4e7603e10b2cb4cf965a5fb2adc66bcae03ba3411289fb1c3768157a11b06": { + "query": "\n INSERT INTO mods_gallery (\n mod_id, image_url, featured\n )\n VALUES (\n $1, $2, $3\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Varchar", + "Bool" + ] + }, + "nullable": [] + } + }, "8129255d25bf0624d83f50558b668ed7b7f9c264e380d276522fc82bc871939b": { "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 ", "describe": { @@ -3011,6 +3043,224 @@ "nullable": [] } }, + "81842199a8f85ba540e626f401b42e718c8b714b8ed2bd204266d78909c7eb65": { + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\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 mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 12, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 18, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "moderation_message_body", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "status_name", + "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "client_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "server_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 27, + "name": "short", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "license_name", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "project_type_name", + "type_info": "Varchar" + }, + { + "ordinal": 30, + "name": "categories", + "type_info": "Text" + }, + { + "ordinal": 31, + "name": "versions", + "type_info": "Text" + }, + { + "ordinal": 32, + "name": "gallery", + "type_info": "Text" + }, + { + "ordinal": 33, + "name": "donations", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8Array" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + null, + null, + null, + null + ] + } + }, "82515e4e7e88f1193c956f032caabc70f535f925e212de30f974afd3ec126092": { "query": "\n INSERT INTO licenses (short, name)\n VALUES ($1, $2)\n ON CONFLICT (short) DO NOTHING\n RETURNING id\n ", "describe": { @@ -3083,147 +3333,6 @@ ] } }, - "86bc6fc06bc768cf5071cb9d5131c1f32a83e369bb096d759c60841ca6e68eb8": { - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published,\n m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions\n 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 INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $2\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "project_type", - "type_info": "Int4" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "follows", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "icon_url", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "published", - "type_info": "Timestamptz" - }, - { - "ordinal": 8, - "name": "updated", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "team_id", - "type_info": "Int8" - }, - { - "ordinal": 10, - "name": "license", - "type_info": "Int4" - }, - { - "ordinal": 11, - "name": "slug", - "type_info": "Varchar" - }, - { - "ordinal": 12, - "name": "status_name", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "client_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "server_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "short", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "project_type_name", - "type_info": "Varchar" - }, - { - "ordinal": 17, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 18, - "name": "categories", - "type_info": "Text" - }, - { - "ordinal": 19, - "name": "loaders", - "type_info": "Text" - }, - { - "ordinal": 20, - "name": "versions", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - false, - false, - false, - true, - false, - false, - false, - false, - false, - false, - null, - null, - null - ] - } - }, "87fd169e19ba231c6cf131ad2841d5c3b95adde53e5ed4000f8e7d54c0e87320": { "query": "\n DELETE FROM project_types\n WHERE name = $1\n ", "describe": { @@ -3280,6 +3389,158 @@ ] } }, + "8b1dcd460baa345311275710a0c05c0c58764b91d50b3d6732b3dce8bf5bff6b": { + "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n moderation_message, moderation_message_body\n FROM mods\n WHERE id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 4, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 9, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 11, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 12, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 17, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 18, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message_body", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true + ] + } + }, "8ba2b2c38958f1c542e514fc62ab4682f58b0b442ac1842d20625420698e34ec": { "query": "\n DELETE FROM team_members\n WHERE (team_id = $1 AND user_id = $2 AND NOT role = $3)\n ", "describe": { @@ -3775,170 +4036,6 @@ "nullable": [] } }, - "b143e2172d3478546537393290a9f4d7da275af673aefaea5499270df6fd11b2": { - "query": "\n UPDATE mods\n SET rejection_reason = NULL\n WHERE (id = $1)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - } - }, - "b18c41ba7edb7d1f12316642e26d56b0c6d2737569a0a6bf53e878312b565982": { - "query": "\n SELECT project_type, title, description, downloads, follows,\n icon_url, body, body_url, published,\n updated, status,\n issues_url, source_url, wiki_url, discord_url, license_url,\n team_id, client_side, server_side, license, slug,\n rejection_reason, rejection_body\n FROM mods\n WHERE id = $1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "project_type", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "follows", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "icon_url", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "body_url", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "published", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "updated", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "status", - "type_info": "Int4" - }, - { - "ordinal": 11, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 12, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "license_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "team_id", - "type_info": "Int8" - }, - { - "ordinal": 17, - "name": "client_side", - "type_info": "Int4" - }, - { - "ordinal": 18, - "name": "server_side", - "type_info": "Int4" - }, - { - "ordinal": 19, - "name": "license", - "type_info": "Int4" - }, - { - "ordinal": 20, - "name": "slug", - "type_info": "Varchar" - }, - { - "ordinal": 21, - "name": "rejection_reason", - "type_info": "Varchar" - }, - { - "ordinal": 22, - "name": "rejection_body", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true - ] - } - }, "b2a4fabfca61da6816a68b4508132b463bff7f3748fdd8e75589be9611fa1229": { "query": "\n UPDATE dependencies\n SET dependency_id = $2\n WHERE id IN (SELECT * FROM UNNEST($1::bigint[]))\n ", "describe": { @@ -4232,19 +4329,6 @@ "nullable": [] } }, - "bf67dcb3ced403fb998737ca4a30f0bcefc34a3102ff0ba908f95b555e180f8c": { - "query": "\n UPDATE mods\n SET rejection_reason = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - } - }, "bf7f721664f5e0ed41adc41b5483037256635f28ff6c4e5d3cbcec4387f9c8ef": { "query": "SELECT EXISTS(SELECT 1 FROM users WHERE id=$1)", "describe": { @@ -4790,6 +4874,19 @@ ] } }, + "d331ca8f22da418cf654985c822ce4466824beaa00dea64cde90dc651a03024b": { + "query": "\n UPDATE mods\n SET moderation_message = $1\n WHERE (id = $2)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + }, + "nullable": [] + } + }, "d5b00d6237b04018822db529995f0b001cd1cabf5ca93b4aff37f12c4feb83f6": { "query": "\n INSERT INTO donation_platforms (short, name)\n VALUES ($1, $2)\n ON CONFLICT (short) DO NOTHING\n RETURNING id\n ", "describe": { @@ -4811,19 +4908,6 @@ ] } }, - "d63386fcc792df45e34001563dd22f78d65f567119f306734c32a01405af703d": { - "query": "\n UPDATE mods\n SET rejection_body = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - } - }, "d6453e50041b5521fa9e919a9162e533bb9426f8c584d98474c6ad414db715c8": { "query": "SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)", "describe": { @@ -5007,8 +5091,8 @@ "nullable": [] } }, - "dc73655baea98436ddd0e266c7b123a9d240d4c1cb9d98875045e2235d772ab6": { - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\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 mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id IN (SELECT * FROM UNNEST($1::bigint[]))\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ", + "dc70fb063947058851923f72ae1618e876c51335c0c6fdb82f097cb0bd68ccd7": { + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published,\n m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions,\n STRING_AGG(DISTINCT mg.image_url, ',') gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $2\n INNER JOIN users u ON tm.user_id = u.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n ", "describe": { "columns": [ { @@ -5048,143 +5132,84 @@ }, { "ordinal": 7, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "body_url", - "type_info": "Varchar" - }, - { - "ordinal": 9, "name": "published", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 8, "name": "updated", "type_info": "Timestamptz" }, { - "ordinal": 11, - "name": "status", - "type_info": "Int4" - }, - { - "ordinal": 12, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "license_url", - "type_info": "Varchar" - }, - { - "ordinal": 17, + "ordinal": 9, "name": "team_id", "type_info": "Int8" }, { - "ordinal": 18, - "name": "client_side", - "type_info": "Int4" - }, - { - "ordinal": 19, - "name": "server_side", - "type_info": "Int4" - }, - { - "ordinal": 20, + "ordinal": 10, "name": "license", "type_info": "Int4" }, { - "ordinal": 21, + "ordinal": 11, "name": "slug", "type_info": "Varchar" }, { - "ordinal": 22, - "name": "rejection_reason", - "type_info": "Varchar" - }, - { - "ordinal": 23, - "name": "rejection_body", - "type_info": "Varchar" - }, - { - "ordinal": 24, + "ordinal": 12, "name": "status_name", "type_info": "Varchar" }, { - "ordinal": 25, + "ordinal": 13, "name": "client_side_type", "type_info": "Varchar" }, { - "ordinal": 26, + "ordinal": 14, "name": "server_side_type", "type_info": "Varchar" }, { - "ordinal": 27, + "ordinal": 15, "name": "short", "type_info": "Varchar" }, { - "ordinal": 28, - "name": "license_name", - "type_info": "Varchar" - }, - { - "ordinal": 29, + "ordinal": 16, "name": "project_type_name", "type_info": "Varchar" }, { - "ordinal": 30, + "ordinal": 17, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 18, "name": "categories", "type_info": "Text" }, { - "ordinal": 31, + "ordinal": 19, + "name": "loaders", + "type_info": "Text" + }, + { + "ordinal": 20, "name": "versions", "type_info": "Text" }, { - "ordinal": 32, + "ordinal": 21, "name": "gallery", "type_info": "Text" - }, - { - "ordinal": 33, - "name": "donations", - "type_info": "Text" } ], "parameters": { "Left": [ - "Int8Array" + "Int8", + "Text" ] }, "nullable": [ @@ -5196,22 +5221,10 @@ false, true, false, - true, false, false, false, true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, false, false, false, @@ -5300,222 +5313,17 @@ "nullable": [] } }, - "e74c46f568e202e4e8cd02935bdfe6bef860e174c1648a9d3d5ef653fc7ac983": { - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\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 mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ", + "e71601dd7b33812e8466a240b2c1a01f029a8426d28580c5cfaab4fd37180877": { + "query": "\n UPDATE mods_gallery\n SET featured = $2\n WHERE id = $1\n ", "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "project_type", - "type_info": "Int4" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "downloads", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "follows", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "icon_url", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "body", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "body_url", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "published", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "updated", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "status", - "type_info": "Int4" - }, - { - "ordinal": 12, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "license_url", - "type_info": "Varchar" - }, - { - "ordinal": 17, - "name": "team_id", - "type_info": "Int8" - }, - { - "ordinal": 18, - "name": "client_side", - "type_info": "Int4" - }, - { - "ordinal": 19, - "name": "server_side", - "type_info": "Int4" - }, - { - "ordinal": 20, - "name": "license", - "type_info": "Int4" - }, - { - "ordinal": 21, - "name": "slug", - "type_info": "Varchar" - }, - { - "ordinal": 22, - "name": "rejection_reason", - "type_info": "Varchar" - }, - { - "ordinal": 23, - "name": "rejection_body", - "type_info": "Varchar" - }, - { - "ordinal": 24, - "name": "status_name", - "type_info": "Varchar" - }, - { - "ordinal": 25, - "name": "client_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 26, - "name": "server_side_type", - "type_info": "Varchar" - }, - { - "ordinal": 27, - "name": "short", - "type_info": "Varchar" - }, - { - "ordinal": 28, - "name": "license_name", - "type_info": "Varchar" - }, - { - "ordinal": 29, - "name": "project_type_name", - "type_info": "Varchar" - }, - { - "ordinal": 30, - "name": "categories", - "type_info": "Text" - }, - { - "ordinal": 31, - "name": "versions", - "type_info": "Text" - }, - { - "ordinal": 32, - "name": "gallery", - "type_info": "Text" - }, - { - "ordinal": 33, - "name": "donations", - "type_info": "Text" - } - ], + "columns": [], "parameters": { "Left": [ - "Int8" + "Int4", + "Bool" ] }, - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - null, - null, - null, - null - ] + "nullable": [] } }, "e7d0a64a08df6783c942f2fcadd94dd45f8d96ad3d3736e52ce90f68d396cdab": { @@ -5635,19 +5443,6 @@ "nullable": [] } }, - "e9a79afea907cec2f6617e325b0b3b80d135ff99149a1516a8cffd4fbbd64e6d": { - "query": "\n INSERT INTO mods_gallery (\n mod_id, image_url\n )\n VALUES (\n $1, $2\n )\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Varchar" - ] - }, - "nullable": [] - } - }, "ea877d50ba461eae97ba3a35c3da71e7cdb7a92de1bb877d6b5dd766aca4e4ef": { "query": "\n SELECT u.id, u.name, u.email,\n u.avatar_url, u.username, u.bio,\n u.created, u.role\n FROM users u\n WHERE u.github_id = $1\n ", "describe": { @@ -5730,6 +5525,19 @@ ] } }, + "ed1d5d9433bc7f4a360431ecfdd9430c5e58cd6d1c623c187d8661200400b1a4": { + "query": "\n UPDATE mods\n SET moderation_message_body = $1\n WHERE (id = $2)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + }, + "nullable": [] + } + }, "ed3e866634135d4f4c8a513eae2856ad71212f6eec09bb4ccef1506912a3a44c": { "query": "\n UPDATE mods\n SET follows = follows + 1\n WHERE id = $1\n ", "describe": { @@ -6089,6 +5897,224 @@ ] } }, + "fd93bafde367994b61f5a5efbae474535357420ee7f631eae95c12dcd4eb66ce": { + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name,\n STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery,\n STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations\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 mods_gallery mg ON mg.mod_id = m.id\n LEFT OUTER JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT OUTER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n WHERE m.id = $1\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id;\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "project_type", + "type_info": "Int4" + }, + { + "ordinal": 2, + "name": "title", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "description", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "downloads", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "follows", + "type_info": "Int4" + }, + { + "ordinal": 6, + "name": "icon_url", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "body", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "body_url", + "type_info": "Varchar" + }, + { + "ordinal": 9, + "name": "published", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "updated", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "status", + "type_info": "Int4" + }, + { + "ordinal": 12, + "name": "issues_url", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "source_url", + "type_info": "Varchar" + }, + { + "ordinal": 14, + "name": "wiki_url", + "type_info": "Varchar" + }, + { + "ordinal": 15, + "name": "discord_url", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "license_url", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "team_id", + "type_info": "Int8" + }, + { + "ordinal": 18, + "name": "client_side", + "type_info": "Int4" + }, + { + "ordinal": 19, + "name": "server_side", + "type_info": "Int4" + }, + { + "ordinal": 20, + "name": "license", + "type_info": "Int4" + }, + { + "ordinal": 21, + "name": "slug", + "type_info": "Varchar" + }, + { + "ordinal": 22, + "name": "moderation_message", + "type_info": "Varchar" + }, + { + "ordinal": 23, + "name": "moderation_message_body", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "status_name", + "type_info": "Varchar" + }, + { + "ordinal": 25, + "name": "client_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 26, + "name": "server_side_type", + "type_info": "Varchar" + }, + { + "ordinal": 27, + "name": "short", + "type_info": "Varchar" + }, + { + "ordinal": 28, + "name": "license_name", + "type_info": "Varchar" + }, + { + "ordinal": 29, + "name": "project_type_name", + "type_info": "Varchar" + }, + { + "ordinal": 30, + "name": "categories", + "type_info": "Text" + }, + { + "ordinal": 31, + "name": "versions", + "type_info": "Text" + }, + { + "ordinal": 32, + "name": "gallery", + "type_info": "Text" + }, + { + "ordinal": 33, + "name": "donations", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + false, + null, + null, + null, + null + ] + } + }, "fe73b6928f13955840e8df248688908fb6d82dd1d35dc803676639a6e0864ed5": { "query": "\n DELETE FROM downloads\n WHERE date < (CURRENT_DATE - INTERVAL '30 minutes ago')\n ", "describe": { diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index 3067bd76a..3ce29aef7 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -38,6 +38,7 @@ impl DonationUrl { pub struct GalleryItem { pub project_id: ProjectId, pub image_url: String, + pub featured: bool, } impl GalleryItem { @@ -48,14 +49,15 @@ impl GalleryItem { sqlx::query!( " INSERT INTO mods_gallery ( - mod_id, image_url + mod_id, image_url, featured ) VALUES ( - $1, $2 + $1, $2, $3 ) ", self.project_id as ProjectId, self.image_url, + self.featured ) .execute(&mut *transaction) .await?; @@ -116,8 +118,8 @@ impl ProjectBuilder { server_side: self.server_side, license: self.license, slug: self.slug, - rejection_reason: None, - rejection_body: None, + moderation_message: None, + moderation_message_body: None, }; project_struct.insert(&mut *transaction).await?; @@ -176,8 +178,8 @@ pub struct Project { pub server_side: SideTypeId, pub license: LicenseId, pub slug: Option, - pub rejection_reason: Option, - pub rejection_body: Option, + pub moderation_message: Option, + pub moderation_message_body: Option, } impl Project { @@ -242,7 +244,7 @@ impl Project { updated, status, issues_url, source_url, wiki_url, discord_url, license_url, team_id, client_side, server_side, license, slug, - rejection_reason, rejection_body + moderation_message, moderation_message_body FROM mods WHERE id = $1 ", @@ -275,8 +277,8 @@ impl Project { slug: row.slug, body: row.body, follows: row.follows, - rejection_reason: row.rejection_reason, - rejection_body: row.rejection_body, + moderation_message: row.moderation_message, + moderation_message_body: row.moderation_message_body, })) } else { Ok(None) @@ -300,7 +302,7 @@ impl Project { updated, status, issues_url, source_url, wiki_url, discord_url, license_url, team_id, client_side, server_side, license, slug, - rejection_reason, rejection_body + moderation_message, moderation_message_body FROM mods WHERE id IN (SELECT * FROM UNNEST($1::bigint[])) ", @@ -331,8 +333,8 @@ impl Project { slug: m.slug, body: m.body, follows: m.follows, - rejection_reason: m.rejection_reason, - rejection_body: m.rejection_body, + moderation_message: m.moderation_message, + moderation_message_body: m.moderation_message_body, })) }) .try_collect::>() @@ -589,9 +591,9 @@ impl Project { m.icon_url icon_url, m.body body, m.body_url body_url, m.published published, m.updated updated, m.status status, m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url, - m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body, + m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body, s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, - STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery, + STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery, STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations FROM mods m LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id @@ -638,22 +640,22 @@ impl Project { slug: m.slug.clone(), body: m.body.clone(), follows: m.follows, - rejection_reason: m.rejection_reason, - rejection_body: m.rejection_body, + moderation_message: m.moderation_message, + moderation_message_body: m.moderation_message_body, }, project_type: m.project_type_name, categories: m .categories - .unwrap_or_default() - .split(',') - .map(|x| x.to_string()) - .collect(), + .map(|x| x.split(',').map(|x| x.to_string()).collect()) + .unwrap_or_default(), versions: m .versions - .unwrap_or_default() - .split(',') - .map(|x| VersionId(x.parse().unwrap_or_default())) - .collect(), + .map(|x| { + x.split(',') + .map(|x| VersionId(x.parse().unwrap_or_default())) + .collect() + }) + .unwrap_or_default(), donation_urls: m .donations .unwrap_or_default() @@ -677,11 +679,22 @@ impl Project { .collect(), gallery_items: m .gallery - .into_iter() - .map(|x| GalleryItem { - project_id: id, - image_url: x, + .unwrap_or_default() + .split(" ,") + .map(|d| { + let strings: Vec<&str> = d.split(", ").collect(); + + if strings.len() >= 2 { + Some(GalleryItem { + project_id: id, + image_url: strings[0].to_string(), + featured: strings[1].parse().unwrap_or(false), + }) + } else { + None + } }) + .flatten() .collect(), status: crate::models::projects::ProjectStatus::from_str(&m.status_name), license_id: m.short, @@ -710,9 +723,9 @@ impl Project { m.icon_url icon_url, m.body body, m.body_url body_url, m.published published, m.updated updated, m.status status, m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url, - m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.rejection_reason rejection_reason, m.rejection_body rejection_body, + m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body, s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, - STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url, ',') gallery, + STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT v.id::text, ',') versions, STRING_AGG(DISTINCT mg.image_url || ', ' || mg.featured, ' ,') gallery, STRING_AGG(DISTINCT md.joining_platform_id || ', ' || md.url || ', ' || dp.short || ', ' || dp.name, ' ,') donations FROM mods m LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id @@ -759,37 +772,52 @@ impl Project { slug: m.slug.clone(), body: m.body.clone(), follows: m.follows, - rejection_reason: m.rejection_reason, - rejection_body: m.rejection_body, + moderation_message: m.moderation_message, + moderation_message_body: m.moderation_message_body, }, project_type: m.project_type_name, - categories: m.categories.unwrap_or_default().split(',').map(|x| x.to_string()).collect(), - versions: m.versions.unwrap_or_default().split(',').map(|x| VersionId(x.parse().unwrap_or_default())).collect(), - donation_urls: m - .donations - .unwrap_or_default() - .split(" ,") - .map(|d| { - let strings: Vec<&str> = d.split(", ").collect(); + categories: m.categories.map(|x| x.split(',').map(|x| x.to_string()).collect()).unwrap_or_default(), + versions: m.versions.map(|x| x.split(',').map(|x| VersionId(x.parse().unwrap_or_default())).collect()).unwrap_or_default(), + gallery_items: m + .gallery + .unwrap_or_default() + .split(" ,") + .map(|d| { + let strings: Vec<&str> = d.split(", ").collect(); - if strings.len() >= 3 { - Some(DonationUrl { - project_id: ProjectId(id), - platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)), - platform_short: strings[2].to_string(), - platform_name: strings[3].to_string(), - url: strings[1].to_string(), - }) - } else { - None - } - }) - .flatten() - .collect(), - gallery_items: m.gallery.iter().map(|x| GalleryItem { - project_id: ProjectId(id), - image_url: x.to_string() - }).collect(), + if strings.len() >= 2 { + Some(GalleryItem { + project_id: ProjectId(id), + image_url: strings[0].to_string(), + featured: strings[1].parse().unwrap_or(false) + }) + } else { + None + } + }) + .flatten() + .collect(), + donation_urls: m + .donations + .unwrap_or_default() + .split(" ,") + .map(|d| { + let strings: Vec<&str> = d.split(", ").collect(); + + if strings.len() >= 3 { + Some(DonationUrl { + project_id: ProjectId(id), + platform_id: DonationPlatformId(strings[0].parse().unwrap_or(0)), + platform_short: strings[2].to_string(), + platform_name: strings[3].to_string(), + url: strings[1].to_string(), + }) + } else { + None + } + }) + .flatten() + .collect(), status: crate::models::projects::ProjectStatus::from_str(&m.status_name), license_id: m.short, license_name: m.license_name, diff --git a/src/database/models/user_item.rs b/src/database/models/user_item.rs index ac3537122..5b363b2b3 100644 --- a/src/database/models/user_item.rs +++ b/src/database/models/user_item.rs @@ -439,13 +439,13 @@ impl User { } pub async fn get_id_from_username_or_id<'a, 'b, E>( - username_or_id: String, + username_or_id: &str, executor: E, ) -> Result, sqlx::error::Error> where E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy, { - let id_option = crate::models::ids::base62_impl::parse_base62(&*username_or_id).ok(); + let id_option = crate::models::ids::base62_impl::parse_base62(username_or_id).ok(); if let Some(id) = id_option { let id = UserId(id as i64); diff --git a/src/models/projects.rs b/src/models/projects.rs index 16c99e278..31263bc62 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -44,7 +44,7 @@ pub struct Project { /// The status of the project pub status: ProjectStatus, /// The rejection data of the project - pub rejection_data: Option, + pub moderator_message: Option, /// The license of this project pub license: License, @@ -77,12 +77,18 @@ pub struct Project { pub donation_urls: Option>, /// A string of URLs to visual content featuring the project - pub gallery: Vec, + pub gallery: Vec, } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct RejectionReason { - pub reason: String, +pub struct GalleryItem { + pub url: String, + pub featured: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ModeratorMessage { + pub message: String, pub body: Option, } diff --git a/src/models/teams.rs b/src/models/teams.rs index 1bed9b9e7..565a44631 100644 --- a/src/models/teams.rs +++ b/src/models/teams.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; pub struct TeamId(pub u64); pub const OWNER_ROLE: &str = "Owner"; +pub const DEFAULT_ROLE: &str = "Member"; // TODO: permissions, role names, etc /// A team of users who control a project diff --git a/src/routes/auth.rs b/src/routes/auth.rs index 401bd637b..892ccf5b9 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -181,19 +181,48 @@ pub async fn auth_callback( None => { let user_id = crate::database::models::generate_user_id(&mut transaction).await?; - User { - id: user_id, - github_id: Some(user.id as i64), - username: user.login, - name: user.name, - email: user.email, - avatar_url: Some(user.avatar_url), - bio: user.bio, - created: Utc::now(), - role: Role::Developer.to_string(), + let mut username_increment: i32 = 0; + let mut username = None; + + while username.is_none() { + let test_username = format!( + "{}{}", + &*user.login, + if username_increment > 0 { + username_increment.to_string() + } else { + "".to_string() + } + ); + + let new_id = crate::database::models::User::get_id_from_username_or_id( + &*test_username, + &**client, + ) + .await?; + + if new_id.is_none() { + username = Some(test_username); + } else { + username_increment += 1; + } + } + + if let Some(username) = username { + User { + id: user_id, + github_id: Some(user.id as i64), + username, + name: user.name, + email: user.email, + avatar_url: Some(user.avatar_url), + bio: user.bio, + created: Utc::now(), + role: Role::Developer.to_string(), + } + .insert(&mut transaction) + .await?; } - .insert(&mut transaction) - .await?; } } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 8dfb66714..7a1d54e9a 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -52,11 +52,18 @@ pub fn projects_config(cfg: &mut web::ServiceConfig) { .service(projects::project_delete) .service(projects::project_edit) .service(projects::project_icon_edit) + .service(projects::delete_project_icon) + .service(projects::add_gallery_item) + .service(projects::edit_gallery_item) + .service(projects::delete_gallery_item) .service(projects::project_follow) .service(projects::project_unfollow) .service(teams::team_members_get_project) - .service(web::scope("{project_id}").service(versions::version_list)) - .service(projects::dependency_list), + .service( + web::scope("{project_id}") + .service(versions::version_list) + .service(projects::dependency_list), + ), ); } @@ -112,6 +119,7 @@ pub fn teams_config(cfg: &mut web::ServiceConfig) { web::scope("team") .service(teams::team_members_get) .service(teams::edit_team_member) + .service(teams::transfer_ownership) .service(teams::add_team_member) .service(teams::join_team) .service(teams::remove_team_member), diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index 1564eeac6..1fa787c40 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -185,7 +185,15 @@ struct ProjectCreateData { #[validate(length(max = 64))] /// The multipart names of the gallery items to upload - pub gallery_items: Option>, + pub gallery_items: Option>, +} + +#[derive(Serialize, Deserialize, Validate, Clone)] +pub struct NewGalleryItem { + /// The name of the multipart item where the gallery media is located + pub item: String, + /// Whether the gallery item should show in search or not + pub featured: bool, } pub struct UploadedFile { @@ -427,27 +435,23 @@ pub async fn project_create_inner( } if let Some(gallery_items) = &project_create_data.gallery_items { - if - gallery_items - .iter() - .find(|x| *x == name) - .is_some() - { + if let Some(item) = gallery_items.iter().find(|x| x.item == name) { let mut data = Vec::new(); while let Some(chunk) = field.next().await { - data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); - } + const FILE_SIZE_CAP: usize = 5 * (1 << 20); - const FILE_SIZE_CAP: usize = 5 * (1 << 20); - - if data.len() >= FILE_SIZE_CAP { - return Err(CreateError::InvalidInput(String::from( - "Gallery image exceeds the maximum of 5MiB.", - ))); + if data.len() >= FILE_SIZE_CAP { + return Err(CreateError::InvalidInput(String::from( + "Gallery image exceeds the maximum of 5MiB.", + ))); + } else { + data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); + } } let hash = sha1::Sha1::from(&data).hexdigest(); - let (_, file_extension) = super::version_creation::get_name_ext(&content_disposition)?; + let (_, file_extension) = + super::version_creation::get_name_ext(&content_disposition)?; let content_type = crate::util::ext::get_image_content_type(file_extension) .ok_or_else(|| CreateError::InvalidIconFormat(file_extension.to_string()))?; @@ -461,7 +465,10 @@ pub async fn project_create_inner( file_name: upload_data.file_name.clone(), }); - gallery_urls.push(format!("{}/{}", cdn_url, url)); + gallery_urls.push(crate::models::projects::GalleryItem { + url, + featured: item.featured, + }); continue; } @@ -628,7 +635,8 @@ pub async fn project_create_inner( .iter() .map(|x| models::project_item::GalleryItem { project_id: project_id.into(), - image_url: x.to_string(), + image_url: x.url.clone(), + featured: x.featured, }) .collect(), }; @@ -647,7 +655,7 @@ pub async fn project_create_inner( published: now, updated: now, status: status.clone(), - rejection_data: None, + moderator_message: None, license: License { id: project_create_data.license_id.clone(), name: "".to_string(), @@ -783,13 +791,13 @@ async fn process_icon_upload( if let Some(content_type) = crate::util::ext::get_image_content_type(file_extension) { let mut data = Vec::new(); while let Some(chunk) = field.next().await { - data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); - } - - if data.len() >= 262144 { - return Err(CreateError::InvalidInput(String::from( - "Icons must be smaller than 256KiB", - ))); + if data.len() >= 262144 { + return Err(CreateError::InvalidInput(String::from( + "Icons must be smaller than 256KiB", + ))); + } else { + data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); + } } let upload_data = file_host diff --git a/src/routes/projects.rs b/src/routes/projects.rs index a3e7e028a..255731d05 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -2,7 +2,8 @@ use crate::database; use crate::file_hosting::FileHost; use crate::models; use crate::models::projects::{ - DonationLink, License, ProjectId, ProjectStatus, RejectionReason, SearchRequest, SideType, + DonationLink, GalleryItem, License, ModeratorMessage, ProjectId, ProjectStatus, SearchRequest, + SideType, }; use crate::models::teams::Permissions; use crate::routes::ApiError; @@ -235,10 +236,10 @@ pub fn convert_project( published: m.published, updated: m.updated, status: data.status, - rejection_data: if let Some(reason) = m.rejection_reason { - Some(RejectionReason { - reason, - body: m.rejection_body, + moderator_message: if let Some(message) = m.moderation_message { + Some(ModeratorMessage { + message, + body: m.moderation_message_body, }) } else { None @@ -272,7 +273,10 @@ pub fn convert_project( gallery: data .gallery_items .into_iter() - .map(|x| x.image_url) + .map(|x| GalleryItem { + url: x.image_url, + featured: x.featured, + }) .collect(), } } @@ -345,14 +349,14 @@ pub struct EditProject { with = "::serde_with::rust::double_option" )] #[validate(length(max = 2000))] - pub rejection_reason: Option>, + pub moderation_message: Option>, #[serde( default, skip_serializing_if = "Option::is_none", with = "::serde_with::rust::double_option" )] #[validate(length(max = 65536))] - pub rejection_body: Option>, + pub moderation_message_body: Option>, } #[patch("{id}")] @@ -465,7 +469,7 @@ pub async fn project_edit( sqlx::query!( " UPDATE mods - SET rejection_reason = NULL + SET moderation_message = NULL WHERE (id = $1) ", id as database::models::ids::ProjectId, @@ -476,7 +480,7 @@ pub async fn project_edit( sqlx::query!( " UPDATE mods - SET rejection_body = NULL + SET moderation_message_body = NULL WHERE (id = $1) ", id as database::models::ids::ProjectId, @@ -841,10 +845,10 @@ pub async fn project_edit( } } - if let Some(rejection_reason) = &new_project.rejection_reason { - if !user.role.is_mod() { + if let Some(moderation_message) = &new_project.moderation_message { + if !user.role.is_mod() && project_item.status != ProjectStatus::Approved { return Err(ApiError::CustomAuthenticationError( - "You do not have the permissions to edit the rejection reason of this project!" + "You do not have the permissions to edit the moderation message of this project!" .to_string(), )); } @@ -852,20 +856,20 @@ pub async fn project_edit( sqlx::query!( " UPDATE mods - SET rejection_reason = $1 + SET moderation_message = $1 WHERE (id = $2) ", - rejection_reason.as_deref(), + moderation_message.as_deref(), id as database::models::ids::ProjectId, ) .execute(&mut *transaction) .await?; } - if let Some(rejection_body) = &new_project.rejection_body { - if !user.role.is_mod() { + if let Some(moderation_message_body) = &new_project.moderation_message_body { + if !user.role.is_mod() && project_item.status != ProjectStatus::Approved { return Err(ApiError::CustomAuthenticationError( - "You do not have the permissions to edit the rejection body of this project!" + "You do not have the permissions to edit the moderation message body of this project!" .to_string(), )); } @@ -873,10 +877,10 @@ pub async fn project_edit( sqlx::query!( " UPDATE mods - SET rejection_body = $1 + SET moderation_message_body = $1 WHERE (id = $2) ", - rejection_body.as_deref(), + moderation_message_body.as_deref(), id as database::models::ids::ProjectId, ) .execute(&mut *transaction) @@ -971,15 +975,17 @@ pub async fn project_icon_edit( let mut bytes = web::BytesMut::new(); while let Some(item) = payload.next().await { - bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInputError("Unable to parse bytes in payload sent!".to_string()) - })?); - } - - if bytes.len() >= 262144 { - return Err(ApiError::InvalidInputError(String::from( - "Icons must be smaller than 256KiB", - ))); + if bytes.len() >= 262144 { + return Err(ApiError::InvalidInputError(String::from( + "Icons must be smaller than 256KiB", + ))); + } else { + bytes.extend_from_slice(&item.map_err(|_| { + ApiError::InvalidInputError( + "Unable to parse bytes in payload sent!".to_string(), + ) + })?); + } } let hash = sha1::Sha1::from(&bytes).hexdigest(); @@ -1081,10 +1087,16 @@ pub async fn delete_project_icon( Ok(HttpResponse::NoContent().body("")) } +#[derive(Serialize, Deserialize)] +pub struct GalleryCreateQuery { + pub featured: bool, +} + #[post("{id}/gallery")] pub async fn add_gallery_item( web::Query(ext): web::Query, req: HttpRequest, + web::Query(item): web::Query, info: web::Path<(String,)>, pool: web::Data, file_host: web::Data>, @@ -1123,17 +1135,19 @@ pub async fn add_gallery_item( let mut bytes = web::BytesMut::new(); while let Some(item) = payload.next().await { - bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInputError("Unable to parse bytes in payload sent!".to_string()) - })?); - } + const FILE_SIZE_CAP: usize = 5 * (1 << 20); - const FILE_SIZE_CAP: usize = 5 * (1 << 20); - - if bytes.len() >= FILE_SIZE_CAP { - return Err(ApiError::InvalidInputError(String::from( - "Gallery image exceeds the maximum of 5MiB.", - ))); + if bytes.len() >= FILE_SIZE_CAP { + return Err(ApiError::InvalidInputError(String::from( + "Gallery image exceeds the maximum of 5MiB.", + ))); + } else { + bytes.extend_from_slice(&item.map_err(|_| { + ApiError::InvalidInputError( + "Unable to parse bytes in payload sent!".to_string(), + ) + })?); + } } let hash = sha1::Sha1::from(&bytes).hexdigest(); @@ -1149,6 +1163,7 @@ pub async fn add_gallery_item( database::models::project_item::GalleryItem { project_id: project_item.id, image_url: format!("{}/{}", cdn_url, url), + featured: item.featured, } .insert(&mut transaction) .await?; @@ -1163,14 +1178,93 @@ pub async fn add_gallery_item( } #[derive(Serialize, Deserialize)] -pub struct GalleryItem { - pub item: String, +pub struct GalleryEditQuery { + pub url: String, + pub featured: bool, +} + +#[patch("{id}/gallery")] +pub async fn edit_gallery_item( + req: HttpRequest, + web::Query(item): web::Query, + info: web::Path<(String,)>, + pool: web::Data, +) -> Result { + let user = get_user_from_headers(req.headers(), &**pool).await?; + let string = info.into_inner().0; + + let project_item = + database::models::Project::get_from_slug_or_project_id(string.clone(), &**pool) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError("The specified project does not exist!".to_string()) + })?; + + if !user.role.is_mod() { + let team_member = database::models::TeamMember::get_from_user_id( + project_item.team_id, + user.id.into(), + &**pool, + ) + .await + .map_err(ApiError::DatabaseError)? + .ok_or_else(|| { + ApiError::InvalidInputError("The specified project does not exist!".to_string()) + })?; + + if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { + return Err(ApiError::CustomAuthenticationError( + "You don't have permission to edit this project's gallery.".to_string(), + )); + } + } + let mut transaction = pool.begin().await?; + + let id = sqlx::query!( + " + SELECT id FROM mods_gallery + WHERE image_url = $1 + ", + item.url + ) + .fetch_optional(&mut *transaction) + .await? + .ok_or_else(|| { + ApiError::InvalidInputError(format!( + "Gallery item at URL {} is not part of the project's gallery.", + item.url + )) + })? + .id; + + let mut transaction = pool.begin().await?; + + sqlx::query!( + " + UPDATE mods_gallery + SET featured = $2 + WHERE id = $1 + ", + id, + item.featured + ) + .execute(&mut *transaction) + .await?; + + transaction.commit().await?; + + Ok(HttpResponse::NoContent().body("")) +} + +#[derive(Serialize, Deserialize)] +pub struct GalleryDeleteQuery { + pub url: String, } #[delete("{id}/gallery")] pub async fn delete_gallery_item( req: HttpRequest, - web::Query(item): web::Query, + web::Query(item): web::Query, info: web::Path<(String,)>, pool: web::Data, file_host: web::Data>, @@ -1199,7 +1293,7 @@ pub async fn delete_gallery_item( if !team_member.permissions.contains(Permissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthenticationError( - "You don't have permission to edit this project's icon.".to_string(), + "You don't have permission to edit this project's gallery.".to_string(), )); } } @@ -1210,19 +1304,19 @@ pub async fn delete_gallery_item( SELECT id FROM mods_gallery WHERE image_url = $1 ", - item.item + item.url ) .fetch_optional(&mut *transaction) .await? .ok_or_else(|| { ApiError::InvalidInputError(format!( "Gallery item at URL {} is not part of the project's gallery.", - item.item + item.url )) })? .id; - let name = item.item.split('/').next(); + let name = item.url.split('/').next(); if let Some(item_path) = name { file_host.delete_file_version("", item_path).await?; diff --git a/src/routes/teams.rs b/src/routes/teams.rs index 398b1fd65..036e2de97 100644 --- a/src/routes/teams.rs +++ b/src/routes/teams.rs @@ -341,6 +341,66 @@ pub async fn edit_team_member( Ok(HttpResponse::NoContent().body("")) } +#[derive(Deserialize)] +pub struct TransferOwnership { + pub user_id: UserId, +} + +#[patch("{id}/owner")] +pub async fn transfer_ownership( + req: HttpRequest, + info: web::Path<(TeamId,)>, + pool: web::Data, + new_owner: web::Json, +) -> Result { + let id = info.into_inner().0; + + let current_user = get_user_from_headers(req.headers(), &**pool).await?; + let team_member = + TeamMember::get_from_user_id(id.into(), current_user.id.into(), &**pool).await?; + + let member = match team_member { + Some(m) => m, + None => { + return Err(ApiError::CustomAuthenticationError( + "You don't have permission to edit members of this team".to_string(), + )) + } + }; + + if member.role != crate::models::teams::OWNER_ROLE { + return Err(ApiError::CustomAuthenticationError( + "You don't have permission to edit the ownership of this team".to_string(), + )); + } + + let mut transaction = pool.begin().await?; + + TeamMember::edit_team_member( + id.into(), + current_user.id.into(), + None, + Some(crate::models::teams::DEFAULT_ROLE.to_string()), + None, + &mut transaction, + ) + .await?; + + TeamMember::edit_team_member( + id.into(), + new_owner.user_id.into(), + None, + Some(crate::models::teams::OWNER_ROLE.to_string()), + None, + &mut transaction, + ) + .await?; + + transaction.commit().await?; + + Ok(HttpResponse::NoContent().body("")) +} + #[delete("{id}/members/{user_id}")] pub async fn remove_team_member( req: HttpRequest, diff --git a/src/routes/users.rs b/src/routes/users.rs index bc0b546ec..33d67ad4d 100644 --- a/src/routes/users.rs +++ b/src/routes/users.rs @@ -98,7 +98,7 @@ pub async fn projects_list( 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) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { @@ -172,7 +172,7 @@ pub async fn user_edit( .map_err(|err| ApiError::ValidationError(validation_errors_to_string(err, None)))?; let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { @@ -182,17 +182,28 @@ pub async fn user_edit( let mut transaction = pool.begin().await?; if let Some(username) = &new_user.username { - sqlx::query!( - " + let user_option = + crate::database::models::User::get_id_from_username_or_id(username, &**pool) + .await?; + + if user_option.is_none() { + sqlx::query!( + " UPDATE users SET username = $1 WHERE (id = $2) ", - username, - id as crate::database::models::ids::UserId, - ) - .execute(&mut *transaction) - .await?; + username, + id as crate::database::models::ids::UserId, + ) + .execute(&mut *transaction) + .await?; + } else { + return Err(ApiError::InvalidInputError(format!( + "Username {} is taken!", + username + ))); + } } if let Some(name) = &new_user.name { @@ -289,9 +300,11 @@ pub async fn user_icon_edit( if let Some(content_type) = crate::util::ext::get_image_content_type(&*ext.ext) { let cdn_url = dotenv::var("CDN_URL")?; let user = get_user_from_headers(req.headers(), &**pool).await?; - let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) - .await?; + let id_option = crate::database::models::User::get_id_from_username_or_id( + &*info.into_inner().0, + &**pool, + ) + .await?; if let Some(id) = id_option { if user.id != id.into() && !user.role.is_mod() { @@ -326,17 +339,17 @@ pub async fn user_icon_edit( let mut bytes = web::BytesMut::new(); while let Some(item) = payload.next().await { - bytes.extend_from_slice(&item.map_err(|_| { - ApiError::InvalidInputError( - "Unable to parse bytes in payload sent!".to_string(), - ) - })?); - } - - if bytes.len() >= 262144 { - return Err(ApiError::InvalidInputError(String::from( - "Icons must be smaller than 256KiB", - ))); + if bytes.len() >= 262144 { + return Err(ApiError::InvalidInputError(String::from( + "Icons must be smaller than 256KiB", + ))); + } else { + bytes.extend_from_slice(&item.map_err(|_| { + ApiError::InvalidInputError( + "Unable to parse bytes in payload sent!".to_string(), + ) + })?); + } } let upload_data = file_host @@ -389,7 +402,7 @@ pub async fn user_delete( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { @@ -428,7 +441,7 @@ pub async fn user_follows( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { @@ -475,7 +488,7 @@ pub async fn user_notifications( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { diff --git a/src/routes/v1/users.rs b/src/routes/v1/users.rs index 06e8807d1..ad09066cf 100644 --- a/src/routes/v1/users.rs +++ b/src/routes/v1/users.rs @@ -16,7 +16,7 @@ pub async fn mods_list( 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) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { @@ -51,7 +51,7 @@ pub async fn user_follows( ) -> Result { let user = get_user_from_headers(req.headers(), &**pool).await?; let id_option = - crate::database::models::User::get_id_from_username_or_id(info.into_inner().0, &**pool) + crate::database::models::User::get_id_from_username_or_id(&*info.into_inner().0, &**pool) .await?; if let Some(id) = id_option { diff --git a/src/routes/version_creation.rs b/src/routes/version_creation.rs index 5c50dcb01..94054b958 100644 --- a/src/routes/version_creation.rs +++ b/src/routes/version_creation.rs @@ -585,17 +585,16 @@ pub async fn upload_file( let mut data = Vec::new(); while let Some(chunk) = field.next().await { - data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); - } + // Project file size limit of 100MiB + const FILE_SIZE_CAP: usize = 100 * (1 << 20); - // Project file size limit of 100MiB - const FILE_SIZE_CAP: usize = 100 * (1 << 20); - - // TODO: override file size cap for authorized users or projects - if data.len() >= FILE_SIZE_CAP { - return Err(CreateError::InvalidInput( - String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.") - )); + if data.len() >= FILE_SIZE_CAP { + return Err(CreateError::InvalidInput( + String::from("Project file exceeds the maximum of 100MiB. Contact a moderator or admin to request permission to upload larger files.") + )); + } else { + data.extend_from_slice(&chunk.map_err(CreateError::MultipartError)?); + } } let validation_result = validate_file( diff --git a/src/search/indexing/local_import.rs b/src/search/indexing/local_import.rs index d3a772b85..79561fd0f 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -18,7 +18,8 @@ pub async fn index_local(pool: PgPool) -> Result, Index 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, l.short short, pt.name project_type_name, u.username username, - STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions + STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions, + STRING_AGG(DISTINCT mg.image_url, ',') gallery 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 @@ -27,6 +28,7 @@ pub async fn index_local(pool: PgPool) -> Result, Index 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 @@ -43,10 +45,10 @@ pub async fn index_local(pool: PgPool) -> Result, Index .fetch_many(&pool) .try_filter_map(|e| async { Ok(e.right().map(|m| { - let mut categories = m.categories.unwrap_or_default().split(',').map(|x| x.to_string()).collect::>(); - categories.append(&mut m.loaders.unwrap_or_default().split(',').map(|x| x.to_string()).collect::>()); + let mut categories = m.categories.map(|x| x.split(',').map(|x| x.to_string()).collect::>()).unwrap_or_default(); + categories.append(&mut m.loaders.map(|x| x.split(',').map(|x| x.to_string()).collect::>()).unwrap_or_default()); - let versions : Vec = m.versions.unwrap_or_default().split(',').map(|x| x.to_string()).collect::>(); + let versions : Vec = m.versions.map(|x| x.split(',').map(|x| x.to_string()).collect()).unwrap_or_default(); let project_id : crate::models::projects::ProjectId = ProjectId(m.id).into(); @@ -70,6 +72,7 @@ pub async fn index_local(pool: PgPool) -> Result, Index server_side: m.server_side_type, slug: m.slug, project_type: m.project_type_name, + gallery: m.gallery.map(|x| x.split(',').map(|x| x.to_string()).collect()).unwrap_or_default() } })) }) @@ -89,7 +92,8 @@ pub async fn query_one( 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, l.short short, pt.name project_type_name, u.username username, - STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions + STRING_AGG(DISTINCT c.category, ',') categories, STRING_AGG(DISTINCT lo.loader, ',') loaders, STRING_AGG(DISTINCT gv.version, ',') versions, + STRING_AGG(DISTINCT mg.image_url, ',') gallery 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 @@ -98,6 +102,7 @@ pub async fn query_one( 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 @@ -116,25 +121,19 @@ pub async fn query_one( let mut categories = m .categories - .unwrap_or_default() - .split(',') - .map(|x| x.to_string()) - .collect::>(); + .map(|x| x.split(',').map(|x| x.to_string()).collect::>()) + .unwrap_or_default(); categories.append( &mut m .loaders - .unwrap_or_default() - .split(',') - .map(|x| x.to_string()) - .collect::>(), + .map(|x| x.split(',').map(|x| x.to_string()).collect::>()) + .unwrap_or_default(), ); let versions: Vec = m .versions - .unwrap_or_default() - .split(',') - .map(|x| x.to_string()) - .collect::>(); + .map(|x| x.split(',').map(|x| x.to_string()).collect()) + .unwrap_or_default(); let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into(); @@ -161,5 +160,9 @@ pub async fn query_one( server_side: m.server_side_type, slug: m.slug, project_type: m.project_type_name, + gallery: m + .gallery + .map(|x| x.split(',').map(|x| x.to_string()).collect()) + .unwrap_or_default(), }) } diff --git a/src/search/mod.rs b/src/search/mod.rs index a0e280062..386d24a9e 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -76,6 +76,7 @@ pub struct UploadSearchProject { pub license: String, pub client_side: String, pub server_side: String, + pub gallery: Vec, /// RFC 3339 formatted creation date of the project pub date_created: DateTime, @@ -117,6 +118,7 @@ pub struct ResultSearchProject { pub license: String, pub client_side: String, pub server_side: String, + pub gallery: Vec, } impl Document for UploadSearchProject {