diff --git a/.sqlx/query-04345d9c23430267f755b1420520df91bd403524fd60ba1a94e3a239ea70cae7.json b/.sqlx/query-04345d9c23430267f755b1420520df91bd403524fd60ba1a94e3a239ea70cae7.json deleted file mode 100644 index 1c31aaa7..00000000 --- a/.sqlx/query-04345d9c23430267f755b1420520df91bd403524fd60ba1a94e3a239ea70cae7.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET source_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "04345d9c23430267f755b1420520df91bd403524fd60ba1a94e3a239ea70cae7" -} diff --git a/.sqlx/query-268af672e8e475885c18da9edd81bac19f3a78a8a462bf9bb2dbe0a72c2f1ff7.json b/.sqlx/query-268af672e8e475885c18da9edd81bac19f3a78a8a462bf9bb2dbe0a72c2f1ff7.json new file mode 100644 index 00000000..aa5b4f46 --- /dev/null +++ b/.sqlx/query-268af672e8e475885c18da9edd81bac19f3a78a8a462bf9bb2dbe0a72c2f1ff7.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO mods_links (\n joining_mod_id, joining_platform_id, url\n )\n SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[])\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8Array", + "Int4Array", + "VarcharArray" + ] + }, + "nullable": [] + }, + "hash": "268af672e8e475885c18da9edd81bac19f3a78a8a462bf9bb2dbe0a72c2f1ff7" +} diff --git a/.sqlx/query-2b8dafe9c3df9fd25235a13868e8e7607decfbe96a413cc576919a1fb510f269.json b/.sqlx/query-2b8dafe9c3df9fd25235a13868e8e7607decfbe96a413cc576919a1fb510f269.json deleted file mode 100644 index c011c5b0..00000000 --- a/.sqlx/query-2b8dafe9c3df9fd25235a13868e8e7607decfbe96a413cc576919a1fb510f269.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET discord_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "2b8dafe9c3df9fd25235a13868e8e7607decfbe96a413cc576919a1fb510f269" -} diff --git a/.sqlx/query-4567790f0dc98ff20b596a33161d1f6ac8af73da67fe8c54192724626c6bf670.json b/.sqlx/query-4567790f0dc98ff20b596a33161d1f6ac8af73da67fe8c54192724626c6bf670.json deleted file mode 100644 index 7893cca8..00000000 --- a/.sqlx/query-4567790f0dc98ff20b596a33161d1f6ac8af73da67fe8c54192724626c6bf670.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM mods_donations\n WHERE joining_mod_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "4567790f0dc98ff20b596a33161d1f6ac8af73da67fe8c54192724626c6bf670" -} diff --git a/.sqlx/query-473db826b691ae1131990ef0927cfe5b63d48829dd41edb7def22248d5668ac7.json b/.sqlx/query-473db826b691ae1131990ef0927cfe5b63d48829dd41edb7def22248d5668ac7.json deleted file mode 100644 index de301423..00000000 --- a/.sqlx/query-473db826b691ae1131990ef0927cfe5b63d48829dd41edb7def22248d5668ac7.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO mods_donations (\n joining_mod_id, joining_platform_id, url\n )\n SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[])\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8Array", - "Int4Array", - "VarcharArray" - ] - }, - "nullable": [] - }, - "hash": "473db826b691ae1131990ef0927cfe5b63d48829dd41edb7def22248d5668ac7" -} diff --git a/.sqlx/query-4d0e2d4345aeab5ee7eed847c03c913073eeb43caaf299cddcac6e41351661fd.json b/.sqlx/query-4d0e2d4345aeab5ee7eed847c03c913073eeb43caaf299cddcac6e41351661fd.json new file mode 100644 index 00000000..801c4326 --- /dev/null +++ b/.sqlx/query-4d0e2d4345aeab5ee7eed847c03c913073eeb43caaf299cddcac6e41351661fd.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM mods_links\n WHERE joining_mod_id = $1 AND joining_platform_id IN (\n SELECT id FROM link_platforms WHERE name = ANY($2)\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "4d0e2d4345aeab5ee7eed847c03c913073eeb43caaf299cddcac6e41351661fd" +} diff --git a/.sqlx/query-5db4b2678406a9977c1e37409920804e0225181befe8b784dea48e37e30fedcc.json b/.sqlx/query-5db4b2678406a9977c1e37409920804e0225181befe8b784dea48e37e30fedcc.json new file mode 100644 index 00000000..0dcd2037 --- /dev/null +++ b/.sqlx/query-5db4b2678406a9977c1e37409920804e0225181befe8b784dea48e37e30fedcc.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Int4", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "5db4b2678406a9977c1e37409920804e0225181befe8b784dea48e37e30fedcc" +} diff --git a/.sqlx/query-07ebc9dc82cd012cd4f5880b1eb3d82602c195a3e3ddd557103ee037aa6dad1c.json b/.sqlx/query-5e7146bc9dc9145cf3d01875ee599ada89e28c63fd10b3f23680d6660d0e57a2.json similarity index 55% rename from .sqlx/query-07ebc9dc82cd012cd4f5880b1eb3d82602c195a3e3ddd557103ee037aa6dad1c.json rename to .sqlx/query-5e7146bc9dc9145cf3d01875ee599ada89e28c63fd10b3f23680d6660d0e57a2.json index 3f89936b..689e6395 100644 --- a/.sqlx/query-07ebc9dc82cd012cd4f5880b1eb3d82602c195a3e3ddd557103ee037aa6dad1c.json +++ b/.sqlx/query-5e7146bc9dc9145cf3d01875ee599ada89e28c63fd10b3f23680d6660d0e57a2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ", + "query": "\n INSERT INTO mods_links (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "07ebc9dc82cd012cd4f5880b1eb3d82602c195a3e3ddd557103ee037aa6dad1c" + "hash": "5e7146bc9dc9145cf3d01875ee599ada89e28c63fd10b3f23680d6660d0e57a2" } diff --git a/.sqlx/query-83d428e1c07d16e356ef26bdf1d707940b1683b5f631ded1f6674a081453d67b.json b/.sqlx/query-83d428e1c07d16e356ef26bdf1d707940b1683b5f631ded1f6674a081453d67b.json deleted file mode 100644 index 41542fd8..00000000 --- a/.sqlx/query-83d428e1c07d16e356ef26bdf1d707940b1683b5f631ded1f6674a081453d67b.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET source_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "83d428e1c07d16e356ef26bdf1d707940b1683b5f631ded1f6674a081453d67b" -} diff --git a/.sqlx/query-fefb4f07a0f0c0cf74e554d120f8707d698fc8b4dbb66d2830f4ec0229bc1019.json b/.sqlx/query-83dc16cc7a0f4507e308a06f4924065e4ea25de0210be222ceae8eb645f888e3.json similarity index 58% rename from .sqlx/query-fefb4f07a0f0c0cf74e554d120f8707d698fc8b4dbb66d2830f4ec0229bc1019.json rename to .sqlx/query-83dc16cc7a0f4507e308a06f4924065e4ea25de0210be222ceae8eb645f888e3.json index e2b9c106..015a47d4 100644 --- a/.sqlx/query-fefb4f07a0f0c0cf74e554d120f8707d698fc8b4dbb66d2830f4ec0229bc1019.json +++ b/.sqlx/query-83dc16cc7a0f4507e308a06f4924065e4ea25de0210be222ceae8eb645f888e3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "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 license_url, license,\n slug, color, monetization_status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8, $9,\n $10, $11, $12, $13, $14,\n $15, $16, \n LOWER($17), $18, $19\n )\n ", + "query": "\n INSERT INTO mods (\n id, team_id, title, description, body,\n published, downloads, icon_url, status, requested_status,\n license_url, license,\n slug, color, monetization_status\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, \n $7, $8, $9, $10, \n $11, $12, \n LOWER($13), $14, $15\n )\n ", "describe": { "columns": [], "parameters": { @@ -17,10 +17,6 @@ "Varchar", "Varchar", "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Varchar", "Text", "Int4", "Varchar" @@ -28,5 +24,5 @@ }, "nullable": [] }, - "hash": "fefb4f07a0f0c0cf74e554d120f8707d698fc8b4dbb66d2830f4ec0229bc1019" + "hash": "83dc16cc7a0f4507e308a06f4924065e4ea25de0210be222ceae8eb645f888e3" } diff --git a/.sqlx/query-872374755deb5d68092dff9fcbc355da7c5c9cea4b471e79b17c4a9cfd6f9954.json b/.sqlx/query-872374755deb5d68092dff9fcbc355da7c5c9cea4b471e79b17c4a9cfd6f9954.json new file mode 100644 index 00000000..1dac397e --- /dev/null +++ b/.sqlx/query-872374755deb5d68092dff9fcbc355da7c5c9cea4b471e79b17c4a9cfd6f9954.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM mods_links\n WHERE joining_mod_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "872374755deb5d68092dff9fcbc355da7c5c9cea4b471e79b17c4a9cfd6f9954" +} diff --git a/.sqlx/query-d8946603f5894d2b18ad66f7747134234cd497c928fbbce6f8adef4fe3f3b1df.json b/.sqlx/query-877927a1310a1c835e1ca24b6995b7d95c62664629d7a59fbfe1404b4aff14d6.json similarity index 66% rename from .sqlx/query-d8946603f5894d2b18ad66f7747134234cd497c928fbbce6f8adef4fe3f3b1df.json rename to .sqlx/query-877927a1310a1c835e1ca24b6995b7d95c62664629d7a59fbfe1404b4aff14d6.json index 1634f3f1..af82884f 100644 --- a/.sqlx/query-d8946603f5894d2b18ad66f7747134234cd497c928fbbce6f8adef4fe3f3b1df.json +++ b/.sqlx/query-877927a1310a1c835e1ca24b6995b7d95c62664629d7a59fbfe1404b4aff14d6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n WITH version_fields_cte AS (\n SELECT mod_id, version_id, field_id, int_value, enum_value, string_value\n FROM mods m\n INNER JOIN versions v ON m.id = v.mod_id\n INNER JOIN version_fields vf ON v.id = vf.version_id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n ),\n\t\t\t\tversion_fields_json AS (\n\t\t\t\t\tSELECT DISTINCT mod_id,\n JSONB_AGG( \n DISTINCT jsonb_build_object('version_id', version_id, 'field_id', field_id, 'int_value', int_value, 'enum_value', enum_value, 'string_value', string_value)\n ) version_fields_json\n FROM version_fields_cte\n GROUP BY mod_id\n\t\t\t\t),\n\t\t\t\tloader_fields_cte AS (\n\t\t\t\t\tSELECT DISTINCT vf.mod_id, vf.version_id, lf.*, l.loader\n\t\t\t\t\tFROM loader_fields lf\n INNER JOIN version_fields_cte vf ON lf.id = vf.field_id\n\t\t\t\t\tLEFT JOIN loaders_versions lv ON vf.version_id = lv.version_id\n\t\t\t\t\tLEFT JOIN loaders l ON lv.loader_id = l.id\n GROUP BY vf.mod_id, vf.version_id, lf.enum_type, lf.id, l.loader\n\t\t\t\t),\n loader_fields_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'version_id', lf.version_id,\n 'lf_id', id, 'loader_name', loader, 'field', field, 'field_type', field_type, 'enum_type', enum_type, 'min_val', min_val, 'max_val', max_val, 'optional', optional\n )\n ) filter (where lf.id is not null) loader_fields_json\n FROM loader_fields_cte lf\n GROUP BY mod_id\n ),\n loader_field_enum_values_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'id', lfev.id, 'enum_id', lfev.enum_id, 'value', lfev.value, 'ordering', lfev.ordering, 'created', lfev.created, 'metadata', lfev.metadata\n ) \n ) filter (where lfev.id is not null) loader_field_enum_values_json\n FROM loader_field_enum_values lfev\n INNER JOIN loader_fields_cte lf on lf.enum_type = lfev.enum_id\n GROUP BY mod_id\n ),\n versions_cte AS (\n SELECT DISTINCT mod_id, v.id as id, date_published\n FROM mods m\n INNER JOIN versions v ON m.id = v.mod_id AND v.status = ANY($3)\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n ),\n versions_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'id', id, 'date_published', date_published\n )\n ) filter (where id is not null) versions_json\n FROM versions_cte\n GROUP BY mod_id\n ),\n loaders_cte AS (\n SELECT DISTINCT mod_id, l.id as id, l.loader\n FROM versions_cte\n INNER JOIN loaders_versions lv ON versions_cte.id = lv.version_id\n INNER JOIN loaders l ON lv.loader_id = l.id \n ),\n mods_gallery_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering\n )\n ) filter (where image_url is not null) mods_gallery_json\n FROM mods_gallery mg\n INNER JOIN mods m ON mg.mod_id = m.id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY mod_id\n ),\n donations_json AS (\n SELECT DISTINCT joining_mod_id as mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', md.url\n )\n ) filter (where md.joining_platform_id is not null) donations_json\n FROM mods_donations md\n INNER JOIN mods m ON md.joining_mod_id = m.id AND m.id = ANY($1) OR m.slug = ANY($2)\n INNER JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n GROUP BY mod_id\n )\n \n SELECT m.id id, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.published published,\n m.updated updated, m.approved approved, m.queued, 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.organization_id organization_id, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n m.webhook_sent, m.color,\n t.id thread_id, m.monetization_status monetization_status,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT pt.name) filter (where pt.name is not null) project_types,\n ARRAY_AGG(DISTINCT g.slug) filter (where g.slug is not null) games,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n v.versions_json versions,\n mg.mods_gallery_json gallery,\n md.donations_json donations,\n vf.version_fields_json version_fields,\n lf.loader_fields_json loader_fields,\n lfev.loader_field_enum_values_json loader_field_enum_values\n FROM mods m \n INNER JOIN threads t ON t.mod_id = m.id\n LEFT JOIN mods_gallery_json mg ON mg.mod_id = m.id\n LEFT JOIN donations_json md ON md.mod_id = m.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_json v ON v.mod_id = m.id\n LEFT JOIN loaders_cte l on l.mod_id = m.id\n LEFT JOIN loaders_project_types lpt ON lpt.joining_loader_id = l.id\n LEFT JOIN project_types pt ON pt.id = lpt.joining_project_type_id\n LEFT JOIN loaders_project_types_games lptg ON lptg.loader_id = l.id AND lptg.project_type_id = pt.id\n LEFT JOIN games g ON lptg.game_id = g.id\n LEFT OUTER JOIN version_fields_json vf ON m.id = vf.mod_id\n LEFT OUTER JOIN loader_fields_json lf ON m.id = lf.mod_id\n LEFT OUTER JOIN loader_field_enum_values_json lfev ON m.id = lfev.mod_id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id, version_fields_json, loader_fields_json, loader_field_enum_values_json, versions_json, mods_gallery_json, donations_json;\n ", + "query": "\n WITH version_fields_cte AS (\n SELECT mod_id, version_id, field_id, int_value, enum_value, string_value\n FROM mods m\n INNER JOIN versions v ON m.id = v.mod_id\n INNER JOIN version_fields vf ON v.id = vf.version_id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n ),\n\t\t\t\tversion_fields_json AS (\n\t\t\t\t\tSELECT DISTINCT mod_id,\n JSONB_AGG( \n DISTINCT jsonb_build_object('version_id', version_id, 'field_id', field_id, 'int_value', int_value, 'enum_value', enum_value, 'string_value', string_value)\n ) version_fields_json\n FROM version_fields_cte\n GROUP BY mod_id\n\t\t\t\t),\n\t\t\t\tloader_fields_cte AS (\n\t\t\t\t\tSELECT DISTINCT vf.mod_id, vf.version_id, lf.*, l.loader\n\t\t\t\t\tFROM loader_fields lf\n INNER JOIN version_fields_cte vf ON lf.id = vf.field_id\n\t\t\t\t\tLEFT JOIN loaders_versions lv ON vf.version_id = lv.version_id\n\t\t\t\t\tLEFT JOIN loaders l ON lv.loader_id = l.id\n GROUP BY vf.mod_id, vf.version_id, lf.enum_type, lf.id, l.loader\n\t\t\t\t),\n loader_fields_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'version_id', lf.version_id,\n 'lf_id', id, 'loader_name', loader, 'field', field, 'field_type', field_type, 'enum_type', enum_type, 'min_val', min_val, 'max_val', max_val, 'optional', optional\n )\n ) filter (where lf.id is not null) loader_fields_json\n FROM loader_fields_cte lf\n GROUP BY mod_id\n ),\n loader_field_enum_values_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'id', lfev.id, 'enum_id', lfev.enum_id, 'value', lfev.value, 'ordering', lfev.ordering, 'created', lfev.created, 'metadata', lfev.metadata\n ) \n ) filter (where lfev.id is not null) loader_field_enum_values_json\n FROM loader_field_enum_values lfev\n INNER JOIN loader_fields_cte lf on lf.enum_type = lfev.enum_id\n GROUP BY mod_id\n ),\n versions_cte AS (\n SELECT DISTINCT mod_id, v.id as id, date_published\n FROM mods m\n INNER JOIN versions v ON m.id = v.mod_id AND v.status = ANY($3)\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n ),\n versions_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'id', id, 'date_published', date_published\n )\n ) filter (where id is not null) versions_json\n FROM versions_cte\n GROUP BY mod_id\n ),\n loaders_cte AS (\n SELECT DISTINCT mod_id, l.id as id, l.loader\n FROM versions_cte\n INNER JOIN loaders_versions lv ON versions_cte.id = lv.version_id\n INNER JOIN loaders l ON lv.loader_id = l.id \n ),\n mods_gallery_json AS (\n SELECT DISTINCT mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'image_url', mg.image_url, 'featured', mg.featured, 'title', mg.title, 'description', mg.description, 'created', mg.created, 'ordering', mg.ordering\n )\n ) filter (where image_url is not null) mods_gallery_json\n FROM mods_gallery mg\n INNER JOIN mods m ON mg.mod_id = m.id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY mod_id\n ),\n links_json AS (\n SELECT DISTINCT joining_mod_id as mod_id,\n JSONB_AGG(\n DISTINCT jsonb_build_object(\n 'platform_id', ml.joining_platform_id, 'platform_name', lp.name,'url', ml.url, 'donation', lp.donation\n )\n ) filter (where ml.joining_platform_id is not null) links_json\n FROM mods_links ml\n INNER JOIN mods m ON ml.joining_mod_id = m.id AND m.id = ANY($1) OR m.slug = ANY($2)\n INNER JOIN link_platforms lp ON ml.joining_platform_id = lp.id\n GROUP BY mod_id\n )\n \n SELECT m.id id, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.published published,\n m.updated updated, m.approved approved, m.queued, m.status status, m.requested_status requested_status,\n m.license_url license_url,\n m.team_id team_id, m.organization_id organization_id, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n m.webhook_sent, m.color,\n t.id thread_id, m.monetization_status monetization_status,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n ARRAY_AGG(DISTINCT pt.name) filter (where pt.name is not null) project_types,\n ARRAY_AGG(DISTINCT g.slug) filter (where g.slug is not null) games,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,\n ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories,\n v.versions_json versions,\n mg.mods_gallery_json gallery,\n ml.links_json links,\n vf.version_fields_json version_fields,\n lf.loader_fields_json loader_fields,\n lfev.loader_field_enum_values_json loader_field_enum_values\n FROM mods m \n INNER JOIN threads t ON t.mod_id = m.id\n LEFT JOIN mods_gallery_json mg ON mg.mod_id = m.id\n LEFT JOIN links_json ml ON ml.mod_id = m.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_json v ON v.mod_id = m.id\n LEFT JOIN loaders_cte l on l.mod_id = m.id\n LEFT JOIN loaders_project_types lpt ON lpt.joining_loader_id = l.id\n LEFT JOIN project_types pt ON pt.id = lpt.joining_project_type_id\n LEFT JOIN loaders_project_types_games lptg ON lptg.loader_id = l.id AND lptg.project_type_id = pt.id\n LEFT JOIN games g ON lptg.game_id = g.id\n LEFT OUTER JOIN version_fields_json vf ON m.id = vf.mod_id\n LEFT OUTER JOIN loader_fields_json lf ON m.id = lf.mod_id\n LEFT OUTER JOIN loader_field_enum_values_json lfev ON m.id = lfev.mod_id\n WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id, version_fields_json, loader_fields_json, loader_field_enum_values_json, versions_json, mods_gallery_json, links_json;\n ", "describe": { "columns": [ { @@ -70,131 +70,111 @@ }, { "ordinal": 13, - "name": "issues_url", - "type_info": "Varchar" - }, - { - "ordinal": 14, - "name": "source_url", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "wiki_url", - "type_info": "Varchar" - }, - { - "ordinal": 16, - "name": "discord_url", - "type_info": "Varchar" - }, - { - "ordinal": 17, "name": "license_url", "type_info": "Varchar" }, { - "ordinal": 18, + "ordinal": 14, "name": "team_id", "type_info": "Int8" }, { - "ordinal": 19, + "ordinal": 15, "name": "organization_id", "type_info": "Int8" }, { - "ordinal": 20, + "ordinal": 16, "name": "license", "type_info": "Varchar" }, { - "ordinal": 21, + "ordinal": 17, "name": "slug", "type_info": "Varchar" }, { - "ordinal": 22, + "ordinal": 18, "name": "moderation_message", "type_info": "Varchar" }, { - "ordinal": 23, + "ordinal": 19, "name": "moderation_message_body", "type_info": "Varchar" }, { - "ordinal": 24, + "ordinal": 20, "name": "webhook_sent", "type_info": "Bool" }, { - "ordinal": 25, + "ordinal": 21, "name": "color", "type_info": "Int4" }, { - "ordinal": 26, + "ordinal": 22, "name": "thread_id", "type_info": "Int8" }, { - "ordinal": 27, + "ordinal": 23, "name": "monetization_status", "type_info": "Varchar" }, { - "ordinal": 28, + "ordinal": 24, "name": "loaders", "type_info": "VarcharArray" }, { - "ordinal": 29, + "ordinal": 25, "name": "project_types", "type_info": "VarcharArray" }, { - "ordinal": 30, + "ordinal": 26, "name": "games", "type_info": "VarcharArray" }, { - "ordinal": 31, + "ordinal": 27, "name": "categories", "type_info": "VarcharArray" }, { - "ordinal": 32, + "ordinal": 28, "name": "additional_categories", "type_info": "VarcharArray" }, { - "ordinal": 33, + "ordinal": 29, "name": "versions", "type_info": "Jsonb" }, { - "ordinal": 34, + "ordinal": 30, "name": "gallery", "type_info": "Jsonb" }, { - "ordinal": 35, - "name": "donations", + "ordinal": 31, + "name": "links", "type_info": "Jsonb" }, { - "ordinal": 36, + "ordinal": 32, "name": "version_fields", "type_info": "Jsonb" }, { - "ordinal": 37, + "ordinal": 33, "name": "loader_fields", "type_info": "Jsonb" }, { - "ordinal": 38, + "ordinal": 34, "name": "loader_field_enum_values", "type_info": "Jsonb" } @@ -221,10 +201,6 @@ false, true, true, - true, - true, - true, - true, false, true, false, @@ -248,5 +224,5 @@ null ] }, - "hash": "d8946603f5894d2b18ad66f7747134234cd497c928fbbce6f8adef4fe3f3b1df" + "hash": "877927a1310a1c835e1ca24b6995b7d95c62664629d7a59fbfe1404b4aff14d6" } diff --git a/.sqlx/query-88a085c2f2b1aa11eacdeedb68b490a695ebf2a9efb24bd5715b8d903f57e2c5.json b/.sqlx/query-88a085c2f2b1aa11eacdeedb68b490a695ebf2a9efb24bd5715b8d903f57e2c5.json new file mode 100644 index 00000000..fd7b63b9 --- /dev/null +++ b/.sqlx/query-88a085c2f2b1aa11eacdeedb68b490a695ebf2a9efb24bd5715b8d903f57e2c5.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE FROM mods_links\n WHERE joining_mod_id = $1 AND joining_platform_id IN (\n SELECT id FROM link_platforms WHERE name = ANY($2)\n )\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "TextArray" + ] + }, + "nullable": [] + }, + "hash": "88a085c2f2b1aa11eacdeedb68b490a695ebf2a9efb24bd5715b8d903f57e2c5" +} diff --git a/.sqlx/query-8abb317c85f48c7dd9ccf4a7b8fbc0b58ac73f7ae87ff2dfe67009a51089f784.json b/.sqlx/query-8abb317c85f48c7dd9ccf4a7b8fbc0b58ac73f7ae87ff2dfe67009a51089f784.json deleted file mode 100644 index fc2c3450..00000000 --- a/.sqlx/query-8abb317c85f48c7dd9ccf4a7b8fbc0b58ac73f7ae87ff2dfe67009a51089f784.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET wiki_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "8abb317c85f48c7dd9ccf4a7b8fbc0b58ac73f7ae87ff2dfe67009a51089f784" -} diff --git a/.sqlx/query-99a1eac69d7f5a5139703df431e6a5c3012a90143a8c635f93632f04d0bc41d4.json b/.sqlx/query-99a1eac69d7f5a5139703df431e6a5c3012a90143a8c635f93632f04d0bc41d4.json deleted file mode 100644 index aba372c1..00000000 --- a/.sqlx/query-99a1eac69d7f5a5139703df431e6a5c3012a90143a8c635f93632f04d0bc41d4.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET wiki_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "99a1eac69d7f5a5139703df431e6a5c3012a90143a8c635f93632f04d0bc41d4" -} diff --git a/.sqlx/query-9aab2350d576fd934b0541d1f71f320ac939b44a179fee3d1638113cdb3ddfe7.json b/.sqlx/query-9aab2350d576fd934b0541d1f71f320ac939b44a179fee3d1638113cdb3ddfe7.json deleted file mode 100644 index 7683ee0c..00000000 --- a/.sqlx/query-9aab2350d576fd934b0541d1f71f320ac939b44a179fee3d1638113cdb3ddfe7.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url)\n VALUES ($1, $2, $3)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Int4", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "9aab2350d576fd934b0541d1f71f320ac939b44a179fee3d1638113cdb3ddfe7" -} diff --git a/.sqlx/query-acbafe265c4b7a1c95b0494a0a03c8bd2cd778ae561ef5a662fa931ca26cf603.json b/.sqlx/query-acbafe265c4b7a1c95b0494a0a03c8bd2cd778ae561ef5a662fa931ca26cf603.json deleted file mode 100644 index 9cbd917c..00000000 --- a/.sqlx/query-acbafe265c4b7a1c95b0494a0a03c8bd2cd778ae561ef5a662fa931ca26cf603.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n DELETE FROM mods_donations\n WHERE joining_mod_id = $1\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "acbafe265c4b7a1c95b0494a0a03c8bd2cd778ae561ef5a662fa931ca26cf603" -} diff --git a/.sqlx/query-d8020ed838c032c2c287dc0f08989b3ab7156f2571bc75505e6f57b0caeef9c7.json b/.sqlx/query-b1bfb62f4a28ca0fae778738e8a8f325185a83c5d5a240c088a2397b35c55f2a.json similarity index 59% rename from .sqlx/query-d8020ed838c032c2c287dc0f08989b3ab7156f2571bc75505e6f57b0caeef9c7.json rename to .sqlx/query-b1bfb62f4a28ca0fae778738e8a8f325185a83c5d5a240c088a2397b35c55f2a.json index c8e8481e..5610c103 100644 --- a/.sqlx/query-d8020ed838c032c2c287dc0f08989b3ab7156f2571bc75505e6f57b0caeef9c7.json +++ b/.sqlx/query-b1bfb62f4a28ca0fae778738e8a8f325185a83c5d5a240c088a2397b35c55f2a.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id FROM donation_platforms\n WHERE short = $1\n ", + "query": "\n SELECT id FROM link_platforms\n WHERE name = $1\n ", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "d8020ed838c032c2c287dc0f08989b3ab7156f2571bc75505e6f57b0caeef9c7" + "hash": "b1bfb62f4a28ca0fae778738e8a8f325185a83c5d5a240c088a2397b35c55f2a" } diff --git a/.sqlx/query-bad7cae347771e801976c26f2afaf33bda371051923b8f74a2f32a0ef5c65e57.json b/.sqlx/query-bad7cae347771e801976c26f2afaf33bda371051923b8f74a2f32a0ef5c65e57.json deleted file mode 100644 index 1ab92b4c..00000000 --- a/.sqlx/query-bad7cae347771e801976c26f2afaf33bda371051923b8f74a2f32a0ef5c65e57.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET discord_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "bad7cae347771e801976c26f2afaf33bda371051923b8f74a2f32a0ef5c65e57" -} diff --git a/.sqlx/query-bc91841f9672608a28bd45a862919f2bd34fac0b3479e3b4b67a9f6bea2a562a.json b/.sqlx/query-bc91841f9672608a28bd45a862919f2bd34fac0b3479e3b4b67a9f6bea2a562a.json deleted file mode 100644 index fb1b14f1..00000000 --- a/.sqlx/query-bc91841f9672608a28bd45a862919f2bd34fac0b3479e3b4b67a9f6bea2a562a.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET issues_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "bc91841f9672608a28bd45a862919f2bd34fac0b3479e3b4b67a9f6bea2a562a" -} diff --git a/.sqlx/query-e3fb74a94a6a78b1007dd99ad11bdcfaa0957ed7d1683997aef7301e0f15baba.json b/.sqlx/query-e3fb74a94a6a78b1007dd99ad11bdcfaa0957ed7d1683997aef7301e0f15baba.json deleted file mode 100644 index ab0f3449..00000000 --- a/.sqlx/query-e3fb74a94a6a78b1007dd99ad11bdcfaa0957ed7d1683997aef7301e0f15baba.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE mods\n SET issues_url = $1\n WHERE (id = $2)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "e3fb74a94a6a78b1007dd99ad11bdcfaa0957ed7d1683997aef7301e0f15baba" -} diff --git a/.sqlx/query-76db1c204139e18002e5751c3dcefff79791a1dd852b62d34fcf008151e8945a.json b/.sqlx/query-fef7802edefa1a6957fa43ccf64b987c3547294f0c8581028ac6b83de42b3a00.json similarity index 64% rename from .sqlx/query-76db1c204139e18002e5751c3dcefff79791a1dd852b62d34fcf008151e8945a.json rename to .sqlx/query-fef7802edefa1a6957fa43ccf64b987c3547294f0c8581028ac6b83de42b3a00.json index a16f796c..bcca3ff2 100644 --- a/.sqlx/query-76db1c204139e18002e5751c3dcefff79791a1dd852b62d34fcf008151e8945a.json +++ b/.sqlx/query-fef7802edefa1a6957fa43ccf64b987c3547294f0c8581028ac6b83de42b3a00.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, short, name FROM donation_platforms\n ", + "query": "\n SELECT id, name, donation FROM link_platforms\n ", "describe": { "columns": [ { @@ -10,13 +10,13 @@ }, { "ordinal": 1, - "name": "short", + "name": "name", "type_info": "Varchar" }, { "ordinal": 2, - "name": "name", - "type_info": "Varchar" + "name": "donation", + "type_info": "Bool" } ], "parameters": { @@ -28,5 +28,5 @@ false ] }, - "hash": "76db1c204139e18002e5751c3dcefff79791a1dd852b62d34fcf008151e8945a" + "hash": "fef7802edefa1a6957fa43ccf64b987c3547294f0c8581028ac6b83de42b3a00" } diff --git a/migrations/20231117073600_links_overhaul.sql b/migrations/20231117073600_links_overhaul.sql new file mode 100644 index 00000000..808f2d0b --- /dev/null +++ b/migrations/20231117073600_links_overhaul.sql @@ -0,0 +1,69 @@ +CREATE TABLE link_platforms ( + id SERIAL PRIMARY KEY, + name VARCHAR(16) UNIQUE NOT NULL, + + -- Used for v2 conversion + donation BOOLEAN NOT NULL DEFAULT false, + -- Will be removed at the end of the migration + donation_platform_id INTEGER REFERENCES donation_platforms (id) +); + +INSERT INTO link_platforms (donation_platform_id, name, donation) +SELECT id, short, true FROM donation_platforms; + +INSERT INTO link_platforms (name, donation) VALUES ('issues', false); +INSERT INTO link_platforms (name, donation) VALUES ('wiki', false); +INSERT INTO link_platforms (name, donation) VALUES ('discord', false); +INSERT INTO link_platforms (name, donation) VALUES ('source', false); +INSERT INTO link_platforms (name, donation) VALUES ('site', false); + +CREATE TABLE mods_links ( + id SERIAL PRIMARY KEY, + joining_mod_id BIGINT NOT NULL REFERENCES mods (id), + joining_platform_id INTEGER NOT NULL REFERENCES link_platforms (id), + url VARCHAR(255) NOT NULL +); + +INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) +SELECT DISTINCT m.id, lp.id, md.url +FROM mods m +INNER JOIN mods_donations md ON m.id = md.joining_mod_id +INNER JOIN donation_platforms dp ON dp.id = md.joining_platform_id +INNER JOIN link_platforms lp ON lp.donation_platform_id = dp.id; + +INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) +SELECT DISTINCT m.id, lp.id, issues_url +FROM mods m +CROSS JOIN link_platforms lp +WHERE issues_url IS NOT NULL +AND lp.name = 'issues'; + +INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) +SELECT DISTINCT m.id, lp.id, wiki_url +FROM mods m +CROSS JOIN link_platforms lp +WHERE wiki_url IS NOT NULL +AND lp.name = 'wiki'; + +INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) +SELECT DISTINCT m.id, lp.id, discord_url +FROM mods m +CROSS JOIN link_platforms lp +WHERE discord_url IS NOT NULL +AND lp.name = 'discord'; + +INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) +SELECT DISTINCT m.id, lp.id, source_url +FROM mods m +CROSS JOIN link_platforms lp +WHERE source_url IS NOT NULL +AND lp.name = 'source'; + +ALTER TABLE mods DROP COLUMN issues_url; +ALTER TABLE mods DROP COLUMN wiki_url; +ALTER TABLE mods DROP COLUMN discord_url; +ALTER TABLE mods DROP COLUMN source_url; + +ALTER TABLE link_platforms DROP COLUMN donation_platform_id; +DROP TABLE mods_donations; +DROP TABLE donation_platforms; \ No newline at end of file diff --git a/src/database/models/categories.rs b/src/database/models/categories.rs index 6205fab8..4e04dba5 100644 --- a/src/database/models/categories.rs +++ b/src/database/models/categories.rs @@ -29,10 +29,10 @@ pub struct ReportType { } #[derive(Serialize, Deserialize)] -pub struct DonationPlatform { - pub id: DonationPlatformId, - pub short: String, +pub struct LinkPlatform { + pub id: LinkPlatformId, pub name: String, + pub donation: bool, } impl Category { @@ -129,38 +129,32 @@ impl Category { } } -impl DonationPlatform { - pub async fn get_id<'a, E>( - id: &str, - exec: E, - ) -> Result, DatabaseError> +impl LinkPlatform { + pub async fn get_id<'a, E>(id: &str, exec: E) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let result = sqlx::query!( " - SELECT id FROM donation_platforms - WHERE short = $1 + SELECT id FROM link_platforms + WHERE name = $1 ", id ) .fetch_optional(exec) .await?; - Ok(result.map(|r| DonationPlatformId(r.id))) + Ok(result.map(|r| LinkPlatformId(r.id))) } - pub async fn list<'a, E>( - exec: E, - redis: &RedisPool, - ) -> Result, DatabaseError> + pub async fn list<'a, E>(exec: E, redis: &RedisPool) -> Result, DatabaseError> where E: sqlx::Executor<'a, Database = sqlx::Postgres>, { let mut redis = redis.connect().await?; - let res: Option> = redis - .get_deserialized_from_json(TAGS_NAMESPACE, "donation_platform") + let res: Option> = redis + .get_deserialized_from_json(TAGS_NAMESPACE, "link_platform") .await?; if let Some(res) = res { @@ -169,22 +163,22 @@ impl DonationPlatform { let result = sqlx::query!( " - SELECT id, short, name FROM donation_platforms + SELECT id, name, donation FROM link_platforms " ) .fetch_many(exec) .try_filter_map(|e| async { - Ok(e.right().map(|c| DonationPlatform { - id: DonationPlatformId(c.id), - short: c.short, + Ok(e.right().map(|c| LinkPlatform { + id: LinkPlatformId(c.id), name: c.name, + donation: c.donation, })) }) - .try_collect::>() + .try_collect::>() .await?; redis - .set_serialized_to_json(TAGS_NAMESPACE, "donation_platform", &result, None) + .set_serialized_to_json(TAGS_NAMESPACE, "link_platform", &result, None) .await?; Ok(result) diff --git a/src/database/models/ids.rs b/src/database/models/ids.rs index 50aab37a..87b35ab9 100644 --- a/src/database/models/ids.rs +++ b/src/database/models/ids.rs @@ -223,9 +223,9 @@ pub struct SideTypeId(pub i32); #[derive(Copy, Clone, Debug, Type, Serialize, Deserialize)] #[sqlx(transparent)] pub struct GameId(pub i32); -#[derive(Copy, Clone, Debug, Type, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Type, Serialize, Deserialize, PartialEq, Eq, Hash)] #[sqlx(transparent)] -pub struct DonationPlatformId(pub i32); +pub struct LinkPlatformId(pub i32); #[derive( Copy, Clone, Debug, Type, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord, diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index c35bdf53..4f8d60d6 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -15,26 +15,26 @@ pub const PROJECTS_SLUGS_NAMESPACE: &str = "projects_slugs"; const PROJECTS_DEPENDENCIES_NAMESPACE: &str = "projects_dependencies"; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DonationUrl { - pub platform_id: DonationPlatformId, - pub platform_short: String, +pub struct LinkUrl { + pub platform_id: LinkPlatformId, pub platform_name: String, pub url: String, + pub donation: bool, // Is this a donation link } -impl DonationUrl { +impl LinkUrl { pub async fn insert_many_projects( - donation_urls: Vec, + links: Vec, project_id: ProjectId, transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result<(), sqlx::error::Error> { - let (project_ids, platform_ids, urls): (Vec<_>, Vec<_>, Vec<_>) = donation_urls + let (project_ids, platform_ids, urls): (Vec<_>, Vec<_>, Vec<_>) = links .into_iter() .map(|url| (project_id.0, url.platform_id.0, url.url)) .multiunzip(); sqlx::query!( " - INSERT INTO mods_donations ( + INSERT INTO mods_links ( joining_mod_id, joining_platform_id, url ) SELECT * FROM UNNEST($1::bigint[], $2::int[], $3::varchar[]) @@ -148,11 +148,7 @@ pub struct ProjectBuilder { pub description: String, pub body: String, pub icon_url: Option, - pub issues_url: Option, - pub source_url: Option, - pub wiki_url: Option, pub license_url: Option, - pub discord_url: Option, pub categories: Vec, pub additional_categories: Vec, pub initial_versions: Vec, @@ -160,7 +156,7 @@ pub struct ProjectBuilder { pub requested_status: Option, pub license: String, pub slug: Option, - pub donation_urls: Vec, + pub link_urls: Vec, pub gallery_items: Vec, pub color: Option, pub monetization_status: MonetizationStatus, @@ -192,11 +188,7 @@ impl ProjectBuilder { downloads: 0, follows: 0, icon_url: self.icon_url, - issues_url: self.issues_url, - source_url: self.source_url, - wiki_url: self.wiki_url, license_url: self.license_url, - discord_url: self.discord_url, license: self.license, slug: self.slug, moderation_message: None, @@ -209,7 +201,7 @@ impl ProjectBuilder { project_struct.insert(&mut *transaction).await?; let ProjectBuilder { - donation_urls, + link_urls, gallery_items, categories, additional_categories, @@ -221,8 +213,7 @@ impl ProjectBuilder { version.insert(&mut *transaction).await?; } - DonationUrl::insert_many_projects(donation_urls, self.project_id, &mut *transaction) - .await?; + LinkUrl::insert_many_projects(link_urls, self.project_id, &mut *transaction).await?; GalleryItem::insert_many(gallery_items, self.project_id, &mut *transaction).await?; @@ -259,11 +250,7 @@ pub struct Project { pub downloads: i32, pub follows: i32, pub icon_url: Option, - pub issues_url: Option, - pub source_url: Option, - pub wiki_url: Option, pub license_url: Option, - pub discord_url: Option, pub license: String, pub slug: Option, pub moderation_message: Option, @@ -283,17 +270,15 @@ impl Project { " INSERT INTO mods ( id, team_id, title, description, body, - published, downloads, icon_url, issues_url, - source_url, wiki_url, status, requested_status, discord_url, + published, downloads, icon_url, status, requested_status, license_url, license, slug, color, monetization_status ) VALUES ( - $1, $2, $3, $4, $5, - $6, $7, $8, $9, - $10, $11, $12, $13, $14, - $15, $16, - LOWER($17), $18, $19 + $1, $2, $3, $4, $5, $6, + $7, $8, $9, $10, + $11, $12, + LOWER($13), $14, $15 ) ", self.id as ProjectId, @@ -304,12 +289,8 @@ impl Project { self.published, self.downloads, self.icon_url.as_ref(), - self.issues_url.as_ref(), - self.source_url.as_ref(), - self.wiki_url.as_ref(), self.status.as_str(), self.requested_status.map(|x| x.as_str()), - self.discord_url.as_ref(), self.license_url.as_ref(), &self.license, self.slug.as_ref(), @@ -384,7 +365,7 @@ impl Project { sqlx::query!( " - DELETE FROM mods_donations + DELETE FROM mods_links WHERE joining_mod_id = $1 ", id as ProjectId, @@ -650,23 +631,23 @@ impl Project { WHERE m.id = ANY($1) OR m.slug = ANY($2) GROUP BY mod_id ), - donations_json AS ( + links_json AS ( SELECT DISTINCT joining_mod_id as mod_id, JSONB_AGG( DISTINCT jsonb_build_object( - 'platform_id', md.joining_platform_id, 'platform_short', dp.short, 'platform_name', dp.name,'url', md.url + 'platform_id', ml.joining_platform_id, 'platform_name', lp.name,'url', ml.url, 'donation', lp.donation ) - ) filter (where md.joining_platform_id is not null) donations_json - FROM mods_donations md - INNER JOIN mods m ON md.joining_mod_id = m.id AND m.id = ANY($1) OR m.slug = ANY($2) - INNER JOIN donation_platforms dp ON md.joining_platform_id = dp.id + ) filter (where ml.joining_platform_id is not null) links_json + FROM mods_links ml + INNER JOIN mods m ON ml.joining_mod_id = m.id AND m.id = ANY($1) OR m.slug = ANY($2) + INNER JOIN link_platforms lp ON ml.joining_platform_id = lp.id GROUP BY mod_id ) SELECT m.id id, m.title title, m.description description, m.downloads downloads, m.follows follows, m.icon_url icon_url, m.body body, m.published published, m.updated updated, m.approved approved, m.queued, 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.license_url license_url, m.team_id team_id, m.organization_id organization_id, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body, m.webhook_sent, m.color, t.id thread_id, m.monetization_status monetization_status, @@ -677,14 +658,14 @@ impl Project { ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories, v.versions_json versions, mg.mods_gallery_json gallery, - md.donations_json donations, + ml.links_json links, vf.version_fields_json version_fields, lf.loader_fields_json loader_fields, lfev.loader_field_enum_values_json loader_field_enum_values FROM mods m INNER JOIN threads t ON t.mod_id = m.id LEFT JOIN mods_gallery_json mg ON mg.mod_id = m.id - LEFT JOIN donations_json md ON md.mod_id = m.id + LEFT JOIN links_json ml ON ml.mod_id = m.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_json v ON v.mod_id = m.id @@ -697,7 +678,7 @@ impl Project { LEFT OUTER JOIN loader_fields_json lf ON m.id = lf.mod_id LEFT OUTER JOIN loader_field_enum_values_json lfev ON m.id = lfev.mod_id WHERE m.id = ANY($1) OR m.slug = ANY($2) - GROUP BY t.id, m.id, version_fields_json, loader_fields_json, loader_field_enum_values_json, versions_json, mods_gallery_json, donations_json; + GROUP BY t.id, m.id, version_fields_json, loader_fields_json, loader_field_enum_values_json, versions_json, mods_gallery_json, links_json; ", &project_ids_parsed, &remaining_strings.into_iter().map(|x| x.to_string().to_lowercase()).collect::>(), @@ -719,11 +700,7 @@ impl Project { icon_url: m.icon_url.clone(), published: m.published, updated: m.updated, - issues_url: m.issues_url.clone(), - source_url: m.source_url.clone(), - wiki_url: m.wiki_url.clone(), license_url: m.license_url.clone(), - discord_url: m.discord_url.clone(), status: ProjectStatus::from_string( &m.status, ), @@ -774,9 +751,9 @@ impl Project { gallery }, - donation_urls: serde_json::from_value( - m.donations.unwrap_or_default(), - ).ok().unwrap_or_default(), + urls: serde_json::from_value( + m.links.unwrap_or_default(), + ).unwrap_or_default(), aggregate_version_fields: VersionField::from_query_json(m.loader_fields, m.version_fields, m.loader_field_enum_values, true), thread_id: ThreadId(m.thread_id), }})) @@ -894,7 +871,7 @@ pub struct QueryProject { pub versions: Vec, pub project_types: Vec, pub games: Vec, - pub donation_urls: Vec, + pub urls: Vec, pub gallery_items: Vec, pub thread_id: ThreadId, pub aggregate_version_fields: Vec, diff --git a/src/models/v2/projects.rs b/src/models/v2/projects.rs index c41be578..456c2aef 100644 --- a/src/models/v2/projects.rs +++ b/src/models/v2/projects.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use std::collections::HashMap; use super::super::ids::OrganizationId; @@ -8,13 +10,14 @@ use crate::database::models::{version_item, DatabaseError}; use crate::database::redis::RedisPool; use crate::models::ids::{ProjectId, VersionId}; use crate::models::projects::{ - Dependency, DonationLink, GalleryItem, License, Loader, ModeratorMessage, MonetizationStatus, - Project, ProjectStatus, Version, VersionFile, VersionStatus, VersionType, + Dependency, GalleryItem, License, Link, Loader, ModeratorMessage, MonetizationStatus, Project, + ProjectStatus, Version, VersionFile, VersionStatus, VersionType, }; use crate::models::threads::ThreadId; use crate::routes::v2_reroute; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use validator::Validate; /// A project returned from the API #[derive(Serialize, Deserialize, Clone)] @@ -132,6 +135,18 @@ impl LegacyProject { } } + let issues_url = data.link_urls.get("issues").map(|l| l.url.clone()); + let source_url = data.link_urls.get("source").map(|l| l.url.clone()); + let wiki_url = data.link_urls.get("wiki").map(|l| l.url.clone()); + let discord_url = data.link_urls.get("discord").map(|l| l.url.clone()); + + let donation_urls = data + .link_urls + .iter() + .filter(|(_, l)| l.donation) + .map(|(_, l)| DonationLink::try_from(l.clone()).ok()) + .collect::>>(); + Self { id: data.id, slug: data.slug, @@ -157,11 +172,11 @@ impl LegacyProject { loaders, versions: data.versions, icon_url: data.icon_url, - issues_url: data.issues_url, - source_url: data.source_url, - wiki_url: data.wiki_url, - discord_url: data.discord_url, - donation_urls: data.donation_urls, + issues_url, + source_url, + wiki_url, + discord_url, + donation_urls, gallery: data.gallery, color: data.color, thread_id: data.thread_id, @@ -316,3 +331,36 @@ impl From for LegacyVersion { } } } + +#[derive(Serialize, Deserialize, Validate, Clone, Eq, PartialEq)] +pub struct DonationLink { + pub id: String, + pub platform: String, + #[validate( + custom(function = "crate::util::validate::validate_url"), + length(max = 2048) + )] + pub url: String, +} + +impl TryFrom for DonationLink { + type Error = String; + fn try_from(link: Link) -> Result { + if !link.donation { + return Err("Not a donation".to_string()); + } + Ok(Self { + platform: capitalize_first(&link.platform), + url: link.url, + id: link.platform, + }) + } +} + +fn capitalize_first(input: &str) -> String { + let mut result = input.to_owned(); + if let Some(first_char) = result.get_mut(0..1) { + first_char.make_ascii_uppercase(); + } + result +} diff --git a/src/models/v3/projects.rs b/src/models/v3/projects.rs index bad97dd2..ed01dbfd 100644 --- a/src/models/v3/projects.rs +++ b/src/models/v3/projects.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use super::ids::{Base62Id, OrganizationId}; use super::teams::TeamId; use super::users::UserId; -use crate::database::models::project_item::QueryProject; +use crate::database::models::project_item::{LinkUrl, QueryProject}; use crate::database::models::version_item::QueryVersion; use crate::models::threads::ThreadId; use chrono::{DateTime, Utc}; @@ -87,16 +87,9 @@ pub struct Project { pub versions: Vec, /// The URL of the icon of the project pub icon_url: Option, - /// An optional link to where to submit bugs or issues with the project. - pub issues_url: Option, - /// An optional link to the source code for the project. - pub source_url: Option, - /// An optional link to the project's wiki page or other relevant information. - pub wiki_url: Option, - /// An optional link to the project's discord - pub discord_url: Option, - /// An optional list of all donation links the project has - pub donation_urls: Option>, + + /// A collection of links to the project's various pages. + pub link_urls: HashMap, /// A string of URLs to visual content featuring the project pub gallery: Vec, @@ -206,20 +199,11 @@ impl From for Project { loaders: m.loaders, versions: data.versions.into_iter().map(|v| v.into()).collect(), icon_url: m.icon_url, - issues_url: m.issues_url, - source_url: m.source_url, - wiki_url: m.wiki_url, - discord_url: m.discord_url, - donation_urls: Some( - data.donation_urls - .into_iter() - .map(|d| DonationLink { - id: d.platform_short, - platform: d.platform_name, - url: d.url, - }) - .collect(), - ), + link_urls: data + .urls + .into_iter() + .map(|d| (d.platform_name.clone(), Link::from(d))) + .collect(), gallery: data .gallery_items .into_iter() @@ -266,15 +250,24 @@ pub struct License { } #[derive(Serialize, Deserialize, Validate, Clone, Eq, PartialEq)] -pub struct DonationLink { - pub id: String, +pub struct Link { pub platform: String, + pub donation: bool, #[validate( custom(function = "crate::util::validate::validate_url"), length(max = 2048) )] pub url: String, } +impl From for Link { + fn from(data: LinkUrl) -> Self { + Self { + platform: data.platform_name, + donation: data.donation, + url: data.url, + } + } +} /// A status decides the visibility of a project in search, URLs, and the whole site itself. /// Approved - Project is displayed on search, and accessible by URL diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index 6108cd47..e56d5159 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -3,8 +3,8 @@ use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; use crate::models; use crate::models::ids::ImageId; -use crate::models::projects::{DonationLink, Loader, Project, ProjectStatus}; -use crate::models::v2::projects::{LegacyProject, LegacySideType}; +use crate::models::projects::{Loader, Project, ProjectStatus}; +use crate::models::v2::projects::{DonationLink, LegacyProject, LegacySideType}; use crate::queue::session::AuthQueue; use crate::routes::v3::project_creation::default_project_type; use crate::routes::v3::project_creation::{CreateError, NewGalleryItem}; @@ -193,6 +193,25 @@ pub async fn project_create( }) .collect(); + let mut link_urls = HashMap::new(); + if let Some(issue_url) = legacy_create.issues_url { + link_urls.insert("issues".to_string(), issue_url); + } + if let Some(source_url) = legacy_create.source_url { + link_urls.insert("source".to_string(), source_url); + } + if let Some(wiki_url) = legacy_create.wiki_url { + link_urls.insert("wiki".to_string(), wiki_url); + } + if let Some(discord_url) = legacy_create.discord_url { + link_urls.insert("discord".to_string(), discord_url); + } + if let Some(donation_urls) = legacy_create.donation_urls { + for donation_url in donation_urls { + link_urls.insert(donation_url.platform, donation_url.url); + } + } + Ok(v3::project_creation::ProjectCreateData { title: legacy_create.title, slug: legacy_create.slug, @@ -201,12 +220,8 @@ pub async fn project_create( initial_versions, categories: legacy_create.categories, additional_categories: legacy_create.additional_categories, - issues_url: legacy_create.issues_url, - source_url: legacy_create.source_url, - wiki_url: legacy_create.wiki_url, license_url: legacy_create.license_url, - discord_url: legacy_create.discord_url, - donation_urls: legacy_create.donation_urls, + link_urls, is_draft: legacy_create.is_draft, license_id: legacy_create.license_id, gallery_items: legacy_create.gallery_items, diff --git a/src/routes/v2/projects.rs b/src/routes/v2/projects.rs index 63482db2..48895e3a 100644 --- a/src/routes/v2/projects.rs +++ b/src/routes/v2/projects.rs @@ -1,21 +1,22 @@ +use crate::database::models::categories::LinkPlatform; use crate::database::models::{project_item, version_item}; use crate::database::redis::RedisPool; use crate::file_hosting::FileHost; use crate::models; use crate::models::projects::{ - DonationLink, MonetizationStatus, Project, ProjectStatus, SearchRequest, Version, + Link, MonetizationStatus, Project, ProjectStatus, SearchRequest, Version, }; -use crate::models::v2::projects::{LegacyProject, LegacySideType}; +use crate::models::v2::projects::{DonationLink, LegacyProject, LegacySideType}; use crate::models::v2::search::LegacySearchResults; use crate::queue::session::AuthQueue; use crate::routes::v3::projects::ProjectIds; use crate::routes::{v2_reroute, v3, ApiError}; use crate::search::{search_for_project, SearchConfig, SearchError}; use actix_web::{delete, get, patch, post, web, HttpRequest, HttpResponse}; - use itertools::Itertools; use serde::{Deserialize, Serialize}; use sqlx::PgPool; +use std::collections::HashMap; use std::sync::Arc; use validator::Validate; @@ -360,18 +361,78 @@ pub async fn project_edit( // - change the loaders to mrpack only // - add categories to the project for the corresponding loaders + let mut new_links = HashMap::new(); + if let Some(issues_url) = v2_new_project.issues_url { + if let Some(issues_url) = issues_url { + new_links.insert("issues".to_string(), Some(issues_url)); + } else { + new_links.insert("issues".to_string(), None); + } + } + + if let Some(source_url) = v2_new_project.source_url { + if let Some(source_url) = source_url { + new_links.insert("source".to_string(), Some(source_url)); + } else { + new_links.insert("source".to_string(), None); + } + } + + if let Some(wiki_url) = v2_new_project.wiki_url { + if let Some(wiki_url) = wiki_url { + new_links.insert("wiki".to_string(), Some(wiki_url)); + } else { + new_links.insert("wiki".to_string(), None); + } + } + + if let Some(discord_url) = v2_new_project.discord_url { + if let Some(discord_url) = discord_url { + new_links.insert("discord".to_string(), Some(discord_url)); + } else { + new_links.insert("discord".to_string(), None); + } + } + + // In v2, setting donation links resets all other donation links + // (resetting to the new ones) + if let Some(donation_urls) = v2_new_project.donation_urls { + // Fetch current donation links from project so we know what to delete + let fetched_example_project = project_item::Project::get(&info.0, &**pool, &redis).await?; + let donation_links = fetched_example_project + .map(|x| { + x.urls + .into_iter() + .filter_map(|l| { + if l.donation { + Some(Link::from(l)) // TODO: tests + } else { + None + } + }) + .collect::>() + }) + .unwrap_or_default(); + + // Set existing donation links to None + for old_link in donation_links { + new_links.insert(old_link.platform, None); + } + + // Add new donation links + for donation_url in donation_urls { + new_links.insert(donation_url.id, Some(donation_url.url)); + } + } + let new_project = v3::projects::EditProject { title: v2_new_project.title, description: v2_new_project.description, body: v2_new_project.body, categories: v2_new_project.categories, additional_categories: v2_new_project.additional_categories, - issues_url: v2_new_project.issues_url, - source_url: v2_new_project.source_url, - wiki_url: v2_new_project.wiki_url, license_url: v2_new_project.license_url, - discord_url: v2_new_project.discord_url, - donation_urls: v2_new_project.donation_urls, + link_urls: Some(new_links), license_id: v2_new_project.license_id, slug: v2_new_project.slug, status: v2_new_project.status, @@ -500,6 +561,70 @@ pub async fn projects_edit( session_queue: web::Data, ) -> Result { let bulk_edit_project = bulk_edit_project.into_inner(); + + let mut link_urls = HashMap::new(); + + // If we are *setting* donation links, we will set every possible donation link to None, as + // setting will delete all of them then 're-add' the ones we want to keep + if let Some(donation_url) = bulk_edit_project.donation_urls { + let link_platforms = LinkPlatform::list(&**pool, &redis).await?; + for link in link_platforms { + if link.donation { + link_urls.insert(link.name, None); + } + } + // add + for donation_url in donation_url { + link_urls.insert(donation_url.id, Some(donation_url.url)); + } + } + + // For every delete, we will set the link to None + if let Some(donation_url) = bulk_edit_project.remove_donation_urls { + for donation_url in donation_url { + link_urls.insert(donation_url.id, None); + } + } + + // For every add, we will set the link to the new url + if let Some(donation_url) = bulk_edit_project.add_donation_urls { + for donation_url in donation_url { + link_urls.insert(donation_url.id, Some(donation_url.url)); + } + } + + if let Some(issue_url) = bulk_edit_project.issues_url { + if let Some(issue_url) = issue_url { + link_urls.insert("issues".to_string(), Some(issue_url)); + } else { + link_urls.insert("issues".to_string(), None); + } + } + + if let Some(source_url) = bulk_edit_project.source_url { + if let Some(source_url) = source_url { + link_urls.insert("source".to_string(), Some(source_url)); + } else { + link_urls.insert("source".to_string(), None); + } + } + + if let Some(wiki_url) = bulk_edit_project.wiki_url { + if let Some(wiki_url) = wiki_url { + link_urls.insert("wiki".to_string(), Some(wiki_url)); + } else { + link_urls.insert("wiki".to_string(), None); + } + } + + if let Some(discord_url) = bulk_edit_project.discord_url { + if let Some(discord_url) = discord_url { + link_urls.insert("discord".to_string(), Some(discord_url)); + } else { + link_urls.insert("discord".to_string(), None); + } + } + v3::projects::projects_edit( req, web::Query(ids), @@ -511,13 +636,7 @@ pub async fn projects_edit( additional_categories: bulk_edit_project.additional_categories, add_additional_categories: bulk_edit_project.add_additional_categories, remove_additional_categories: bulk_edit_project.remove_additional_categories, - donation_urls: bulk_edit_project.donation_urls, - add_donation_urls: bulk_edit_project.add_donation_urls, - remove_donation_urls: bulk_edit_project.remove_donation_urls, - issues_url: bulk_edit_project.issues_url, - source_url: bulk_edit_project.source_url, - wiki_url: bulk_edit_project.wiki_url, - discord_url: bulk_edit_project.discord_url, + link_urls: Some(link_urls), }), redis, session_queue, diff --git a/src/routes/v2/tags.rs b/src/routes/v2/tags.rs index 38081fdb..71bbadcf 100644 --- a/src/routes/v2/tags.rs +++ b/src/routes/v2/tags.rs @@ -4,7 +4,9 @@ use super::ApiError; use crate::database::models::loader_fields::LoaderFieldEnumValue; use crate::database::redis::RedisPool; use crate::models::v2::projects::LegacySideType; -use crate::routes::v3::tags::{LoaderData as LoaderDataV3, LoaderFieldsEnumQuery}; +use crate::routes::v3::tags::{ + LinkPlatformQueryData, LoaderData as LoaderDataV3, LoaderFieldsEnumQuery, +}; use crate::routes::{v2_reroute, v3}; use actix_web::{get, web, HttpResponse}; use chrono::{DateTime, Utc}; @@ -164,8 +166,10 @@ pub async fn license_text(params: web::Path<(String,)>) -> Result, redis: web::Data, ) -> Result { - v3::tags::donation_platform_list(pool, redis).await + let response = v3::tags::link_platform_list(pool, redis).await?; + + // Convert to V2 format + Ok( + match v2_reroute::extract_ok_json::>(response).await { + Ok(platforms) => { + let platforms = platforms + .into_iter() + .map(|p| DonationPlatformQueryData { name: p.name }) + .collect::>(); + HttpResponse::Ok().json(platforms) + } + Err(response) => response, + }, + ) } #[get("report_type")] diff --git a/src/routes/v3/project_creation.rs b/src/routes/v3/project_creation.rs index 659f6dec..f5295c23 100644 --- a/src/routes/v3/project_creation.rs +++ b/src/routes/v3/project_creation.rs @@ -10,7 +10,7 @@ use crate::models::ids::{ImageId, OrganizationId}; use crate::models::images::{Image, ImageContext}; use crate::models::pats::Scopes; use crate::models::projects::{ - DonationLink, License, MonetizationStatus, ProjectId, ProjectStatus, VersionId, VersionStatus, + License, Link, MonetizationStatus, ProjectId, ProjectStatus, VersionId, VersionStatus, }; use crate::models::teams::ProjectPermissions; use crate::models::threads::ThreadType; @@ -187,39 +187,12 @@ pub struct ProjectCreateData { /// A list of the categories that the project is in. pub additional_categories: Vec, - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - /// An optional link to where to submit bugs or issues with the project. - pub issues_url: Option, - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - /// An optional link to the source code for the project. - pub source_url: Option, - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - /// An optional link to the project's wiki page or other relevant information. - pub wiki_url: Option, - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] /// An optional link to the project's license page pub license_url: Option, - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - /// An optional link to the project's discord. - pub discord_url: Option, - /// An optional list of all donation links the project has\ - #[validate] - pub donation_urls: Option>, + /// An optional list of all donation links the project has + #[validate(custom(function = "crate::util::validate::validate_url_hashmap_values"))] + #[serde(default)] + pub link_urls: HashMap, /// An optional boolean. If true, the project will be created as a draft. pub is_draft: Option, @@ -671,27 +644,35 @@ async fn project_create_inner( CreateError::InvalidInput(format!("Invalid SPDX license identifier: {err}")) })?; - let mut donation_urls = vec![]; - - if let Some(urls) = &project_create_data.donation_urls { - for url in urls { - let platform_id = - models::categories::DonationPlatform::get_id(&url.id, &mut **transaction) - .await? - .ok_or_else(|| { - CreateError::InvalidInput(format!( - "Donation platform {} does not exist.", - url.id.clone() - )) - })?; - - donation_urls.push(models::project_item::DonationUrl { - platform_id, - platform_short: "".to_string(), - platform_name: "".to_string(), - url: url.url.clone(), - }) - } + let mut link_urls = vec![]; + + let link_platforms = + models::categories::LinkPlatform::list(&mut **transaction, redis).await?; + for (platform, url) in &project_create_data.link_urls { + let platform_id = + models::categories::LinkPlatform::get_id(platform, &mut **transaction) + .await? + .ok_or_else(|| { + CreateError::InvalidInput(format!( + "Link platform {} does not exist.", + platform.clone() + )) + })?; + let link_platform = link_platforms + .iter() + .find(|x| x.id == platform_id) + .ok_or_else(|| { + CreateError::InvalidInput(format!( + "Link platform {} does not exist.", + platform.clone() + )) + })?; + link_urls.push(models::project_item::LinkUrl { + platform_id, + platform_name: link_platform.name.clone(), + url: url.clone(), + donation: link_platform.donation, + }) } let project_builder_actual = models::project_item::ProjectBuilder { @@ -702,12 +683,8 @@ async fn project_create_inner( description: project_create_data.description, body: project_create_data.body, icon_url: icon_data.clone().map(|x| x.0), - issues_url: project_create_data.issues_url, - source_url: project_create_data.source_url, - wiki_url: project_create_data.wiki_url, license_url: project_create_data.license_url, - discord_url: project_create_data.discord_url, categories, additional_categories, initial_versions: versions, @@ -715,7 +692,7 @@ async fn project_create_inner( requested_status: Some(project_create_data.requested_status), license: license_id.to_string(), slug: Some(project_create_data.slug), - donation_urls, + link_urls, gallery_items: gallery_urls .iter() .map(|x| models::project_item::GalleryItem { @@ -835,11 +812,12 @@ async fn project_create_inner( .map(|v| v.version_id.into()) .collect::>(), icon_url: project_builder.icon_url.clone(), - issues_url: project_builder.issues_url.clone(), - source_url: project_builder.source_url.clone(), - wiki_url: project_builder.wiki_url.clone(), - discord_url: project_builder.discord_url.clone(), - donation_urls: project_create_data.donation_urls.clone(), + link_urls: project_builder + .link_urls + .clone() + .into_iter() + .map(|x| (x.platform_name.clone(), Link::from(x))) + .collect(), gallery: gallery_urls, color: project_builder.color, thread_id: thread_id.into(), diff --git a/src/routes/v3/projects.rs b/src/routes/v3/projects.rs index 6106f661..ae91fcaf 100644 --- a/src/routes/v3/projects.rs +++ b/src/routes/v3/projects.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::Arc; use crate::auth::{filter_authorized_projects, get_user_from_headers, is_authorized}; @@ -14,7 +15,7 @@ use crate::models::images::ImageContext; use crate::models::notifications::NotificationBody; use crate::models::pats::Scopes; use crate::models::projects::{ - DonationLink, MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, + MonetizationStatus, Project, ProjectId, ProjectStatus, SearchRequest, }; use crate::models::teams::ProjectPermissions; use crate::models::threads::MessageBody; @@ -192,49 +193,10 @@ pub struct EditProject { custom(function = "crate::util::validate::validate_url"), length(max = 2048) )] - pub issues_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub source_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub wiki_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] pub license_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub discord_url: Option>, - #[validate] - pub donation_urls: Option>, + #[validate(custom(function = "crate::util::validate::validate_url_hashmap_optional_values"))] + // (leave url empty to delete) + pub link_urls: Option>>, pub license_id: Option, #[validate( length(min = 3, max = 64), @@ -592,69 +554,6 @@ pub async fn project_edit( .await?; } - if let Some(issues_url) = &new_project.issues_url { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the issues URL of this project!" - .to_string(), - )); - } - - sqlx::query!( - " - UPDATE mods - SET issues_url = $1 - WHERE (id = $2) - ", - issues_url.as_deref(), - id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - - if let Some(source_url) = &new_project.source_url { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the source URL of this project!" - .to_string(), - )); - } - - sqlx::query!( - " - UPDATE mods - SET source_url = $1 - WHERE (id = $2) - ", - source_url.as_deref(), - id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - - if let Some(wiki_url) = &new_project.wiki_url { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the wiki URL of this project!" - .to_string(), - )); - } - - sqlx::query!( - " - UPDATE mods - SET wiki_url = $1 - WHERE (id = $2) - ", - wiki_url.as_deref(), - id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - if let Some(license_url) = &new_project.license_url { if !perms.contains(ProjectPermissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( @@ -676,27 +575,6 @@ pub async fn project_edit( .await?; } - if let Some(discord_url) = &new_project.discord_url { - if !perms.contains(ProjectPermissions::EDIT_DETAILS) { - return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the discord URL of this project!" - .to_string(), - )); - } - - sqlx::query!( - " - UPDATE mods - SET discord_url = $1 - WHERE (id = $2) - ", - discord_url.as_deref(), - id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - if let Some(slug) = &new_project.slug { if !perms.contains(ProjectPermissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( @@ -785,51 +663,59 @@ pub async fn project_edit( .execute(&mut *transaction) .await?; } - if let Some(donations) = &new_project.donation_urls { + if let Some(links) = &new_project.link_urls { if !perms.contains(ProjectPermissions::EDIT_DETAILS) { return Err(ApiError::CustomAuthentication( - "You do not have the permissions to edit the donation links of this project!" + "You do not have the permissions to edit the links of this project!" .to_string(), )); } + let ids_to_delete = links + .iter() + .map(|(name, _)| name.clone()) + .collect::>(); + // Deletes all links from hashmap- either will be deleted or be replaced sqlx::query!( " - DELETE FROM mods_donations - WHERE joining_mod_id = $1 + DELETE FROM mods_links + WHERE joining_mod_id = $1 AND joining_platform_id IN ( + SELECT id FROM link_platforms WHERE name = ANY($2) + ) ", id as db_ids::ProjectId, + &ids_to_delete ) .execute(&mut *transaction) .await?; - for donation in donations { - let platform_id = db_models::categories::DonationPlatform::get_id( - &donation.id, - &mut *transaction, - ) - .await? - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Platform {} does not exist.", - donation.id.clone() - )) - })?; - - sqlx::query!( - " - INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) - VALUES ($1, $2, $3) - ", - id as db_ids::ProjectId, - platform_id as db_ids::DonationPlatformId, - donation.url - ) - .execute(&mut *transaction) - .await?; + for (platform, url) in links { + if let Some(url) = url { + let platform_id = db_models::categories::LinkPlatform::get_id( + platform, + &mut *transaction, + ) + .await? + .ok_or_else(|| { + ApiError::InvalidInput(format!( + "Platform {} does not exist.", + platform.clone() + )) + })?; + sqlx::query!( + " + INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) + VALUES ($1, $2, $3) + ", + id as db_ids::ProjectId, + platform_id as db_ids::LinkPlatformId, + url + ) + .execute(&mut *transaction) + .await?; + } } } - if let Some(moderation_message) = &new_project.moderation_message { if !user.role.is_mod() && (!project_item.inner.status.is_approved() || moderation_message.is_some()) @@ -1139,53 +1025,8 @@ pub struct BulkEditProject { pub add_additional_categories: Option>, pub remove_additional_categories: Option>, - #[validate] - pub donation_urls: Option>, - #[validate] - pub add_donation_urls: Option>, - #[validate] - pub remove_donation_urls: Option>, - - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub issues_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub source_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub wiki_url: Option>, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "::serde_with::rust::double_option" - )] - #[validate( - custom(function = "crate::util::validate::validate_url"), - length(max = 2048) - )] - pub discord_url: Option>, + #[validate(custom(function = " crate::util::validate::validate_url_hashmap_optional_values"))] + pub link_urls: Option>>, } pub async fn projects_edit( @@ -1250,7 +1091,7 @@ pub async fn projects_edit( .await?; let categories = db_models::categories::Category::list(&**pool, &redis).await?; - let donation_platforms = db_models::categories::DonationPlatform::list(&**pool, &redis).await?; + let link_platforms = db_models::categories::LinkPlatform::list(&**pool, &redis).await?; let mut transaction = pool.begin().await?; @@ -1330,128 +1171,50 @@ pub async fn projects_edit( ) .await?; - let project_donations: Vec = project - .donation_urls - .into_iter() - .map(|d| DonationLink { - id: d.platform_short, - platform: d.platform_name, - url: d.url, - }) - .collect(); - let mut set_donation_links = - if let Some(donation_links) = bulk_edit_project.donation_urls.clone() { - donation_links - } else { - project_donations.clone() - }; - - if let Some(delete_donations) = &bulk_edit_project.remove_donation_urls { - for donation in delete_donations { - if let Some(pos) = set_donation_links - .iter() - .position(|x| donation.url == x.url && donation.id == x.id) - { - set_donation_links.remove(pos); - } - } - } - - if let Some(add_donations) = &bulk_edit_project.add_donation_urls { - set_donation_links.append(&mut add_donations.clone()); - } - - if set_donation_links != project_donations { + if let Some(links) = &bulk_edit_project.link_urls { + let ids_to_delete = links + .iter() + .map(|(name, _)| name.clone()) + .collect::>(); + // Deletes all links from hashmap- either will be deleted or be replaced sqlx::query!( " - DELETE FROM mods_donations - WHERE joining_mod_id = $1 - ", - project.inner.id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - - for donation in set_donation_links { - let platform_id = donation_platforms - .iter() - .find(|x| x.short == donation.id) - .ok_or_else(|| { - ApiError::InvalidInput(format!( - "Platform {} does not exist.", - donation.id.clone() - )) - })? - .id; - - sqlx::query!( - " - INSERT INTO mods_donations (joining_mod_id, joining_platform_id, url) - VALUES ($1, $2, $3) - ", - project.inner.id as db_ids::ProjectId, - platform_id as db_ids::DonationPlatformId, - donation.url + DELETE FROM mods_links + WHERE joining_mod_id = $1 AND joining_platform_id IN ( + SELECT id FROM link_platforms WHERE name = ANY($2) ) - .execute(&mut *transaction) - .await?; - } - } - - if let Some(issues_url) = &bulk_edit_project.issues_url { - sqlx::query!( - " - UPDATE mods - SET issues_url = $1 - WHERE (id = $2) ", - issues_url.as_deref(), project.inner.id as db_ids::ProjectId, + &ids_to_delete ) .execute(&mut *transaction) .await?; - } - if let Some(source_url) = &bulk_edit_project.source_url { - sqlx::query!( - " - UPDATE mods - SET source_url = $1 - WHERE (id = $2) - ", - source_url.as_deref(), - project.inner.id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - - if let Some(wiki_url) = &bulk_edit_project.wiki_url { - sqlx::query!( - " - UPDATE mods - SET wiki_url = $1 - WHERE (id = $2) - ", - wiki_url.as_deref(), - project.inner.id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; - } - - if let Some(discord_url) = &bulk_edit_project.discord_url { - sqlx::query!( - " - UPDATE mods - SET discord_url = $1 - WHERE (id = $2) - ", - discord_url.as_deref(), - project.inner.id as db_ids::ProjectId, - ) - .execute(&mut *transaction) - .await?; + for (platform, url) in links { + if let Some(url) = url { + let platform_id = link_platforms + .iter() + .find(|x| &x.name == platform) + .ok_or_else(|| { + ApiError::InvalidInput(format!( + "Platform {} does not exist.", + platform.clone() + )) + })? + .id; + sqlx::query!( + " + INSERT INTO mods_links (joining_mod_id, joining_platform_id, url) + VALUES ($1, $2, $3) + ", + project.inner.id as db_ids::ProjectId, + platform_id as db_ids::LinkPlatformId, + url + ) + .execute(&mut *transaction) + .await?; + } + } } db_models::Project::clear_cache(project.inner.id, project.inner.slug, None, &redis).await?; diff --git a/src/routes/v3/tags.rs b/src/routes/v3/tags.rs index 3c0ecd20..8d4cfdf6 100644 --- a/src/routes/v3/tags.rs +++ b/src/routes/v3/tags.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use super::ApiError; -use crate::database::models::categories::{Category, DonationPlatform, ProjectType, ReportType}; +use crate::database::models::categories::{Category, LinkPlatform, ProjectType, ReportType}; use crate::database::models::loader_fields::{ Game, Loader, LoaderField, LoaderFieldEnumValue, LoaderFieldType, }; @@ -22,7 +22,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route("loader_field", web::get().to(loader_fields_list)) .route("license", web::get().to(license_list)) .route("license/{id}", web::get().to(license_text)) - .route("donation_platform", web::get().to(donation_platform_list)) + .route("link_platform", web::get().to(link_platform_list)) .route("report_type", web::get().to(report_type_list)) .route("project_type", web::get().to(project_type_list)); } @@ -214,23 +214,19 @@ pub async fn license_text(params: web::Path<(String,)>) -> Result, redis: web::Data, ) -> Result { - let results: Vec = DonationPlatform::list(&**pool, &redis) + let results: Vec = LinkPlatform::list(&**pool, &redis) .await? .into_iter() - .map(|x| DonationPlatformQueryData { - short: x.short, - name: x.name, - }) + .map(|x| LinkPlatformQueryData { name: x.name }) .collect(); Ok(HttpResponse::Ok().json(results)) } diff --git a/src/util/validate.rs b/src/util/validate.rs index 83b24ba1..93ae5087 100644 --- a/src/util/validate.rs +++ b/src/util/validate.rs @@ -93,6 +93,26 @@ pub fn validate_url(value: &str) -> Result<(), validator::ValidationError> { Ok(()) } +pub fn validate_url_hashmap_optional_values( + values: &std::collections::HashMap>, +) -> Result<(), validator::ValidationError> { + for value in values.values().flatten() { + validate_url(value)?; + } + + Ok(()) +} + +pub fn validate_url_hashmap_values( + values: &std::collections::HashMap, +) -> Result<(), validator::ValidationError> { + for value in values.values() { + validate_url(value)?; + } + + Ok(()) +} + pub fn validate_no_restricted_scopes(value: &Scopes) -> Result<(), validator::ValidationError> { if value.is_restricted() { return Err(validator::ValidationError::new( diff --git a/tests/common/api_common/models.rs b/tests/common/api_common/models.rs index 40fbb8ab..a186b76d 100644 --- a/tests/common/api_common/models.rs +++ b/tests/common/api_common/models.rs @@ -3,8 +3,8 @@ use labrinth::models::{ notifications::{NotificationAction, NotificationBody, NotificationId}, organizations::OrganizationId, projects::{ - Dependency, DonationLink, GalleryItem, License, ModeratorMessage, MonetizationStatus, - ProjectId, ProjectStatus, VersionFile, VersionId, VersionStatus, VersionType, + Dependency, GalleryItem, License, ModeratorMessage, MonetizationStatus, ProjectId, + ProjectStatus, VersionFile, VersionId, VersionStatus, VersionType, }, teams::{OrganizationPermissions, ProjectPermissions, TeamId}, threads::ThreadId, @@ -52,11 +52,6 @@ pub struct CommonProject { pub loaders: Vec, pub versions: Vec, pub icon_url: Option, - pub issues_url: Option, - pub source_url: Option, - pub wiki_url: Option, - pub discord_url: Option, - pub donation_urls: Option>, pub gallery: Vec, pub color: Option, pub thread_id: ThreadId, diff --git a/tests/project.rs b/tests/project.rs index 07c1343d..238e5f43 100644 --- a/tests/project.rs +++ b/tests/project.rs @@ -271,7 +271,7 @@ async fn test_add_remove_project() { #[actix_rt::test] pub async fn test_patch_project() { - with_test_environment_all(None, |test_env| async move { + with_test_environment(None, |test_env: TestEnvironment| async move { let api = &test_env.api; let alpha_project_slug = &test_env.dummy.as_ref().unwrap().project_alpha.project_slug; @@ -291,12 +291,14 @@ pub async fn test_patch_project() { assert_eq!(resp.status(), 401); // Failure because we are setting URL fields to invalid urls. - for url_type in ["issues_url", "source_url", "wiki_url", "discord_url"] { + for url_type in ["issues", "source", "wiki", "discord"] { let resp = api .edit_project( alpha_project_slug, json!({ - url_type: "w.fake.url", + "link_urls": { + url_type: "not a url", + }, }), USER_USER_PAT, ) @@ -391,18 +393,18 @@ pub async fn test_patch_project() { "body": "New successful body", "categories": [DUMMY_CATEGORIES[0]], "license_id": "MIT", - "issues_url": "https://github.com", - "discord_url": "https://discord.gg", - "wiki_url": "https://wiki.com", - "donation_urls": [{ - "id": "patreon", - "platform": "Patreon", - "url": "https://patreon.com" - }] + "link_urls": + { + "patreon": "https://patreon.com", + "issues": "https://github.com", + "discord": "https://discord.gg", + "wiki": "https://wiki.com" + } }), USER_USER_PAT, ) .await; + println!("{:?}", resp.response().body()); assert_eq!(resp.status(), 204); // Old slug no longer works @@ -410,9 +412,7 @@ pub async fn test_patch_project() { assert_eq!(resp.status(), 404); // New slug does work - let project = api - .get_project_deserialized_common("newslug", USER_USER_PAT) - .await; + let project = api.get_project_deserialized("newslug", USER_USER_PAT).await; assert_eq!(project.slug.unwrap(), "newslug"); assert_eq!(project.title, "New successful title"); @@ -420,13 +420,40 @@ pub async fn test_patch_project() { assert_eq!(project.body, "New successful body"); assert_eq!(project.categories, vec![DUMMY_CATEGORIES[0]]); assert_eq!(project.license.id, "MIT"); - assert_eq!(project.issues_url, Some("https://github.com".to_string())); - assert_eq!(project.discord_url, Some("https://discord.gg".to_string())); - assert_eq!(project.wiki_url, Some("https://wiki.com".to_string())); - assert_eq!(project.donation_urls.unwrap()[0].url, "https://patreon.com"); - // TODO: - // for loader fields? + let link_urls = project.link_urls; + assert_eq!(link_urls.len(), 4); + assert_eq!(link_urls["patreon"].platform, "patreon"); + assert_eq!(link_urls["patreon"].url, "https://patreon.com"); + assert!(link_urls["patreon"].donation); + assert_eq!(link_urls["issues"].platform, "issues"); + assert_eq!(link_urls["issues"].url, "https://github.com"); + assert!(!link_urls["issues"].donation); + assert_eq!(link_urls["discord"].platform, "discord"); + assert_eq!(link_urls["discord"].url, "https://discord.gg"); + assert!(!link_urls["discord"].donation); + assert_eq!(link_urls["wiki"].platform, "wiki"); + assert_eq!(link_urls["wiki"].url, "https://wiki.com"); + assert!(!link_urls["wiki"].donation); + + // Unset the set link_urls + let resp = api + .edit_project( + "newslug", + json!({ + "link_urls": + { + "issues": null, + } + }), + USER_USER_PAT, + ) + .await; + println!("{:?}", resp.response().body()); + assert_eq!(resp.status(), 204); + let project = api.get_project_deserialized("newslug", USER_USER_PAT).await; + assert_eq!(project.link_urls.len(), 3); + assert!(!project.link_urls.contains_key("issues")); }) .await; } @@ -472,6 +499,57 @@ pub async fn test_bulk_edit_categories() { .await; } +#[actix_rt::test] +pub async fn test_bulk_edit_links() { + with_test_environment(None, |test_env: TestEnvironment| async move { + let api = &test_env.api; + let alpha_project_id: &str = &test_env.dummy.as_ref().unwrap().project_alpha.project_id; + let beta_project_id: &str = &test_env.dummy.as_ref().unwrap().project_beta.project_id; + + // Sets links for issue, source, wiki, and patreon for all projects + // The first loop, sets issue, the second, clears it for all projects. + for issues in [Some("https://www.issues.com"), None] { + let resp = api + .edit_project_bulk( + &[alpha_project_id, beta_project_id], + json!({ + "link_urls": { + "issues": issues, + "wiki": "https://wiki.com", + "patreon": "https://patreon.com", + }, + }), + ADMIN_USER_PAT, + ) + .await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let alpha_body = api + .get_project_deserialized(alpha_project_id, ADMIN_USER_PAT) + .await; + if let Some(issues) = issues { + assert_eq!(alpha_body.link_urls.len(), 3); + assert_eq!(alpha_body.link_urls["issues"].url, issues); + } else { + assert_eq!(alpha_body.link_urls.len(), 2); + assert!(!alpha_body.link_urls.contains_key("issues")); + } + assert_eq!(alpha_body.link_urls["wiki"].url, "https://wiki.com"); + assert_eq!(alpha_body.link_urls["patreon"].url, "https://patreon.com"); + + let beta_body = api + .get_project_deserialized(beta_project_id, ADMIN_USER_PAT) + .await; + assert_eq!(beta_body.categories, alpha_body.categories); + assert_eq!( + beta_body.additional_categories, + alpha_body.additional_categories, + ); + } + }) + .await; +} + #[actix_rt::test] async fn permissions_patch_project() { with_test_environment_all(Some(8), |test_env| async move { diff --git a/tests/v2/project.rs b/tests/v2/project.rs index fe01e991..490779f6 100644 --- a/tests/v2/project.rs +++ b/tests/v2/project.rs @@ -4,11 +4,15 @@ use crate::common::{ request_data::{get_public_project_creation_data_json, get_public_version_creation_data}, ApiV2, }, - database::{ENEMY_USER_PAT, FRIEND_USER_ID, FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT}, + database::{ + ADMIN_USER_PAT, ENEMY_USER_PAT, FRIEND_USER_ID, FRIEND_USER_PAT, MOD_USER_PAT, + USER_USER_PAT, + }, dummy_data::{TestFile, DUMMY_CATEGORIES}, environment::{with_test_environment, TestEnvironment}, permissions::{PermissionsTest, PermissionsTestContext}, }; +use actix_http::StatusCode; use actix_web::test; use itertools::Itertools; use labrinth::{ @@ -475,7 +479,6 @@ pub async fn test_patch_project() { // New slug does work let project = api.get_project_deserialized("newslug", USER_USER_PAT).await; - assert_eq!(project.slug.unwrap(), "newslug"); assert_eq!(project.title, "New successful title"); assert_eq!(project.description, "New successful description"); @@ -495,3 +498,169 @@ pub async fn test_patch_project() { }) .await; } + +#[actix_rt::test] +pub async fn test_bulk_edit_links() { + with_test_environment(None, |test_env: TestEnvironment| async move { + let api = &test_env.api; + let alpha_project_id: &str = &test_env.dummy.as_ref().unwrap().project_alpha.project_id; + let beta_project_id: &str = &test_env.dummy.as_ref().unwrap().project_beta.project_id; + + let resp = api + .edit_project_bulk( + &[alpha_project_id, beta_project_id], + json!({ + "issues_url": "https://github.com", + "donation_urls": [ + { + "id": "patreon", + "platform": "Patreon", + "url": "https://www.patreon.com/my_user" + } + ], + }), + ADMIN_USER_PAT, + ) + .await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let alpha_body = api + .get_project_deserialized(alpha_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = alpha_body.donation_urls.unwrap(); + assert_eq!(donation_urls.len(), 1); + assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user"); + assert_eq!( + alpha_body.issues_url, + Some("https://github.com".to_string()) + ); + assert_eq!(alpha_body.discord_url, None); + + let beta_body = api + .get_project_deserialized(beta_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = beta_body.donation_urls.unwrap(); + assert_eq!(donation_urls.len(), 1); + assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user"); + assert_eq!(beta_body.issues_url, Some("https://github.com".to_string())); + assert_eq!(beta_body.discord_url, None); + + let resp = api + .edit_project_bulk( + &[alpha_project_id, beta_project_id], + json!({ + "discord_url": "https://discord.gg", + "issues_url": null, + "add_donation_urls": [ + { + "id": "bmac", + "platform": "Buy Me a Coffee", + "url": "https://www.buymeacoffee.com/my_user" + } + ], + }), + ADMIN_USER_PAT, + ) + .await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let alpha_body = api + .get_project_deserialized(alpha_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = alpha_body + .donation_urls + .unwrap() + .into_iter() + .sorted_by_key(|x| x.id.clone()) + .collect_vec(); + assert_eq!(donation_urls.len(), 2); + assert_eq!(donation_urls[0].url, "https://www.buymeacoffee.com/my_user"); + assert_eq!(donation_urls[1].url, "https://www.patreon.com/my_user"); + assert_eq!(alpha_body.issues_url, None); + assert_eq!( + alpha_body.discord_url, + Some("https://discord.gg".to_string()) + ); + + let beta_body = api + .get_project_deserialized(beta_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = beta_body + .donation_urls + .unwrap() + .into_iter() + .sorted_by_key(|x| x.id.clone()) + .collect_vec(); + assert_eq!(donation_urls.len(), 2); + assert_eq!(donation_urls[0].url, "https://www.buymeacoffee.com/my_user"); + assert_eq!(donation_urls[1].url, "https://www.patreon.com/my_user"); + assert_eq!(alpha_body.issues_url, None); + assert_eq!( + alpha_body.discord_url, + Some("https://discord.gg".to_string()) + ); + + let resp = api + .edit_project_bulk( + &[alpha_project_id, beta_project_id], + json!({ + "donation_urls": [ + { + "id": "patreon", + "platform": "Patreon", + "url": "https://www.patreon.com/my_user" + }, + { + "id": "ko-fi", + "platform": "Ko-fi", + "url": "https://www.ko-fi.com/my_user" + } + ], + "add_donation_urls": [ + { + "id": "paypal", + "platform": "PayPal", + "url": "https://www.paypal.com/my_user" + } + ], + "remove_donation_urls": [ + { + "id": "ko-fi", + "platform": "Ko-fi", + "url": "https://www.ko-fi.com/my_user" + } + ], + }), + ADMIN_USER_PAT, + ) + .await; + assert_eq!(resp.status(), StatusCode::NO_CONTENT); + + let alpha_body = api + .get_project_deserialized(alpha_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = alpha_body + .donation_urls + .unwrap() + .into_iter() + .sorted_by_key(|x| x.id.clone()) + .collect_vec(); + assert_eq!(donation_urls.len(), 2); + assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user"); + assert_eq!(donation_urls[1].url, "https://www.paypal.com/my_user"); + + let beta_body = api + .get_project_deserialized(beta_project_id, ADMIN_USER_PAT) + .await; + let donation_urls = beta_body + .donation_urls + .unwrap() + .into_iter() + .sorted_by_key(|x| x.id.clone()) + .collect_vec(); + assert_eq!(donation_urls.len(), 2); + assert_eq!(donation_urls[0].url, "https://www.patreon.com/my_user"); + assert_eq!(donation_urls[1].url, "https://www.paypal.com/my_user"); + }) + .await; +}