feat(labrinth): rework v3 side types to a single environment field (#3701)
* feat(labrinth): rework v3 side types to a single `environment` field This field is meant to be able to represent the existing v2 side type information and beyond, in a way that may also be slightly easier to comprehend. * chore(labrinth/migrations): use proper val for `HAVING` clause * feat(labrinth): add `side_types_migration_review_status` field to projects
This commit is contained in:
parent
65126b3a23
commit
ef04dcc37b
15
apps/labrinth/.sqlx/query-374c234b92b838b0bd65de100a9008b0fb78c79976fd858e0599e1ccb7f08b82.json
generated
Normal file
15
apps/labrinth/.sqlx/query-374c234b92b838b0bd65de100a9008b0fb78c79976fd858e0599e1ccb7f08b82.json
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE mods\n SET side_types_migration_review_status = $1\n WHERE id = $2\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Varchar",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "374c234b92b838b0bd65de100a9008b0fb78c79976fd858e0599e1ccb7f08b82"
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT m.id id, m.name name, m.summary summary, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.raw_icon_url raw_icon_url, m.description description, m.published published,\n 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 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 FROM mods m\n INNER JOIN threads t ON t.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 WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id;\n ",
|
"query": "\n SELECT m.id id, m.name name, m.summary summary, m.downloads downloads, m.follows follows,\n m.icon_url icon_url, m.raw_icon_url raw_icon_url, m.description description, m.published published,\n 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 m.side_types_migration_review_status side_types_migration_review_status,\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 FROM mods m\n INNER JOIN threads t ON t.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 WHERE m.id = ANY($1) OR m.slug = ANY($2)\n GROUP BY t.id, m.id;\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -125,11 +125,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 24,
|
"ordinal": 24,
|
||||||
|
"name": "side_types_migration_review_status",
|
||||||
|
"type_info": "Varchar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 25,
|
||||||
"name": "categories",
|
"name": "categories",
|
||||||
"type_info": "VarcharArray"
|
"type_info": "VarcharArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 25,
|
"ordinal": 26,
|
||||||
"name": "additional_categories",
|
"name": "additional_categories",
|
||||||
"type_info": "VarcharArray"
|
"type_info": "VarcharArray"
|
||||||
}
|
}
|
||||||
@ -165,9 +170,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "5f75c0c48083de27f853ee877aac070567fd2ed2be4a9a038821b790dd7cb763"
|
"hash": "7a6d6a91e6bd27f7be34b8cc7955a66c4175ebd1c55e437f187f61efca681c62"
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n INSERT INTO mods (\n id, team_id, name, summary, description,\n published, downloads, icon_url, raw_icon_url, status, requested_status,\n license_url, license,\n slug, color, monetization_status, organization_id\n )\n VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13,\n LOWER($14), $15, $16, $17\n )\n ",
|
"query": "\n INSERT INTO mods (\n id, team_id, name, summary, description,\n published, downloads, icon_url, raw_icon_url, status, requested_status,\n license_url, license,\n slug, color, monetization_status, organization_id,\n side_types_migration_review_status\n )\n VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13,\n LOWER($14), $15, $16, $17,\n $18\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -21,10 +21,11 @@
|
|||||||
"Text",
|
"Text",
|
||||||
"Int4",
|
"Int4",
|
||||||
"Varchar",
|
"Varchar",
|
||||||
"Int8"
|
"Int8",
|
||||||
|
"Varchar"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": []
|
"nullable": []
|
||||||
},
|
},
|
||||||
"hash": "bcbcac3c0b2b2b0327577d3095fa744ab42f7f1dcd2b7f3c3dace12b899b3f38"
|
"hash": "ee74bbff42dd29ab5a23d5811ea18e62ac199fe5e68275bf1bc7c71ace630702"
|
||||||
}
|
}
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
DO LANGUAGE plpgsql $$
|
||||||
|
DECLARE
|
||||||
|
VAR_env_field_id INT;
|
||||||
|
VAR_env_field_enum_id INT := 4; -- Known available ID for a new enum type
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
-- Define a new loader field for environment
|
||||||
|
INSERT INTO loader_field_enums (id, enum_name, ordering, hidable)
|
||||||
|
VALUES (VAR_env_field_enum_id, 'environment', NULL, TRUE);
|
||||||
|
|
||||||
|
INSERT INTO loader_field_enum_values (enum_id, value, ordering, created, metadata)
|
||||||
|
VALUES
|
||||||
|
-- Must be installed on both client and (integrated) server
|
||||||
|
(VAR_env_field_enum_id, 'client_and_server', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed only on the client
|
||||||
|
(VAR_env_field_enum_id, 'client_only', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed on the client, may be installed on a (integrated) server. To be displayed as a
|
||||||
|
-- client mod
|
||||||
|
(VAR_env_field_enum_id, 'client_only_server_optional', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed only on the integrated singleplayer server. To be displayed as a server mod for
|
||||||
|
-- singleplayer exclusively
|
||||||
|
(VAR_env_field_enum_id, 'singleplayer_only', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed only on a (integrated) server
|
||||||
|
(VAR_env_field_enum_id, 'server_only', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed on the server, may be installed on the client. To be displayed as a
|
||||||
|
-- singleplayer-compatible server mod
|
||||||
|
(VAR_env_field_enum_id, 'server_only_client_optional', NULL, NOW(), NULL),
|
||||||
|
-- Must be installed only on a dedicated multiplayer server (not the integrated singleplayer server).
|
||||||
|
-- To be displayed as an server mod for multiplayer exclusively
|
||||||
|
(VAR_env_field_enum_id, 'dedicated_server_only', NULL, NOW(), NULL),
|
||||||
|
-- Can be installed on both client and server, with no strong preference for either. To be displayed
|
||||||
|
-- as both a client and server mod
|
||||||
|
(VAR_env_field_enum_id, 'client_or_server', NULL, NOW(), NULL),
|
||||||
|
-- Can be installed on both client and server, with a preference for being installed on both. To be
|
||||||
|
-- displayed as a client and server mod
|
||||||
|
(VAR_env_field_enum_id, 'client_or_server_prefers_both', NULL, NOW(), NULL),
|
||||||
|
(VAR_env_field_enum_id, 'unknown', NULL, NOW(), NULL);
|
||||||
|
|
||||||
|
INSERT INTO loader_fields (field, field_type, enum_type, optional)
|
||||||
|
VALUES ('environment', 'enum', VAR_env_field_enum_id, FALSE)
|
||||||
|
RETURNING id INTO VAR_env_field_id;
|
||||||
|
|
||||||
|
-- Update version_fields to have the new environment field, initializing it from the
|
||||||
|
-- values of the previous fields
|
||||||
|
INSERT INTO version_fields (version_id, field_id, enum_value)
|
||||||
|
SELECT vf.version_id, VAR_env_field_id, (
|
||||||
|
SELECT id
|
||||||
|
FROM loader_field_enum_values
|
||||||
|
WHERE enum_id = VAR_env_field_enum_id
|
||||||
|
AND value = (
|
||||||
|
CASE jsonb_object_agg(lf.field, vf.int_value)
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 0, "client_and_server": 0, "client_only": 1 }'::jsonb THEN 'client_only'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 0, "client_and_server": 1, "client_only": 0 }'::jsonb THEN 'client_and_server'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 0, "client_and_server": 1, "client_only": 1 }'::jsonb THEN 'client_only_server_optional'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 1, "client_and_server": 0, "client_only": 0 }'::jsonb THEN 'singleplayer_only'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 1, "client_and_server": 0, "client_only": 1 }'::jsonb THEN 'client_only'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 1, "client_and_server": 1, "client_only": 0 }'::jsonb THEN 'client_and_server'
|
||||||
|
WHEN '{ "server_only": 0, "singleplayer": 1, "client_and_server": 1, "client_only": 1 }'::jsonb THEN 'client_only_server_optional'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 0, "client_and_server": 0, "client_only": 0 }'::jsonb THEN 'server_only'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 0, "client_and_server": 0, "client_only": 1 }'::jsonb THEN 'client_or_server'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 0, "client_and_server": 1, "client_only": 0 }'::jsonb THEN 'server_only_client_optional'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 0, "client_and_server": 1, "client_only": 1 }'::jsonb THEN 'client_or_server_prefers_both'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 1, "client_and_server": 0, "client_only": 0 }'::jsonb THEN 'server_only'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 1, "client_and_server": 0, "client_only": 1 }'::jsonb THEN 'client_or_server'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 1, "client_and_server": 1, "client_only": 0 }'::jsonb THEN 'server_only_client_optional'
|
||||||
|
WHEN '{ "server_only": 1, "singleplayer": 1, "client_and_server": 1, "client_only": 1 }'::jsonb THEN 'client_or_server_prefers_both'
|
||||||
|
ELSE 'unknown'
|
||||||
|
END
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM version_fields vf
|
||||||
|
JOIN loader_fields lf ON vf.field_id = lf.id
|
||||||
|
WHERE lf.field IN ('server_only', 'singleplayer', 'client_and_server', 'client_only')
|
||||||
|
GROUP BY vf.version_id
|
||||||
|
HAVING COUNT(DISTINCT lf.field) = 4;
|
||||||
|
|
||||||
|
-- Clean up old fields from the project versions
|
||||||
|
DELETE FROM version_fields
|
||||||
|
WHERE field_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM loader_fields
|
||||||
|
WHERE field IN ('server_only', 'singleplayer', 'client_and_server', 'client_only')
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Switch loader fields definitions on the available loaders to use the new environment field
|
||||||
|
ALTER TABLE loader_fields_loaders DROP CONSTRAINT unique_loader_field;
|
||||||
|
ALTER TABLE loader_fields_loaders DROP CONSTRAINT loader_fields_loaders_pkey;
|
||||||
|
|
||||||
|
UPDATE loader_fields_loaders
|
||||||
|
SET loader_field_id = VAR_env_field_id
|
||||||
|
WHERE loader_field_id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM loader_fields
|
||||||
|
WHERE field IN ('server_only', 'singleplayer', 'client_and_server', 'client_only')
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Remove duplicate (loader_id, loader_field_id) pairs that may have been created due to several
|
||||||
|
-- old fields being converted to a single new field
|
||||||
|
DELETE FROM loader_fields_loaders
|
||||||
|
WHERE ctid NOT IN (
|
||||||
|
SELECT MIN(ctid)
|
||||||
|
FROM loader_fields_loaders
|
||||||
|
GROUP BY loader_id, loader_field_id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Having both a PK and UNIQUE constraint for the same columns is redundant, so only restore the PK
|
||||||
|
ALTER TABLE loader_fields_loaders ADD PRIMARY KEY (loader_id, loader_field_id);
|
||||||
|
|
||||||
|
-- Finally, remove the old loader fields
|
||||||
|
DELETE FROM loader_fields
|
||||||
|
WHERE field IN ('server_only', 'singleplayer', 'client_and_server', 'client_only');
|
||||||
|
|
||||||
|
-- Add a field to the projects table to track whether the new environment field value has been
|
||||||
|
-- reviewed to be appropriate after automated migration
|
||||||
|
ALTER TABLE mods
|
||||||
|
ADD COLUMN side_types_migration_review_status VARCHAR(64) NOT NULL DEFAULT 'reviewed'
|
||||||
|
CHECK (side_types_migration_review_status IN ('reviewed', 'pending'));
|
||||||
|
|
||||||
|
UPDATE mods SET side_types_migration_review_status = 'pending';
|
||||||
|
|
||||||
|
END;
|
||||||
|
$$
|
||||||
@ -6,7 +6,9 @@ use super::{DBUser, ids::*};
|
|||||||
use crate::database::models;
|
use crate::database::models;
|
||||||
use crate::database::models::DatabaseError;
|
use crate::database::models::DatabaseError;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::projects::{MonetizationStatus, ProjectStatus};
|
use crate::models::projects::{
|
||||||
|
MonetizationStatus, ProjectStatus, SideTypesMigrationReviewStatus,
|
||||||
|
};
|
||||||
use ariadne::ids::base62_impl::parse_base62;
|
use ariadne::ids::base62_impl::parse_base62;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::{DashMap, DashSet};
|
||||||
@ -210,6 +212,8 @@ impl ProjectBuilder {
|
|||||||
webhook_sent: false,
|
webhook_sent: false,
|
||||||
color: self.color,
|
color: self.color,
|
||||||
monetization_status: self.monetization_status,
|
monetization_status: self.monetization_status,
|
||||||
|
side_types_migration_review_status:
|
||||||
|
SideTypesMigrationReviewStatus::Reviewed,
|
||||||
loaders: vec![],
|
loaders: vec![],
|
||||||
};
|
};
|
||||||
project_struct.insert(&mut *transaction).await?;
|
project_struct.insert(&mut *transaction).await?;
|
||||||
@ -288,6 +292,7 @@ pub struct DBProject {
|
|||||||
pub webhook_sent: bool,
|
pub webhook_sent: bool,
|
||||||
pub color: Option<u32>,
|
pub color: Option<u32>,
|
||||||
pub monetization_status: MonetizationStatus,
|
pub monetization_status: MonetizationStatus,
|
||||||
|
pub side_types_migration_review_status: SideTypesMigrationReviewStatus,
|
||||||
pub loaders: Vec<String>,
|
pub loaders: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,13 +307,15 @@ impl DBProject {
|
|||||||
id, team_id, name, summary, description,
|
id, team_id, name, summary, description,
|
||||||
published, downloads, icon_url, raw_icon_url, status, requested_status,
|
published, downloads, icon_url, raw_icon_url, status, requested_status,
|
||||||
license_url, license,
|
license_url, license,
|
||||||
slug, color, monetization_status, organization_id
|
slug, color, monetization_status, organization_id,
|
||||||
|
side_types_migration_review_status
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1, $2, $3, $4, $5, $6,
|
$1, $2, $3, $4, $5, $6,
|
||||||
$7, $8, $9, $10, $11,
|
$7, $8, $9, $10, $11,
|
||||||
$12, $13,
|
$12, $13,
|
||||||
LOWER($14), $15, $16, $17
|
LOWER($14), $15, $16, $17,
|
||||||
|
$18
|
||||||
)
|
)
|
||||||
",
|
",
|
||||||
self.id as DBProjectId,
|
self.id as DBProjectId,
|
||||||
@ -328,6 +335,7 @@ impl DBProject {
|
|||||||
self.color.map(|x| x as i32),
|
self.color.map(|x| x as i32),
|
||||||
self.monetization_status.as_str(),
|
self.monetization_status.as_str(),
|
||||||
self.organization_id.map(|x| x.0 as i64),
|
self.organization_id.map(|x| x.0 as i64),
|
||||||
|
self.side_types_migration_review_status.as_str()
|
||||||
)
|
)
|
||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -770,6 +778,7 @@ impl DBProject {
|
|||||||
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.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,
|
m.webhook_sent, m.color,
|
||||||
t.id thread_id, m.monetization_status monetization_status,
|
t.id thread_id, m.monetization_status monetization_status,
|
||||||
|
m.side_types_migration_review_status side_types_migration_review_status,
|
||||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
|
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is false) categories,
|
||||||
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories
|
ARRAY_AGG(DISTINCT c.category) filter (where c.category is not null and mc.is_additional is true) additional_categories
|
||||||
FROM mods m
|
FROM mods m
|
||||||
@ -835,6 +844,9 @@ impl DBProject {
|
|||||||
monetization_status: MonetizationStatus::from_string(
|
monetization_status: MonetizationStatus::from_string(
|
||||||
&m.monetization_status,
|
&m.monetization_status,
|
||||||
),
|
),
|
||||||
|
side_types_migration_review_status: SideTypesMigrationReviewStatus::from_string(
|
||||||
|
&m.side_types_migration_review_status,
|
||||||
|
),
|
||||||
loaders,
|
loaders,
|
||||||
},
|
},
|
||||||
categories: m.categories.unwrap_or_default(),
|
categories: m.categories.unwrap_or_default(),
|
||||||
|
|||||||
@ -127,7 +127,7 @@ impl LegacyProject {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(versions_item) = versions_item {
|
if let Some(versions_item) = versions_item {
|
||||||
// Extract side types from remaining fields (singleplayer, client_only, etc)
|
// Extract side types from remaining fields
|
||||||
let fields = versions_item
|
let fields = versions_item
|
||||||
.version_fields
|
.version_fields
|
||||||
.iter()
|
.iter()
|
||||||
@ -135,10 +135,11 @@ impl LegacyProject {
|
|||||||
(f.field_name.clone(), f.value.clone().serialize_internal())
|
(f.field_name.clone(), f.value.clone().serialize_internal())
|
||||||
})
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
(client_side, server_side) = v2_reroute::convert_side_types_v2(
|
(client_side, server_side) =
|
||||||
&fields,
|
v2_reroute::convert_v3_side_types_to_v2_side_types(
|
||||||
Some(&*og_project_type),
|
&fields,
|
||||||
);
|
Some(&*og_project_type),
|
||||||
|
);
|
||||||
|
|
||||||
// - if loader is mrpack, this is a modpack
|
// - if loader is mrpack, this is a modpack
|
||||||
// the loaders are whatever the corresponding loader fields are
|
// the loaders are whatever the corresponding loader fields are
|
||||||
|
|||||||
@ -102,28 +102,20 @@ impl LegacyResultSearchProject {
|
|||||||
|
|
||||||
let project_loader_fields =
|
let project_loader_fields =
|
||||||
result_search_project.project_loader_fields.clone();
|
result_search_project.project_loader_fields.clone();
|
||||||
let get_one_bool_loader_field = |key: &str| {
|
let get_one_string_loader_field = |key: &str| {
|
||||||
project_loader_fields
|
project_loader_fields
|
||||||
.get(key)
|
.get(key)
|
||||||
.cloned()
|
.map_or(&[][..], |values| values.as_slice())
|
||||||
.unwrap_or_default()
|
|
||||||
.first()
|
.first()
|
||||||
.and_then(|s| s.as_bool())
|
.and_then(|s| s.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
let singleplayer = get_one_bool_loader_field("singleplayer");
|
let environment =
|
||||||
let client_only =
|
get_one_string_loader_field("environment").unwrap_or("unknown");
|
||||||
get_one_bool_loader_field("client_only").unwrap_or(false);
|
|
||||||
let server_only =
|
|
||||||
get_one_bool_loader_field("server_only").unwrap_or(false);
|
|
||||||
let client_and_server = get_one_bool_loader_field("client_and_server");
|
|
||||||
|
|
||||||
let (client_side, server_side) =
|
let (client_side, server_side) =
|
||||||
v2_reroute::convert_side_types_v2_bools(
|
v2_reroute::convert_v3_environment_to_v2_side_types(
|
||||||
singleplayer,
|
environment,
|
||||||
client_only,
|
|
||||||
server_only,
|
|
||||||
client_and_server,
|
|
||||||
Some(&*og_project_type),
|
Some(&*og_project_type),
|
||||||
);
|
);
|
||||||
let client_side = client_side.to_string();
|
let client_side = client_side.to_string();
|
||||||
|
|||||||
@ -92,6 +92,9 @@ pub struct Project {
|
|||||||
/// The monetization status of this project
|
/// The monetization status of this project
|
||||||
pub monetization_status: MonetizationStatus,
|
pub monetization_status: MonetizationStatus,
|
||||||
|
|
||||||
|
/// The status of the manual review of the migration of side types of this project
|
||||||
|
pub side_types_migration_review_status: SideTypesMigrationReviewStatus,
|
||||||
|
|
||||||
/// Aggregated loader-fields across its myriad of versions
|
/// Aggregated loader-fields across its myriad of versions
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub fields: HashMap<String, Vec<serde_json::Value>>,
|
pub fields: HashMap<String, Vec<serde_json::Value>>,
|
||||||
@ -206,6 +209,8 @@ impl From<ProjectQueryResult> for Project {
|
|||||||
color: m.color,
|
color: m.color,
|
||||||
thread_id: data.thread_id.into(),
|
thread_id: data.thread_id.into(),
|
||||||
monetization_status: m.monetization_status,
|
monetization_status: m.monetization_status,
|
||||||
|
side_types_migration_review_status: m
|
||||||
|
.side_types_migration_review_status,
|
||||||
fields,
|
fields,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -588,6 +593,35 @@ impl MonetizationStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the status of the manual review of the migration of side types of this
|
||||||
|
/// project to the new environment field.
|
||||||
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum SideTypesMigrationReviewStatus {
|
||||||
|
/// The project has been reviewed to use the new environment side types appropriately.
|
||||||
|
Reviewed,
|
||||||
|
/// The project has been automatically migrated to the new environment side types, but
|
||||||
|
/// the appropriateness of such migration has not been reviewed.
|
||||||
|
Pending,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SideTypesMigrationReviewStatus {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SideTypesMigrationReviewStatus::Reviewed => "reviewed",
|
||||||
|
SideTypesMigrationReviewStatus::Pending => "pending",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_string(string: &str) -> SideTypesMigrationReviewStatus {
|
||||||
|
match string {
|
||||||
|
"reviewed" => SideTypesMigrationReviewStatus::Reviewed,
|
||||||
|
"pending" => SideTypesMigrationReviewStatus::Pending,
|
||||||
|
_ => SideTypesMigrationReviewStatus::Reviewed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A specific version of a project
|
/// A specific version of a project
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
@ -846,7 +880,6 @@ impl std::fmt::Display for VersionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VersionType {
|
impl VersionType {
|
||||||
// These are constant, so this can remove unneccessary allocations (`to_string`)
|
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
VersionType::Release => "release",
|
VersionType::Release => "release",
|
||||||
|
|||||||
@ -244,7 +244,7 @@ impl AutomatedModerationQueue {
|
|||||||
version_specific: HashMap::new(),
|
version_specific: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if project.project_types.iter().any(|x| ["mod", "modpack"].contains(&&**x)) && !project.aggregate_version_fields.iter().any(|x| ["server_only", "client_only", "client_and_server", "singleplayer"].contains(&&*x.field_name)) {
|
if project.project_types.iter().any(|x| ["mod", "modpack"].contains(&&**x)) && !project.aggregate_version_fields.iter().any(|x| x.field_name == "environment") {
|
||||||
mod_messages.messages.push(ModerationMessage::NoSideTypes);
|
mod_messages.messages.push(ModerationMessage::NoSideTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -158,10 +158,12 @@ pub async fn project_create(
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
let mut fields = HashMap::new();
|
let mut fields = HashMap::new();
|
||||||
fields.extend(v2_reroute::convert_side_types_v3(
|
fields.extend(
|
||||||
client_side,
|
v2_reroute::convert_v2_side_types_to_v3_side_types(
|
||||||
server_side,
|
client_side,
|
||||||
));
|
server_side,
|
||||||
|
),
|
||||||
|
);
|
||||||
fields.insert(
|
fields.insert(
|
||||||
"game_versions".to_string(),
|
"game_versions".to_string(),
|
||||||
json!(v.game_versions),
|
json!(v.game_versions),
|
||||||
|
|||||||
@ -511,6 +511,7 @@ pub async fn project_edit(
|
|||||||
moderation_message: v2_new_project.moderation_message,
|
moderation_message: v2_new_project.moderation_message,
|
||||||
moderation_message_body: v2_new_project.moderation_message_body,
|
moderation_message_body: v2_new_project.moderation_message_body,
|
||||||
monetization_status: v2_new_project.monetization_status,
|
monetization_status: v2_new_project.monetization_status,
|
||||||
|
side_types_migration_review_status: None, // Not to be exposed in v2
|
||||||
};
|
};
|
||||||
|
|
||||||
// This returns 204 or failure so we don't need to do anything with it
|
// This returns 204 or failure so we don't need to do anything with it
|
||||||
@ -547,10 +548,12 @@ pub async fn project_edit(
|
|||||||
let version = Version::from(version);
|
let version = Version::from(version);
|
||||||
let mut fields = version.fields;
|
let mut fields = version.fields;
|
||||||
let (current_client_side, current_server_side) =
|
let (current_client_side, current_server_side) =
|
||||||
v2_reroute::convert_side_types_v2(&fields, None);
|
v2_reroute::convert_v3_side_types_to_v2_side_types(
|
||||||
|
&fields, None,
|
||||||
|
);
|
||||||
let client_side = client_side.unwrap_or(current_client_side);
|
let client_side = client_side.unwrap_or(current_client_side);
|
||||||
let server_side = server_side.unwrap_or(current_server_side);
|
let server_side = server_side.unwrap_or(current_server_side);
|
||||||
fields.extend(v2_reroute::convert_side_types_v3(
|
fields.extend(v2_reroute::convert_v2_side_types_to_v3_side_types(
|
||||||
client_side,
|
client_side,
|
||||||
server_side,
|
server_side,
|
||||||
));
|
));
|
||||||
|
|||||||
@ -105,7 +105,7 @@ pub async fn version_create(
|
|||||||
json!(legacy_create.game_versions),
|
json!(legacy_create.game_versions),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply singleplayer, etc.
|
// Get all possible side-types for loaders given- we will use these to check if we need to convert/apply side types
|
||||||
let loaders =
|
let loaders =
|
||||||
match v3::tags::loader_list(client.clone(), redis.clone())
|
match v3::tags::loader_list(client.clone(), redis.clone())
|
||||||
.await
|
.await
|
||||||
@ -136,53 +136,32 @@ pub async fn version_create(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Copies side types of another version of the project.
|
// Copies side types of another version of the project.
|
||||||
// If no version exists, defaults to all false.
|
// If no version exists, defaults to an unknown side type.
|
||||||
// This is inherently lossy, but not much can be done about it, as side types are no longer associated with projects,
|
// This is inherently lossy, but not much can be done about it, as side types are no longer associated with projects,
|
||||||
// so the 'missing' ones can't be easily accessed, and versions do need to have these fields explicitly set.
|
// so the 'missing' ones can't be easily accessed, and versions do need to have that field explicitly set.
|
||||||
let side_type_loader_field_names = [
|
|
||||||
"singleplayer",
|
|
||||||
"client_and_server",
|
|
||||||
"client_only",
|
|
||||||
"server_only",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Check if loader_fields_aggregate contains any of these side types
|
// Check if loader_fields_aggregate contains the side types
|
||||||
// We assume these four fields are linked together.
|
// We assume these four fields are linked together.
|
||||||
if loader_fields_aggregate
|
if loader_fields_aggregate
|
||||||
.iter()
|
.iter()
|
||||||
.any(|f| side_type_loader_field_names.contains(&f.as_str()))
|
.any(|field| field == "environment")
|
||||||
{
|
{
|
||||||
// If so, we get the fields of the example version of the project, and set the side types to match.
|
// If so, we get the field of an example version of the project, and set the side types to match.
|
||||||
fields.extend(
|
fields.insert(
|
||||||
side_type_loader_field_names
|
"environment".into(),
|
||||||
.iter()
|
|
||||||
.map(|f| (f.to_string(), json!(false))),
|
|
||||||
);
|
|
||||||
if let Some(example_version_fields) =
|
|
||||||
get_example_version_fields(
|
get_example_version_fields(
|
||||||
legacy_create.project_id,
|
legacy_create.project_id,
|
||||||
client,
|
client,
|
||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
.into_iter()
|
||||||
fields.extend(
|
.flatten()
|
||||||
example_version_fields.into_iter().filter_map(
|
.find(|f| f.field_name == "environment")
|
||||||
|f| {
|
.map_or(json!("unknown"), |f| {
|
||||||
if side_type_loader_field_names
|
f.value.serialize_internal()
|
||||||
.contains(&f.field_name.as_str())
|
}),
|
||||||
{
|
);
|
||||||
Some((
|
|
||||||
f.field_name,
|
|
||||||
f.value.serialize_internal(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Handle project type via file extension prediction
|
// Handle project type via file extension prediction
|
||||||
let mut project_type = None;
|
let mut project_type = None;
|
||||||
|
|||||||
@ -164,69 +164,46 @@ where
|
|||||||
Ok(new_multipart)
|
Ok(new_multipart)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Converts a "client_side" and "server_side" pair into the new v3 corresponding fields
|
/// Converts V2 side types to V3 side types.
|
||||||
pub fn convert_side_types_v3(
|
pub fn convert_v2_side_types_to_v3_side_types(
|
||||||
client_side: LegacySideType,
|
client_side: LegacySideType,
|
||||||
server_side: LegacySideType,
|
server_side: LegacySideType,
|
||||||
) -> HashMap<String, Value> {
|
) -> HashMap<String, Value> {
|
||||||
use LegacySideType::{Optional, Required};
|
use LegacySideType::{Optional, Required, Unsupported};
|
||||||
|
|
||||||
let singleplayer = client_side == Required
|
let environment = match (client_side, server_side) {
|
||||||
|| client_side == Optional
|
(Required, Required) => "client_and_server", // Or "singleplayer_only"
|
||||||
|| server_side == Required
|
(Required, Unsupported) => "client_only",
|
||||||
|| server_side == Optional;
|
(Required, Optional) => "client_only_server_optional",
|
||||||
let client_and_server = singleplayer;
|
(Unsupported, Required) => "server_only", // Or "dedicated_server_only"
|
||||||
let client_only = (client_side == Required || client_side == Optional)
|
(Optional, Required) => "server_only_client_optional",
|
||||||
&& server_side != Required;
|
(Optional, Optional) => "client_or_server", // Or "client_or_server_prefers_both"
|
||||||
let server_only = (server_side == Required || server_side == Optional)
|
_ => "unknown",
|
||||||
&& client_side != Required;
|
};
|
||||||
|
|
||||||
let mut fields = HashMap::new();
|
[("environment".to_string(), json!(environment))]
|
||||||
fields.insert("singleplayer".to_string(), json!(singleplayer));
|
.into_iter()
|
||||||
fields.insert("client_and_server".to_string(), json!(client_and_server));
|
.collect()
|
||||||
fields.insert("client_only".to_string(), json!(client_only));
|
|
||||||
fields.insert("server_only".to_string(), json!(server_only));
|
|
||||||
fields
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert search facets from V3 back to v2
|
/// Converts a V3 side types map into the corresponding V2 side types.
|
||||||
// this is not lossless. (See tests)
|
pub fn convert_v3_side_types_to_v2_side_types(
|
||||||
pub fn convert_side_types_v2(
|
|
||||||
side_types: &HashMap<String, Value>,
|
side_types: &HashMap<String, Value>,
|
||||||
project_type: Option<&str>,
|
project_type: Option<&str>,
|
||||||
) -> (LegacySideType, LegacySideType) {
|
) -> (LegacySideType, LegacySideType) {
|
||||||
let client_and_server = side_types
|
convert_v3_environment_to_v2_side_types(
|
||||||
.get("client_and_server")
|
side_types
|
||||||
.and_then(|x| x.as_bool())
|
.get("environment")
|
||||||
.unwrap_or(false);
|
.and_then(|x| x.as_str())
|
||||||
let singleplayer = side_types
|
.unwrap_or("unknown"),
|
||||||
.get("singleplayer")
|
|
||||||
.and_then(|x| x.as_bool())
|
|
||||||
.unwrap_or(client_and_server);
|
|
||||||
let client_only = side_types
|
|
||||||
.get("client_only")
|
|
||||||
.and_then(|x| x.as_bool())
|
|
||||||
.unwrap_or(false);
|
|
||||||
let server_only = side_types
|
|
||||||
.get("server_only")
|
|
||||||
.and_then(|x| x.as_bool())
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
convert_side_types_v2_bools(
|
|
||||||
Some(singleplayer),
|
|
||||||
client_only,
|
|
||||||
server_only,
|
|
||||||
Some(client_and_server),
|
|
||||||
project_type,
|
project_type,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client side, server side
|
/// Converts a V3 environment and project type into the corresponding V2 side types.
|
||||||
pub fn convert_side_types_v2_bools(
|
/// The first side type is for the client, the second is for the server.
|
||||||
singleplayer: Option<bool>,
|
pub fn convert_v3_environment_to_v2_side_types(
|
||||||
client_only: bool,
|
environment: &str,
|
||||||
server_only: bool,
|
|
||||||
client_and_server: Option<bool>,
|
|
||||||
project_type: Option<&str>,
|
project_type: Option<&str>,
|
||||||
) -> (LegacySideType, LegacySideType) {
|
) -> (LegacySideType, LegacySideType) {
|
||||||
use LegacySideType::{Optional, Required, Unknown, Unsupported};
|
use LegacySideType::{Optional, Required, Unknown, Unsupported};
|
||||||
@ -236,30 +213,18 @@ pub fn convert_side_types_v2_bools(
|
|||||||
Some("datapack") => (Optional, Required),
|
Some("datapack") => (Optional, Required),
|
||||||
Some("shader") => (Required, Unsupported),
|
Some("shader") => (Required, Unsupported),
|
||||||
Some("resourcepack") => (Required, Unsupported),
|
Some("resourcepack") => (Required, Unsupported),
|
||||||
_ => {
|
_ => match environment {
|
||||||
let singleplayer =
|
"client_and_server" => (Required, Required),
|
||||||
singleplayer.or(client_and_server).unwrap_or(false);
|
"client_only" => (Required, Unsupported),
|
||||||
|
"client_only_server_optional" => (Required, Optional),
|
||||||
match (singleplayer, client_only, server_only) {
|
"singleplayer_only" => (Required, Required),
|
||||||
// Only singleplayer
|
"server_only" => (Unsupported, Required),
|
||||||
(true, false, false) => (Required, Required),
|
"server_only_client_optional" => (Optional, Required),
|
||||||
|
"dedicated_server_only" => (Unsupported, Required),
|
||||||
// Client only and not server only
|
"client_or_server" => (Optional, Optional),
|
||||||
(false, true, false) => (Required, Unsupported),
|
"client_or_server_prefers_both" => (Optional, Optional),
|
||||||
(true, true, false) => (Required, Unsupported),
|
_ => (Unknown, Unknown), // "unknown"
|
||||||
|
},
|
||||||
// Server only and not client only
|
|
||||||
(false, false, true) => (Unsupported, Required),
|
|
||||||
(true, false, true) => (Unsupported, Required),
|
|
||||||
|
|
||||||
// Both server only and client only
|
|
||||||
(true, true, true) => (Optional, Optional),
|
|
||||||
(false, true, true) => (Optional, Optional),
|
|
||||||
|
|
||||||
// Bad type
|
|
||||||
(false, false, false) => (Unknown, Unknown),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,13 +244,14 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn convert_types() {
|
fn v2_v3_side_type_conversion() {
|
||||||
// Converting types from V2 to V3 and back should be idempotent- for certain pairs
|
// Only nonsensical V2 side types cannot be round-tripped from V2 to V3 and back.
|
||||||
|
// When converting from V3 to V2, only additional information about the
|
||||||
|
// singleplayer-only, multiplayer-only, or install on both sides nature of the
|
||||||
|
// project is lost.
|
||||||
let lossy_pairs = [
|
let lossy_pairs = [
|
||||||
(Optional, Unsupported),
|
(Optional, Unsupported),
|
||||||
(Unsupported, Optional),
|
(Unsupported, Optional),
|
||||||
(Required, Optional),
|
|
||||||
(Optional, Required),
|
|
||||||
(Unsupported, Unsupported),
|
(Unsupported, Unsupported),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -294,10 +260,13 @@ mod tests {
|
|||||||
if lossy_pairs.contains(&(client_side, server_side)) {
|
if lossy_pairs.contains(&(client_side, server_side)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let side_types =
|
let side_types = convert_v2_side_types_to_v3_side_types(
|
||||||
convert_side_types_v3(client_side, server_side);
|
client_side,
|
||||||
|
server_side,
|
||||||
|
);
|
||||||
let (client_side2, server_side2) =
|
let (client_side2, server_side2) =
|
||||||
convert_side_types_v2(&side_types, None);
|
convert_v3_side_types_to_v2_side_types(&side_types, None);
|
||||||
|
|
||||||
assert_eq!(client_side, client_side2);
|
assert_eq!(client_side, client_side2);
|
||||||
assert_eq!(server_side, server_side2);
|
assert_eq!(server_side, server_side2);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,8 @@ use crate::models::ids::{ImageId, OrganizationId, ProjectId, VersionId};
|
|||||||
use crate::models::images::{Image, ImageContext};
|
use crate::models::images::{Image, ImageContext};
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
License, Link, MonetizationStatus, ProjectStatus, VersionStatus,
|
License, Link, MonetizationStatus, ProjectStatus,
|
||||||
|
SideTypesMigrationReviewStatus, VersionStatus,
|
||||||
};
|
};
|
||||||
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
use crate::models::teams::{OrganizationPermissions, ProjectPermissions};
|
||||||
use crate::models::threads::ThreadType;
|
use crate::models::threads::ThreadType;
|
||||||
@ -901,6 +902,9 @@ async fn project_create_inner(
|
|||||||
color: project_builder.color,
|
color: project_builder.color,
|
||||||
thread_id: thread_id.into(),
|
thread_id: thread_id.into(),
|
||||||
monetization_status: MonetizationStatus::Monetized,
|
monetization_status: MonetizationStatus::Monetized,
|
||||||
|
// New projects are considered reviewed with respect to side types migrations
|
||||||
|
side_types_migration_review_status:
|
||||||
|
SideTypesMigrationReviewStatus::Reviewed,
|
||||||
fields: HashMap::new(), // Fields instantiate to empty
|
fields: HashMap::new(), // Fields instantiate to empty
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ use crate::models::notifications::NotificationBody;
|
|||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::projects::{
|
use crate::models::projects::{
|
||||||
MonetizationStatus, Project, ProjectStatus, SearchRequest,
|
MonetizationStatus, Project, ProjectStatus, SearchRequest,
|
||||||
|
SideTypesMigrationReviewStatus,
|
||||||
};
|
};
|
||||||
use crate::models::teams::ProjectPermissions;
|
use crate::models::teams::ProjectPermissions;
|
||||||
use crate::models::threads::MessageBody;
|
use crate::models::threads::MessageBody;
|
||||||
@ -247,6 +248,8 @@ pub struct EditProject {
|
|||||||
#[validate(length(max = 65536))]
|
#[validate(length(max = 65536))]
|
||||||
pub moderation_message_body: Option<Option<String>>,
|
pub moderation_message_body: Option<Option<String>>,
|
||||||
pub monetization_status: Option<MonetizationStatus>,
|
pub monetization_status: Option<MonetizationStatus>,
|
||||||
|
pub side_types_migration_review_status:
|
||||||
|
Option<SideTypesMigrationReviewStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -844,6 +847,29 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(side_types_migration_review_status) =
|
||||||
|
&new_project.side_types_migration_review_status
|
||||||
|
{
|
||||||
|
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||||
|
return Err(ApiError::CustomAuthentication(
|
||||||
|
"You do not have the permissions to edit the side types migration review status of this project!"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE mods
|
||||||
|
SET side_types_migration_review_status = $1
|
||||||
|
WHERE id = $2
|
||||||
|
",
|
||||||
|
side_types_migration_review_status.as_str(),
|
||||||
|
id as db_ids::DBProjectId,
|
||||||
|
)
|
||||||
|
.execute(&mut *transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
// check new description and body for links to associated images
|
// check new description and body for links to associated images
|
||||||
// if they no longer exist in the description or body, delete them
|
// if they no longer exist in the description or body, delete them
|
||||||
let checkable_strings: Vec<&str> =
|
let checkable_strings: Vec<&str> =
|
||||||
|
|||||||
@ -362,7 +362,7 @@ pub async fn index_local(
|
|||||||
let (_, v2_og_project_type) =
|
let (_, v2_og_project_type) =
|
||||||
LegacyProject::get_project_type(&project_types);
|
LegacyProject::get_project_type(&project_types);
|
||||||
let (client_side, server_side) =
|
let (client_side, server_side) =
|
||||||
v2_reroute::convert_side_types_v2(
|
v2_reroute::convert_v3_side_types_to_v2_side_types(
|
||||||
&unvectorized_loader_fields,
|
&unvectorized_loader_fields,
|
||||||
Some(&v2_og_project_type),
|
Some(&v2_og_project_type),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -350,11 +350,8 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[
|
|||||||
"color",
|
"color",
|
||||||
// Note: loader fields are not here, but are added on as they are needed (so they can be dynamically added depending on which exist).
|
// Note: loader fields are not here, but are added on as they are needed (so they can be dynamically added depending on which exist).
|
||||||
// TODO: remove these- as they should be automatically populated. This is a band-aid fix.
|
// TODO: remove these- as they should be automatically populated. This is a band-aid fix.
|
||||||
"server_only",
|
"environment",
|
||||||
"client_only",
|
|
||||||
"game_versions",
|
"game_versions",
|
||||||
"singleplayer",
|
|
||||||
"client_and_server",
|
|
||||||
"mrpack_loaders",
|
"mrpack_loaders",
|
||||||
// V2 legacy fields for logical consistency
|
// V2 legacy fields for logical consistency
|
||||||
"client_side",
|
"client_side",
|
||||||
@ -397,11 +394,8 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
|||||||
"color",
|
"color",
|
||||||
// Note: loader fields are not here, but are added on as they are needed (so they can be dynamically added depending on which exist).
|
// Note: loader fields are not here, but are added on as they are needed (so they can be dynamically added depending on which exist).
|
||||||
// TODO: remove these- as they should be automatically populated. This is a band-aid fix.
|
// TODO: remove these- as they should be automatically populated. This is a band-aid fix.
|
||||||
"server_only",
|
"environment",
|
||||||
"client_only",
|
|
||||||
"game_versions",
|
"game_versions",
|
||||||
"singleplayer",
|
|
||||||
"client_and_server",
|
|
||||||
"mrpack_loaders",
|
"mrpack_loaders",
|
||||||
// V2 legacy fields for logical consistency
|
// V2 legacy fields for logical consistency
|
||||||
"client_side",
|
"client_side",
|
||||||
|
|||||||
@ -73,10 +73,7 @@ pub fn get_public_version_creation_data_json(
|
|||||||
|
|
||||||
// Loader fields
|
// Loader fields
|
||||||
"game_versions": ["1.20.1"],
|
"game_versions": ["1.20.1"],
|
||||||
"singleplayer": true,
|
"environment": "client_only_server_optional",
|
||||||
"client_and_server": true,
|
|
||||||
"client_only": true,
|
|
||||||
"server_only": false,
|
|
||||||
});
|
});
|
||||||
if is_modpack {
|
if is_modpack {
|
||||||
j["mrpack_loaders"] = json!(["fabric"]);
|
j["mrpack_loaders"] = json!(["fabric"]);
|
||||||
|
|||||||
@ -63,7 +63,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 0;
|
let id = 0;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[4..6] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[4..6] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "server_only" },
|
||||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||||
]))
|
]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -78,7 +78,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 1;
|
let id = 1;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server" },
|
||||||
]))
|
]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
project_creation_futures.push(create_async_future(
|
project_creation_futures.push(create_async_future(
|
||||||
@ -92,7 +92,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 2;
|
let id = 2;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..2] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "server_only" },
|
||||||
{ "op": "add", "path": "/name", "value": "Mysterious Project" },
|
{ "op": "add", "path": "/name", "value": "Mysterious Project" },
|
||||||
]))
|
]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -107,7 +107,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 3;
|
let id = 3;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "server_only" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.4"] },
|
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.4"] },
|
||||||
{ "op": "add", "path": "/name", "value": "Mysterious Project" },
|
{ "op": "add", "path": "/name", "value": "Mysterious Project" },
|
||||||
{ "op": "add", "path": "/license_id", "value": "LicenseRef-All-Rights-Reserved" },
|
{ "op": "add", "path": "/license_id", "value": "LicenseRef-All-Rights-Reserved" },
|
||||||
@ -124,7 +124,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 4;
|
let id = 4;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[0..3] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
||||||
]))
|
]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -139,7 +139,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 5;
|
let id = 5;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.5"] },
|
||||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||||
]))
|
]))
|
||||||
@ -155,8 +155,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 6;
|
let id = 6;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server_prefers_both" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
|
||||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||||
]))
|
]))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -173,8 +172,7 @@ pub async fn setup_search_projects(
|
|||||||
let id = 7;
|
let id = 7;
|
||||||
let modify_json = serde_json::from_value(json!([
|
let modify_json = serde_json::from_value(json!([
|
||||||
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
{ "op": "add", "path": "/categories", "value": DUMMY_CATEGORIES[5..6] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/client_only", "value": false },
|
{ "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server_prefers_both" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/server_only", "value": true },
|
|
||||||
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
{ "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" },
|
||||||
{ "op": "add", "path": "/initial_versions/0/loaders", "value": ["forge"] },
|
{ "op": "add", "path": "/initial_versions/0/loaders", "value": ["forge"] },
|
||||||
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.2"] },
|
{ "op": "add", "path": "/initial_versions/0/game_versions", "value": ["1.20.2"] },
|
||||||
|
|||||||
@ -68,7 +68,7 @@ INSERT INTO loader_field_enum_values(enum_id, value, metadata, ordering)
|
|||||||
VALUES (2, 'Ordering_Positive100', '{"type":"release","major":false}', 100);
|
VALUES (2, 'Ordering_Positive100', '{"type":"release","major":false}', 100);
|
||||||
|
|
||||||
INSERT INTO loader_fields_loaders(loader_id, loader_field_id)
|
INSERT INTO loader_fields_loaders(loader_id, loader_field_id)
|
||||||
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf WHERE lf.field IN ('game_versions','singleplayer', 'client_and_server', 'client_only', 'server_only') ON CONFLICT DO NOTHING;
|
SELECT l.id, lf.id FROM loaders l CROSS JOIN loader_fields lf WHERE lf.field IN ('game_versions','environment') ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
INSERT INTO categories (id, category, project_type) VALUES
|
INSERT INTO categories (id, category, project_type) VALUES
|
||||||
(51, 'combat', 1),
|
(51, 'combat', 1),
|
||||||
|
|||||||
@ -114,7 +114,7 @@ async fn creating_loader_fields() {
|
|||||||
Some(
|
Some(
|
||||||
serde_json::from_value(json!([{
|
serde_json::from_value(json!([{
|
||||||
"op": "remove",
|
"op": "remove",
|
||||||
"path": "/singleplayer"
|
"path": "/environment"
|
||||||
}]))
|
}]))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
@ -273,12 +273,8 @@ async fn creating_loader_fields() {
|
|||||||
"value": ["1.20.1", "1.20.2"]
|
"value": ["1.20.1", "1.20.2"]
|
||||||
}, {
|
}, {
|
||||||
"op": "add",
|
"op": "add",
|
||||||
"path": "/singleplayer",
|
"path": "/environment",
|
||||||
"value": false
|
"value": "client_or_server_prefers_both"
|
||||||
}, {
|
|
||||||
"op": "add",
|
|
||||||
"path": "/server_only",
|
|
||||||
"value": true
|
|
||||||
}]))
|
}]))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
@ -286,16 +282,17 @@ async fn creating_loader_fields() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(&v.fields["game_versions"], &json!(["1.20.1", "1.20.2"]));
|
assert_eq!(&v.fields["game_versions"], &json!(["1.20.1", "1.20.2"]));
|
||||||
assert_eq!(&v.fields["singleplayer"], &json!(false));
|
assert_eq!(
|
||||||
assert_eq!(&v.fields["server_only"], &json!(true));
|
&v.fields["environment"],
|
||||||
|
&json!("client_or_server_prefers_both")
|
||||||
|
);
|
||||||
// - Patch
|
// - Patch
|
||||||
let resp = api
|
let resp = api
|
||||||
.edit_version(
|
.edit_version(
|
||||||
alpha_version_id,
|
alpha_version_id,
|
||||||
json!({
|
json!({
|
||||||
"game_versions": ["1.20.1", "1.20.2"],
|
"game_versions": ["1.20.1", "1.20.2"],
|
||||||
"singleplayer": false,
|
"environment": "client_or_server_prefers_both"
|
||||||
"server_only": true
|
|
||||||
}),
|
}),
|
||||||
USER_USER_PAT,
|
USER_USER_PAT,
|
||||||
)
|
)
|
||||||
@ -320,8 +317,8 @@ async fn creating_loader_fields() {
|
|||||||
"value": ["1.20.5"]
|
"value": ["1.20.5"]
|
||||||
}, {
|
}, {
|
||||||
"op": "add",
|
"op": "add",
|
||||||
"path": "/singleplayer",
|
"path": "/environment",
|
||||||
"value": false
|
"value": "client_or_server"
|
||||||
}]))
|
}]))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
),
|
),
|
||||||
@ -357,8 +354,13 @@ async fn creating_loader_fields() {
|
|||||||
&project.fields["game_versions"],
|
&project.fields["game_versions"],
|
||||||
&[json!("1.20.1"), json!("1.20.2"), json!("1.20.5")]
|
&[json!("1.20.1"), json!("1.20.2"), json!("1.20.5")]
|
||||||
);
|
);
|
||||||
assert!(project.fields["singleplayer"].contains(&json!(false)));
|
assert!(
|
||||||
assert!(project.fields["singleplayer"].contains(&json!(true)));
|
project.fields["environment"].contains(&json!("client_or_server"))
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
project.fields["environment"]
|
||||||
|
.contains(&json!("client_or_server_prefers_both"))
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -421,10 +423,7 @@ async fn get_available_loader_fields() {
|
|||||||
fabric_loader_fields,
|
fabric_loader_fields,
|
||||||
[
|
[
|
||||||
"game_versions",
|
"game_versions",
|
||||||
"singleplayer",
|
"environment",
|
||||||
"client_and_server",
|
|
||||||
"client_only",
|
|
||||||
"server_only",
|
|
||||||
"test_fabric_optional" // exists for testing
|
"test_fabric_optional" // exists for testing
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
@ -444,10 +443,7 @@ async fn get_available_loader_fields() {
|
|||||||
mrpack_loader_fields,
|
mrpack_loader_fields,
|
||||||
[
|
[
|
||||||
"game_versions",
|
"game_versions",
|
||||||
"singleplayer",
|
"environment",
|
||||||
"client_and_server",
|
|
||||||
"client_only",
|
|
||||||
"server_only",
|
|
||||||
// mrpack has all the general fields as well as this
|
// mrpack has all the general fields as well as this
|
||||||
"mrpack_loaders"
|
"mrpack_loaders"
|
||||||
]
|
]
|
||||||
|
|||||||
@ -52,8 +52,11 @@ async fn search_projects() {
|
|||||||
vec![1, 2, 3, 4],
|
vec![1, 2, 3, 4],
|
||||||
),
|
),
|
||||||
(json!([["project_types:modpack"]]), vec![4]),
|
(json!([["project_types:modpack"]]), vec![4]),
|
||||||
(json!([["client_only:true"]]), vec![0, 2, 3, 7, 9]),
|
(json!([["environment:server_only"]]), vec![0, 2, 3]),
|
||||||
(json!([["server_only:true"]]), vec![0, 2, 3, 6, 7]),
|
(
|
||||||
|
json!([["environment:client_or_server_prefers_both"]]),
|
||||||
|
vec![6, 7],
|
||||||
|
),
|
||||||
(json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7, 9]),
|
(json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7, 9]),
|
||||||
(json!([["license:MIT"]]), vec![1, 2, 4, 9]),
|
(json!([["license:MIT"]]), vec![1, 2, 4, 9]),
|
||||||
(json!([[r#"name:'Mysterious Project'"#]]), vec![2, 3]),
|
(json!([[r#"name:'Mysterious Project'"#]]), vec![2, 3]),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user