From 820519b4f7b7f0ce6ae5afc89be4cf10f5c1eada Mon Sep 17 00:00:00 2001 From: triphora Date: Tue, 29 Nov 2022 23:53:24 -0500 Subject: [PATCH] Move to SPDX licenses (#449) * Move to SPDX licenses Found a way to do this without breaking API compat, so here it is, instead of waiting for v3 Resolves MOD-129 Resolves #396 * License URL updates * what was I thinking * Do a thing * Add open source filter * Remove dead imports * Borrow * Update 20220910132835_spdx-licenses.sql * Add license text route * Update migration * Address comments --- Cargo.lock | 10 + Cargo.toml | 1 + migrations/20221126222222_spdx-licenses.sql | 27 + sqlx-data.json | 1339 +++++++++---------- src/database/models/categories.rs | 157 --- src/database/models/ids.rs | 3 - src/database/models/project_item.rs | 30 +- src/models/projects.rs | 22 +- src/routes/project_creation.rs | 16 +- src/routes/projects.rs | 20 +- src/routes/tags.rs | 106 +- src/routes/v1/mod.rs | 2 - src/search/indexing/local_import.rs | 20 +- src/search/indexing/mod.rs | 1 + src/search/mod.rs | 1 + 15 files changed, 748 insertions(+), 1007 deletions(-) create mode 100644 migrations/20221126222222_spdx-licenses.sql diff --git a/Cargo.lock b/Cargo.lock index 8b2ae8b71..7a82d3481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,6 +1566,7 @@ dependencies = [ "serde_with", "sha1 0.6.1", "sha2 0.9.9", + "spdx", "sqlx", "thiserror", "tokio", @@ -2781,6 +2782,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "spdx" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a346909b3fd07776f9b96b98d4a58e3666f831c9a672c279b10f795a34c36425" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 99938d512..9e8a7fbe7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ itertools = "0.10.5" validator = { version = "0.16.0", features = ["derive", "phone"] } regex = "1.6.0" censor = "0.2.0" +spdx = { version = "0.9.0", features = ["text"] } dotenvy = "0.15.6" log = "0.4.17" diff --git a/migrations/20221126222222_spdx-licenses.sql b/migrations/20221126222222_spdx-licenses.sql new file mode 100644 index 000000000..fd088c3f9 --- /dev/null +++ b/migrations/20221126222222_spdx-licenses.sql @@ -0,0 +1,27 @@ +ALTER TABLE mods ADD COLUMN license_new varchar(2048) DEFAULT 'LicenseRef-All-Rights-Reserved' NOT NULL; + +UPDATE mods SET license_new = licenses.short FROM licenses WHERE mods.license = licenses.id; + +UPDATE mods SET license_new = 'LicenseRef-Custom' WHERE license_new = 'custom'; +UPDATE mods SET license_new = 'LicenseRef-All-Rights-Reserved' WHERE license_new = 'arr'; +UPDATE mods SET license_new = 'Apache-2.0' WHERE license_new = 'apache'; +UPDATE mods SET license_new = 'BSD-2-Clause' WHERE license_new = 'bsd-2-clause'; +UPDATE mods SET license_new = 'BSD-3-Clause' WHERE license_new = 'bsd-3-clause' OR license_new = 'bsd'; +UPDATE mods SET license_new = 'CC0-1.0' WHERE license_new = 'cc0'; +UPDATE mods SET license_new = 'Unlicense' WHERE license_new = 'unlicense'; +UPDATE mods SET license_new = 'MIT' WHERE license_new = 'mit'; +UPDATE mods SET license_new = 'LGPL-3.0-only' WHERE license_new = 'lgpl-3'; +UPDATE mods SET license_new = 'LGPL-2.1-only' WHERE license_new = 'lgpl-2.1' OR license_new = 'lgpl'; +UPDATE mods SET license_new = 'MPL-2.0' WHERE license_new = 'mpl-2'; +UPDATE mods SET license_new = 'ISC' WHERE license_new = 'isc'; +UPDATE mods SET license_new = 'Zlib' WHERE license_new = 'zlib'; +UPDATE mods SET license_new = 'GPL-2.0-only' WHERE license_new = 'gpl-2'; +UPDATE mods SET license_new = 'GPL-3.0-only' WHERE license_new = 'gpl-3'; +UPDATE mods SET license_new = 'AGPL-3.0-only' WHERE license_new = 'agpl'; + +UPDATE mods SET license_url = NULL WHERE license_url LIKE 'https://cdn.modrinth.com/licenses/%'; + +ALTER TABLE mods DROP COLUMN license; +ALTER TABLE mods RENAME COLUMN license_new TO license; + +DROP TABLE licenses; diff --git a/sqlx-data.json b/sqlx-data.json index e44bb09ca..1f7f8dce5 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -538,7 +538,7 @@ "nullable": [], "parameters": { "Left": [ - "Int4", + "Varchar", "Int8" ] } @@ -822,32 +822,6 @@ }, "query": "\n UPDATE team_members\n SET permissions = $1\n WHERE (team_id = $2 AND user_id = $3)\n " }, - "24e5daad907eec54505274f93952d5c20f4bbdd3f771eb0a2fdfa6324768df39": { - "describe": { - "columns": [ - { - "name": "short", - "ordinal": 0, - "type_info": "Varchar" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [ - "Int4" - ] - } - }, - "query": "\n SELECT short, name FROM licenses\n WHERE id = $1\n " - }, "250e7deb143373fc326bb54cf6f82b9790051329b5beb55f5942673d432e2c24": { "describe": { "columns": [ @@ -995,6 +969,154 @@ }, "query": "INSERT INTO banned_users (github_id) VALUES ($1);" }, + "28ef29e10d4a46dae0bbacd276f6efc1abfa5dbc10981257ccb1d82fc2484af2": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 7, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 8, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "team_id", + "ordinal": 10, + "type_info": "Int8" + }, + { + "name": "license", + "ordinal": 11, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 12, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "username", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "categories", + "ordinal": 18, + "type_info": "TextArray" + }, + { + "name": "loaders", + "ordinal": 19, + "type_info": "VarcharArray" + }, + { + "name": "versions", + "ordinal": 20, + "type_info": "VarcharArray" + }, + { + "name": "gallery", + "ordinal": 21, + "type_info": "VarcharArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + false, + false, + false, + false, + false, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + } + }, + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published, m.approved approved, m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null) gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1 OR s.status = $2\n GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id;\n " + }, "292da3eec2cc7d7eb635fa123be1b1387e9e91466f007e10101053fdb9874e3f": { "describe": { "columns": [ @@ -1048,18 +1170,6 @@ }, "query": "\n INSERT INTO loaders_versions (loader_id, version_id)\n VALUES ($1, $2)\n " }, - "2abecb467a9ad3b792babf20e09601c011fc2622e101e98054baeaacaa16795a": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n DELETE FROM licenses\n WHERE short = $1\n " - }, "2b8dafe9c3df9fd25235a13868e8e7607decfbe96a413cc576919a1fb510f269": { "describe": { "columns": [], @@ -1654,242 +1764,6 @@ }, "query": "\n INSERT INTO game_versions_versions (game_version_id, joining_version_id)\n VALUES ($1, $2)\n " }, - "3fd03e7fa83a108d7d2720607e9fce0e2aab060c441d8fbdec3404a379098138": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 23, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 24, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "short", - "ordinal": 28, - "type_info": "Varchar" - }, - { - "name": "license_name", - "ordinal": 29, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 30, - "type_info": "Varchar" - }, - { - "name": "flame_anvil_project", - "ordinal": 31, - "type_info": "Int4" - }, - { - "name": "flame_anvil_user", - "ordinal": 32, - "type_info": "Int8" - }, - { - "name": "categories", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 34, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 35, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 36, - "type_info": "TextArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - true, - true, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " - }, "40f7c5bec98fe3503d6bd6db2eae5a4edb8d5d6efda9b9dc124f344ae5c60e08": { "describe": { "columns": [], @@ -2005,6 +1879,230 @@ }, "query": "\n INSERT INTO loaders_project_types (joining_loader_id, joining_project_type_id)\n VALUES ($1, $2)\n " }, + "46629cac59f5e86839b58d48239b65fade89598c613607e1db557e7b8b20b937": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 18, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 21, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 29, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 30, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 31, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 32, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 34, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + true, + true, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8Array" + ] + } + }, + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, s.id, cs.id, ss.id, m.id;\n " + }, "4778d2f5994fda2f978fa53e0840c1a9a2582ef0434a5ff7f21706f1dc4edcf4": { "describe": { "columns": [], @@ -2743,36 +2841,6 @@ }, "query": "\n INSERT INTO dependencies (dependent_id, dependency_type, dependency_id, mod_dependency_id, dependency_file_name)\n VALUES ($1, $2, $3, $4, $5)\n " }, - "6131d32a65f5e04775308386812f25c6d8464582678536a392a4a3737667f363": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - }, - { - "name": "short", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "name", - "ordinal": 2, - "type_info": "Varchar" - } - ], - "nullable": [ - false, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "\n SELECT id, short, name FROM licenses\n " - }, "61a7f29e024bf2f1368370e3f6e8ef70317c7e8545b5b6d4235f21164948ba27": { "describe": { "columns": [], @@ -3086,7 +3154,7 @@ { "name": "license", "ordinal": 20, - "type_info": "Int4" + "type_info": "Varchar" }, { "name": "slug", @@ -3462,7 +3530,7 @@ "Int4", "Int4", "Varchar", - "Int4", + "Varchar", "Text", "Int4" ] @@ -3583,27 +3651,6 @@ }, "query": "\n INSERT INTO notifications_actions (\n notification_id, title, action_route, action_route_method\n )\n VALUES (\n $1, $2, $3, $4\n )\n " }, - "82515e4e7e88f1193c956f032caabc70f535f925e212de30f974afd3ec126092": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Varchar", - "Varchar" - ] - } - }, - "query": "\n INSERT INTO licenses (short, name)\n VALUES ($1, $2)\n ON CONFLICT (short) DO NOTHING\n RETURNING id\n " - }, "844735a1ffd4b17c96e0cc441c85b3d05325523b415c509bbf2860b7e1ca0de3": { "describe": { "columns": [ @@ -4226,7 +4273,7 @@ { "name": "license", "ordinal": 21, - "type_info": "Int4" + "type_info": "Varchar" }, { "name": "slug", @@ -4778,160 +4825,6 @@ }, "query": "\n UPDATE users\n SET balance = balance - $1\n WHERE id = $2\n " }, - "b41ba860c9d5402ba78297800b9df632a45718f5680a4e96d05372e59466ed7d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 7, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 8, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "team_id", - "ordinal": 10, - "type_info": "Int8" - }, - { - "name": "license", - "ordinal": 11, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 12, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "short", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "username", - "ordinal": 18, - "type_info": "Varchar" - }, - { - "name": "categories", - "ordinal": 19, - "type_info": "TextArray" - }, - { - "name": "loaders", - "ordinal": 20, - "type_info": "VarcharArray" - }, - { - "name": "versions", - "ordinal": 21, - "type_info": "VarcharArray" - }, - { - "name": "gallery", - "ordinal": 22, - "type_info": "VarcharArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - false, - true, - false, - false, - false, - false, - false, - false, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - } - }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.published published, m.approved approved, m.updated updated,\n m.team_id team_id, m.license license, m.slug slug,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders,\n ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url) filter (where mg.image_url is not null) gallery\n FROM mods m\n LEFT OUTER JOIN mods_categories mc ON joining_mod_id = m.id\n LEFT OUTER JOIN categories c ON mc.joining_category_id = c.id\n LEFT OUTER JOIN versions v ON v.mod_id = m.id\n LEFT OUTER JOIN game_versions_versions gvv ON gvv.joining_version_id = v.id\n LEFT OUTER JOIN game_versions gv ON gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv ON lv.version_id = v.id\n LEFT OUTER JOIN loaders lo ON lo.id = lv.loader_id\n LEFT OUTER JOIN mods_gallery mg ON mg.mod_id = m.id\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE\n INNER JOIN users u ON tm.user_id = u.id\n WHERE s.status = $1 OR s.status = $2\n GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id;\n " - }, "b69a6f42965b3e7103fcbf46e39528466926789ff31e9ed2591bb175527ec169": { "describe": { "columns": [], @@ -5973,242 +5866,6 @@ }, "query": "\n DELETE FROM team_members\n WHERE team_id = $1\n " }, - "d13a0ae0f7600a96353ef0555b2f9bf4e254f5f9d3f243a18c04bdb8b6d0322e": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_type", - "ordinal": 1, - "type_info": "Int4" - }, - { - "name": "title", - "ordinal": 2, - "type_info": "Varchar" - }, - { - "name": "description", - "ordinal": 3, - "type_info": "Varchar" - }, - { - "name": "downloads", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "follows", - "ordinal": 5, - "type_info": "Int4" - }, - { - "name": "icon_url", - "ordinal": 6, - "type_info": "Varchar" - }, - { - "name": "body", - "ordinal": 7, - "type_info": "Varchar" - }, - { - "name": "body_url", - "ordinal": 8, - "type_info": "Varchar" - }, - { - "name": "published", - "ordinal": 9, - "type_info": "Timestamptz" - }, - { - "name": "updated", - "ordinal": 10, - "type_info": "Timestamptz" - }, - { - "name": "approved", - "ordinal": 11, - "type_info": "Timestamptz" - }, - { - "name": "status", - "ordinal": 12, - "type_info": "Int4" - }, - { - "name": "issues_url", - "ordinal": 13, - "type_info": "Varchar" - }, - { - "name": "source_url", - "ordinal": 14, - "type_info": "Varchar" - }, - { - "name": "wiki_url", - "ordinal": 15, - "type_info": "Varchar" - }, - { - "name": "discord_url", - "ordinal": 16, - "type_info": "Varchar" - }, - { - "name": "license_url", - "ordinal": 17, - "type_info": "Varchar" - }, - { - "name": "team_id", - "ordinal": 18, - "type_info": "Int8" - }, - { - "name": "client_side", - "ordinal": 19, - "type_info": "Int4" - }, - { - "name": "server_side", - "ordinal": 20, - "type_info": "Int4" - }, - { - "name": "license", - "ordinal": 21, - "type_info": "Int4" - }, - { - "name": "slug", - "ordinal": 22, - "type_info": "Varchar" - }, - { - "name": "moderation_message", - "ordinal": 23, - "type_info": "Varchar" - }, - { - "name": "moderation_message_body", - "ordinal": 24, - "type_info": "Varchar" - }, - { - "name": "status_name", - "ordinal": 25, - "type_info": "Varchar" - }, - { - "name": "client_side_type", - "ordinal": 26, - "type_info": "Varchar" - }, - { - "name": "server_side_type", - "ordinal": 27, - "type_info": "Varchar" - }, - { - "name": "short", - "ordinal": 28, - "type_info": "Varchar" - }, - { - "name": "license_name", - "ordinal": 29, - "type_info": "Varchar" - }, - { - "name": "project_type_name", - "ordinal": 30, - "type_info": "Varchar" - }, - { - "name": "flame_anvil_project", - "ordinal": 31, - "type_info": "Int4" - }, - { - "name": "flame_anvil_user", - "ordinal": 32, - "type_info": "Int8" - }, - { - "name": "categories", - "ordinal": 33, - "type_info": "TextArray" - }, - { - "name": "versions", - "ordinal": 34, - "type_info": "TextArray" - }, - { - "name": "gallery", - "ordinal": 35, - "type_info": "TextArray" - }, - { - "name": "donations", - "ordinal": 36, - "type_info": "TextArray" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - false, - true, - false, - false, - true, - false, - true, - true, - true, - true, - true, - false, - false, - false, - false, - true, - true, - true, - false, - false, - false, - false, - false, - false, - true, - true, - null, - null, - null, - null - ], - "parameters": { - "Left": [ - "Int8Array" - ] - } - }, - "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n INNER JOIN licenses l ON m.license = l.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = ANY($1)\n GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id;\n " - }, "d1866ecc161c3fe3fbe094289510e99b17de563957e1f824c347c1e6ac40c40c": { "describe": { "columns": [ @@ -6497,26 +6154,6 @@ }, "query": "\n DELETE FROM files\n WHERE files.version_id = $1\n " }, - "d97203c84aa3818d20bb88671c3160ce701f9c40c143f9a8f2ec6239e3165d84": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int4" - } - ], - "nullable": [ - false - ], - "parameters": { - "Left": [ - "Text" - ] - } - }, - "query": "\n SELECT id FROM licenses\n WHERE short = $1\n " - }, "dc2a3a07469ce25a9749d9d1c2e7424ac6765a72e8d9dd90eb682664f7cf036f": { "describe": { "columns": [], @@ -7048,6 +6685,230 @@ }, "query": "\n DELETE FROM dependencies WHERE mod_dependency_id = $1\n " }, + "ee6b9377ab72c433b9459e0f86f49abff150206193af553be64e0f82d0a627e8": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "project_type", + "ordinal": 1, + "type_info": "Int4" + }, + { + "name": "title", + "ordinal": 2, + "type_info": "Varchar" + }, + { + "name": "description", + "ordinal": 3, + "type_info": "Varchar" + }, + { + "name": "downloads", + "ordinal": 4, + "type_info": "Int4" + }, + { + "name": "follows", + "ordinal": 5, + "type_info": "Int4" + }, + { + "name": "icon_url", + "ordinal": 6, + "type_info": "Varchar" + }, + { + "name": "body", + "ordinal": 7, + "type_info": "Varchar" + }, + { + "name": "body_url", + "ordinal": 8, + "type_info": "Varchar" + }, + { + "name": "published", + "ordinal": 9, + "type_info": "Timestamptz" + }, + { + "name": "updated", + "ordinal": 10, + "type_info": "Timestamptz" + }, + { + "name": "approved", + "ordinal": 11, + "type_info": "Timestamptz" + }, + { + "name": "status", + "ordinal": 12, + "type_info": "Int4" + }, + { + "name": "issues_url", + "ordinal": 13, + "type_info": "Varchar" + }, + { + "name": "source_url", + "ordinal": 14, + "type_info": "Varchar" + }, + { + "name": "wiki_url", + "ordinal": 15, + "type_info": "Varchar" + }, + { + "name": "discord_url", + "ordinal": 16, + "type_info": "Varchar" + }, + { + "name": "license_url", + "ordinal": 17, + "type_info": "Varchar" + }, + { + "name": "team_id", + "ordinal": 18, + "type_info": "Int8" + }, + { + "name": "client_side", + "ordinal": 19, + "type_info": "Int4" + }, + { + "name": "server_side", + "ordinal": 20, + "type_info": "Int4" + }, + { + "name": "license", + "ordinal": 21, + "type_info": "Varchar" + }, + { + "name": "slug", + "ordinal": 22, + "type_info": "Varchar" + }, + { + "name": "moderation_message", + "ordinal": 23, + "type_info": "Varchar" + }, + { + "name": "moderation_message_body", + "ordinal": 24, + "type_info": "Varchar" + }, + { + "name": "status_name", + "ordinal": 25, + "type_info": "Varchar" + }, + { + "name": "client_side_type", + "ordinal": 26, + "type_info": "Varchar" + }, + { + "name": "server_side_type", + "ordinal": 27, + "type_info": "Varchar" + }, + { + "name": "project_type_name", + "ordinal": 28, + "type_info": "Varchar" + }, + { + "name": "flame_anvil_project", + "ordinal": 29, + "type_info": "Int4" + }, + { + "name": "flame_anvil_user", + "ordinal": 30, + "type_info": "Int8" + }, + { + "name": "categories", + "ordinal": 31, + "type_info": "TextArray" + }, + { + "name": "versions", + "ordinal": 32, + "type_info": "TextArray" + }, + { + "name": "gallery", + "ordinal": 33, + "type_info": "TextArray" + }, + { + "name": "donations", + "ordinal": 34, + "type_info": "TextArray" + } + ], + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + true, + true, + null, + null, + null, + null + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "\n SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.body body, m.body_url body_url, m.published published,\n m.updated updated, m.approved approved, m.status status,\n m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url,\n m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body,\n s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user,\n ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories,\n ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions,\n ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery,\n ARRAY_AGG(DISTINCT md.joining_platform_id || ' |||| ' || dp.short || ' |||| ' || dp.name || ' |||| ' || md.url) filter (where md.joining_platform_id is not null) donations\n FROM mods m\n INNER JOIN project_types pt ON pt.id = m.project_type\n INNER JOIN statuses s ON s.id = m.status\n INNER JOIN side_types cs ON m.client_side = cs.id\n INNER JOIN side_types ss ON m.server_side = ss.id\n LEFT JOIN mods_donations md ON md.joining_mod_id = m.id\n LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id\n LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id\n LEFT JOIN categories c ON mc.joining_category_id = c.id\n LEFT JOIN versions v ON v.mod_id = m.id\n LEFT JOIN mods_gallery mg ON mg.mod_id = m.id\n WHERE m.id = $1\n GROUP BY pt.id, s.id, cs.id, ss.id, m.id;\n " + }, "ef3d43d3424824eed67370f10cc0672581a95a169bf404022cbe3cac0415d99c": { "describe": { "columns": [ diff --git a/src/database/models/categories.rs b/src/database/models/categories.rs index f3aae8d84..d2b8c2fff 100644 --- a/src/database/models/categories.rs +++ b/src/database/models/categories.rs @@ -38,12 +38,6 @@ pub struct ReportType { pub report_type: String, } -pub struct License { - pub id: LicenseId, - pub short: String, - pub name: String, -} - pub struct DonationPlatform { pub id: DonationPlatformId, pub short: String, @@ -685,157 +679,6 @@ impl<'a> GameVersionBuilder<'a> { } } -#[derive(Default)] -pub struct LicenseBuilder<'a> { - pub short: Option<&'a str>, - pub name: Option<&'a str>, -} - -impl License { - pub fn builder() -> LicenseBuilder<'static> { - LicenseBuilder::default() - } - - 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 licenses - WHERE short = $1 - ", - id - ) - .fetch_optional(exec) - .await?; - - Ok(result.map(|r| LicenseId(r.id))) - } - - pub async fn get<'a, E>( - id: LicenseId, - exec: E, - ) -> Result - where - E: sqlx::Executor<'a, Database = sqlx::Postgres>, - { - let result = sqlx::query!( - " - SELECT short, name FROM licenses - WHERE id = $1 - ", - id as LicenseId - ) - .fetch_one(exec) - .await?; - - Ok(License { - id, - short: result.short, - name: result.name, - }) - } - - pub async fn list<'a, E>(exec: E) -> Result, DatabaseError> - where - E: sqlx::Executor<'a, Database = sqlx::Postgres>, - { - let result = sqlx::query!( - " - SELECT id, short, name FROM licenses - " - ) - .fetch_many(exec) - .try_filter_map(|e| async { - Ok(e.right().map(|c| License { - id: LicenseId(c.id), - short: c.short, - name: c.name, - })) - }) - .try_collect::>() - .await?; - - Ok(result) - } - - pub async fn remove<'a, E>( - short: &str, - exec: E, - ) -> Result, DatabaseError> - where - E: sqlx::Executor<'a, Database = sqlx::Postgres>, - { - let result = sqlx::query!( - " - DELETE FROM licenses - WHERE short = $1 - ", - short - ) - .execute(exec) - .await?; - - if result.rows_affected() == 0 { - // Nothing was deleted - Ok(None) - } else { - Ok(Some(())) - } - } -} - -impl<'a> LicenseBuilder<'a> { - /// The license's short name/abbreviation. Spaces must be replaced with '_' for it to be valid - pub fn short( - self, - short: &'a str, - ) -> Result, DatabaseError> { - Ok(Self { - short: Some(short), - ..self - }) - } - - /// The license's long name - pub fn name( - self, - name: &'a str, - ) -> Result, DatabaseError> { - Ok(Self { - name: Some(name), - ..self - }) - } - - pub async fn insert<'b, E>( - self, - exec: E, - ) -> Result - where - E: sqlx::Executor<'b, Database = sqlx::Postgres>, - { - let result = sqlx::query!( - " - INSERT INTO licenses (short, name) - VALUES ($1, $2) - ON CONFLICT (short) DO NOTHING - RETURNING id - ", - self.short, - self.name, - ) - .fetch_one(exec) - .await?; - - Ok(LicenseId(result.id)) - } -} - #[derive(Default)] pub struct DonationPlatformBuilder<'a> { pub short: Option<&'a str>, diff --git a/src/database/models/ids.rs b/src/database/models/ids.rs index 9e97862b1..5d25031b3 100644 --- a/src/database/models/ids.rs +++ b/src/database/models/ids.rs @@ -131,9 +131,6 @@ pub struct StatusId(pub i32); pub struct SideTypeId(pub i32); #[derive(Copy, Clone, Debug, Type)] #[sqlx(transparent)] -pub struct LicenseId(pub i32); -#[derive(Copy, Clone, Debug, Type)] -#[sqlx(transparent)] pub struct DonationPlatformId(pub i32); #[derive(Copy, Clone, Debug, Type, PartialEq, Eq, Hash)] diff --git a/src/database/models/project_item.rs b/src/database/models/project_item.rs index af8ec62fc..f56e40db6 100644 --- a/src/database/models/project_item.rs +++ b/src/database/models/project_item.rs @@ -92,7 +92,7 @@ pub struct ProjectBuilder { pub status: StatusId, pub client_side: SideTypeId, pub server_side: SideTypeId, - pub license: LicenseId, + pub license: String, pub slug: Option, pub donation_urls: Vec, pub gallery_items: Vec, @@ -201,7 +201,7 @@ pub struct Project { pub discord_url: Option, pub client_side: SideTypeId, pub server_side: SideTypeId, - pub license: LicenseId, + pub license: String, pub slug: Option, pub moderation_message: Option, pub moderation_message_body: Option, @@ -247,7 +247,7 @@ impl Project { self.client_side as SideTypeId, self.server_side as SideTypeId, self.license_url.as_ref(), - self.license as LicenseId, + &self.license, self.slug.as_ref(), self.project_type as ProjectTypeId ) @@ -301,7 +301,7 @@ impl Project { client_side: SideTypeId(row.client_side), status: StatusId(row.status), server_side: SideTypeId(row.server_side), - license: LicenseId(row.license), + license: row.license, slug: row.slug, body: row.body, follows: row.follows, @@ -362,7 +362,7 @@ impl Project { client_side: SideTypeId(m.client_side), status: StatusId(m.status), server_side: SideTypeId(m.server_side), - license: LicenseId(m.license), + license: m.license, slug: m.slug, body: m.body, follows: m.follows, @@ -649,7 +649,7 @@ impl Project { m.updated updated, m.approved approved, m.status status, m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url, m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body, - s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, + s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions, ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery, @@ -659,7 +659,6 @@ impl Project { INNER JOIN statuses s ON s.id = m.status INNER JOIN side_types cs ON m.client_side = cs.id INNER JOIN side_types ss ON m.server_side = ss.id - INNER JOIN licenses l ON m.license = l.id LEFT JOIN mods_donations md ON md.joining_mod_id = m.id LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id @@ -667,7 +666,7 @@ impl Project { LEFT JOIN versions v ON v.mod_id = m.id LEFT JOIN mods_gallery mg ON mg.mod_id = m.id WHERE m.id = $1 - GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id; + GROUP BY pt.id, s.id, cs.id, ss.id, m.id; ", id as ProjectId, ) @@ -712,7 +711,7 @@ impl Project { client_side: SideTypeId(m.client_side), status: StatusId(m.status), server_side: SideTypeId(m.server_side), - license: LicenseId(m.license), + license: m.license.clone(), slug: m.slug.clone(), body: m.body.clone(), follows: m.follows, @@ -806,8 +805,6 @@ impl Project { status: crate::models::projects::ProjectStatus::from_str( &m.status_name, ), - license_id: m.short, - license_name: m.license_name, client_side: crate::models::projects::SideType::from_str( &m.client_side_type, ), @@ -838,7 +835,7 @@ impl Project { m.updated updated, m.approved approved, m.status status, m.issues_url issues_url, m.source_url source_url, m.wiki_url wiki_url, m.discord_url discord_url, m.license_url license_url, m.team_id team_id, m.client_side client_side, m.server_side server_side, m.license license, m.slug slug, m.moderation_message moderation_message, m.moderation_message_body moderation_message_body, - s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, l.name license_name, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, + s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, m.flame_anvil_project flame_anvil_project, m.flame_anvil_user flame_anvil_user, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT v.id || ' |||| ' || v.date_published) filter (where v.id is not null) versions, ARRAY_AGG(DISTINCT mg.image_url || ' |||| ' || mg.featured || ' |||| ' || mg.created || ' |||| ' || COALESCE(mg.title, ' ') || ' |||| ' || COALESCE(mg.description, ' ')) filter (where mg.image_url is not null) gallery, @@ -848,7 +845,6 @@ impl Project { INNER JOIN statuses s ON s.id = m.status INNER JOIN side_types cs ON m.client_side = cs.id INNER JOIN side_types ss ON m.server_side = ss.id - INNER JOIN licenses l ON m.license = l.id LEFT JOIN mods_donations md ON md.joining_mod_id = m.id LEFT JOIN donation_platforms dp ON md.joining_platform_id = dp.id LEFT JOIN mods_categories mc ON mc.joining_mod_id = m.id @@ -856,7 +852,7 @@ impl Project { LEFT JOIN versions v ON v.mod_id = m.id LEFT JOIN mods_gallery mg ON mg.mod_id = m.id WHERE m.id = ANY($1) - GROUP BY pt.id, s.id, cs.id, ss.id, l.id, m.id; + GROUP BY pt.id, s.id, cs.id, ss.id, m.id; ", &project_ids_parsed ) @@ -903,7 +899,7 @@ impl Project { client_side: SideTypeId(m.client_side), status: StatusId(m.status), server_side: SideTypeId(m.server_side), - license: LicenseId(m.license), + license: m.license.clone(), slug: m.slug.clone(), body: m.body.clone(), follows: m.follows, @@ -983,8 +979,6 @@ impl Project { }) .collect(), status: crate::models::projects::ProjectStatus::from_str(&m.status_name), - license_id: m.short, - license_name: m.license_name, client_side: crate::models::projects::SideType::from_str(&m.client_side_type), server_side: crate::models::projects::SideType::from_str(&m.server_side_type), }})) @@ -1004,8 +998,6 @@ pub struct QueryProject { pub donation_urls: Vec, pub gallery_items: Vec, pub status: crate::models::projects::ProjectStatus, - pub license_id: String, - pub license_name: String, pub client_side: crate::models::projects::SideType, pub server_side: crate::models::projects::SideType, } diff --git a/src/models/projects.rs b/src/models/projects.rs index b9ba81d19..614bde272 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -121,8 +121,24 @@ impl From for Project { None }, license: License { - id: data.license_id, - name: data.license_name, + id: m.license.clone(), + name: match spdx::Expression::parse(&*m.license) { + Ok(spdx_expr) => { + let mut vec: Vec<&str> = Vec::new(); + for node in spdx_expr.iter() { + if let spdx::expression::ExprNode::Req(req) = node { + if let Some(id) = req.req.license.id() { + vec.push(id.full_name); + } + } + } + // spdx crate returns AND/OR operations in postfix order + // and it would be a lot more effort to make it actually in order + // so let's just ignore that and make them comma-separated + vec.join(", ") + } + Err(_) => "".to_string(), + }, url: m.license_url, }, client_side: data.client_side, @@ -215,6 +231,8 @@ impl SideType { } } +pub const DEFAULT_LICENSE_ID: &str = "LicenseRef-All-Rights-Reserved"; + #[derive(Serialize, Deserialize, Clone)] pub struct License { pub id: String, diff --git a/src/routes/project_creation.rs b/src/routes/project_creation.rs index fe515afed..e010c8cda 100644 --- a/src/routes/project_creation.rs +++ b/src/routes/project_creation.rs @@ -688,16 +688,16 @@ pub async fn project_create_inner( ) })?; - let license_id = models::categories::License::get_id( + let license_id = spdx::Expression::parse( &project_create_data.license_id, - &mut *transaction, ) - .await? - .ok_or_else(|| { - CreateError::InvalidInput( - "License specified does not exist.".to_string(), - ) + .map_err(|err| { + CreateError::InvalidInput(format!( + "Invalid SPDX license identifier: {}", + err + )) })?; + let mut donation_urls = vec![]; if let Some(urls) = &project_create_data.donation_urls { @@ -744,7 +744,7 @@ pub async fn project_create_inner( status: status_id, client_side: client_side_id, server_side: server_side_id, - license: license_id, + license: license_id.to_string(), slug: Some(project_create_data.slug), donation_urls, gallery_items: gallery_urls diff --git a/src/routes/projects.rs b/src/routes/projects.rs index 6561ee9af..fdf969c09 100644 --- a/src/routes/projects.rs +++ b/src/routes/projects.rs @@ -857,12 +857,18 @@ pub async fn project_edit( )); } - let license_id = database::models::categories::License::get_id( - license, - &mut *transaction, - ) - .await? - .expect("No database entry found for license"); + let mut license = license.clone(); + + if license.to_lowercase() == "arr" { + license = models::projects::DEFAULT_LICENSE_ID.to_string(); + } + + spdx::Expression::parse(&*license).map_err(|err| { + ApiError::InvalidInput(format!( + "Invalid SPDX license identifier: {}", + err + )) + })?; sqlx::query!( " @@ -870,7 +876,7 @@ pub async fn project_edit( SET license = $1 WHERE (id = $2) ", - license_id as database::models::LicenseId, + license, id as database::models::ids::ProjectId, ) .execute(&mut *transaction) diff --git a/src/routes/tags.rs b/src/routes/tags.rs index 7841c9033..52af8c922 100644 --- a/src/routes/tags.rs +++ b/src/routes/tags.rs @@ -1,7 +1,7 @@ use super::ApiError; use crate::database::models; use crate::database::models::categories::{ - DonationPlatform, License, ProjectType, ReportType, + DonationPlatform, ProjectType, ReportType, }; use crate::util::auth::check_is_admin_from_headers; use actix_web::{delete, get, put, web, HttpRequest, HttpResponse}; @@ -21,9 +21,8 @@ pub fn config(cfg: &mut web::ServiceConfig) { .service(game_version_list) .service(game_version_create) .service(game_version_delete) - .service(license_create) - .service(license_delete) .service(license_list) + .service(license_text) .service(donation_platform_create) .service(donation_platform_list) .service(donation_platform_delete) @@ -302,75 +301,52 @@ pub async fn game_version_delete( } #[derive(serde::Serialize)] -pub struct LicenseQueryData { +pub struct License { short: String, name: String, } #[get("license")] -pub async fn license_list( - pool: web::Data, -) -> Result { - let results: Vec = License::list(&**pool) - .await? - .into_iter() - .map(|x| LicenseQueryData { - short: x.short, - name: x.name, - }) - .collect(); - Ok(HttpResponse::Ok().json(results)) -} +pub async fn license_list() -> HttpResponse { + let licenses = spdx::identifiers::LICENSES; + let mut results: Vec = Vec::with_capacity(licenses.len()); -#[derive(serde::Deserialize)] -pub struct LicenseData { - name: String, -} - -#[put("license/{name}")] -pub async fn license_create( - req: HttpRequest, - pool: web::Data, - license: web::Path<(String,)>, - license_data: web::Json, -) -> Result { - check_is_admin_from_headers(req.headers(), &**pool).await?; - - let short = license.into_inner().0; - - let _id = License::builder() - .short(&short)? - .name(&license_data.name)? - .insert(&**pool) - .await?; - - Ok(HttpResponse::NoContent().body("")) -} - -#[delete("license/{name}")] -pub async fn license_delete( - req: HttpRequest, - pool: web::Data, - license: web::Path<(String,)>, -) -> Result { - check_is_admin_from_headers(req.headers(), &**pool).await?; - - let name = license.into_inner().0; - let mut transaction = - pool.begin().await.map_err(models::DatabaseError::from)?; - - let result = License::remove(&name, &mut transaction).await?; - - transaction - .commit() - .await - .map_err(models::DatabaseError::from)?; - - if result.is_some() { - Ok(HttpResponse::NoContent().body("")) - } else { - Ok(HttpResponse::NotFound().body("")) + for (short, name, _) in licenses { + results.push(License { + short: short.to_string(), + name: name.to_string(), + }); } + + HttpResponse::Ok().json(results) +} + +#[derive(serde::Serialize)] +pub struct LicenseText { + body: String, +} + +#[get("license/{id}")] +pub async fn license_text( + params: web::Path<(String,)>, +) -> Result { + let license_id = params.into_inner().0; + + if license_id == crate::models::projects::DEFAULT_LICENSE_ID.to_string() { + return Ok(HttpResponse::Ok().json(LicenseText { + body: "All rights reserved unless explicitly stated.".to_string(), + })); + } + + if let Some(license) = spdx::license_id(&*license_id) { + return Ok(HttpResponse::Ok().json(LicenseText { + body: license.text().to_string(), + })); + } + + Err(ApiError::InvalidInput( + "Invalid SPDX identifier specified".to_string(), + )) } #[derive(serde::Serialize)] diff --git a/src/routes/v1/mod.rs b/src/routes/v1/mod.rs index f975a46ce..a4abdc553 100644 --- a/src/routes/v1/mod.rs +++ b/src/routes/v1/mod.rs @@ -35,8 +35,6 @@ pub fn tags_config(cfg: &mut web::ServiceConfig) { .service(tags::game_version_list) .service(super::tags::game_version_create) .service(super::tags::game_version_delete) - .service(super::tags::license_create) - .service(super::tags::license_delete) .service(super::tags::license_list) .service(super::tags::donation_platform_create) .service(super::tags::donation_platform_list) diff --git a/src/search/indexing/local_import.rs b/src/search/indexing/local_import.rs index 2e1ef2a87..85ed90e73 100644 --- a/src/search/indexing/local_import.rs +++ b/src/search/indexing/local_import.rs @@ -16,7 +16,7 @@ pub async fn index_local( SELECT m.id id, m.project_type project_type, m.title title, m.description description, m.downloads downloads, m.follows follows, m.icon_url icon_url, m.published published, m.approved approved, m.updated updated, m.team_id team_id, m.license license, m.slug slug, - s.status status_name, cs.name client_side_type, ss.name server_side_type, l.short short, pt.name project_type_name, u.username username, + s.status status_name, cs.name client_side_type, ss.name server_side_type, pt.name project_type_name, u.username username, ARRAY_AGG(DISTINCT c.category || ' |||| ' || mc.is_additional) filter (where c.category is not null) categories, ARRAY_AGG(DISTINCT lo.loader) filter (where lo.loader is not null) loaders, ARRAY_AGG(DISTINCT gv.version) filter (where gv.version is not null) versions, @@ -34,11 +34,10 @@ pub async fn index_local( INNER JOIN project_types pt ON pt.id = m.project_type INNER JOIN side_types cs ON m.client_side = cs.id INNER JOIN side_types ss ON m.server_side = ss.id - INNER JOIN licenses l ON m.license = l.id INNER JOIN team_members tm ON tm.team_id = m.team_id AND tm.role = $3 AND tm.accepted = TRUE INNER JOIN users u ON tm.user_id = u.id WHERE s.status = $1 OR s.status = $2 - GROUP BY m.id, s.id, cs.id, ss.id, l.id, pt.id, u.id; + GROUP BY m.id, s.id, cs.id, ss.id, pt.id, u.id; ", crate::models::projects::ProjectStatus::Approved.as_str(), crate::models::projects::ProjectStatus::Archived.as_str(), @@ -73,6 +72,16 @@ pub async fn index_local( let project_id: crate::models::projects::ProjectId = ProjectId(m.id).into(); + let license = match m.license.split(" ").next() { + Some(license) => license.to_string(), + None => m.license, + }; + + let open_source = match spdx::license_id(&license) { + Some(id) => id.is_osi_approved(), + _ => false, + }; + UploadSearchProject { project_id: format!("{}", project_id), title: m.title, @@ -88,13 +97,14 @@ pub async fn index_local( modified_timestamp: m.updated.timestamp(), latest_version: versions.last().cloned().unwrap_or_else(|| "None".to_string()), versions, - license: m.short, + license, client_side: m.client_side_type, server_side: m.server_side_type, slug: m.slug, project_type: m.project_type_name, gallery: m.gallery.unwrap_or_default(), - display_categories + display_categories, + open_source, } })) }) diff --git a/src/search/indexing/mod.rs b/src/search/indexing/mod.rs index 35af419c6..2b2948c38 100644 --- a/src/search/indexing/mod.rs +++ b/src/search/indexing/mod.rs @@ -217,6 +217,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ "date_created", "date_modified", "project_id", + "open_source", ]; const DEFAULT_SORTABLE_ATTRIBUTES: &[&str] = diff --git a/src/search/mod.rs b/src/search/mod.rs index 20dc7bae8..b78d7d499 100644 --- a/src/search/mod.rs +++ b/src/search/mod.rs @@ -97,6 +97,7 @@ pub struct UploadSearchProject { pub date_modified: DateTime, /// Unix timestamp of the last major modification pub modified_timestamp: i64, + pub open_source: bool, } #[derive(Serialize, Deserialize, Debug)]