diff --git a/apps/labrinth/migrations/20250523174544_project-versions-environments.sql b/apps/labrinth/migrations/20250523174544_project-versions-environments.sql new file mode 100644 index 000000000..f3db809e3 --- /dev/null +++ b/apps/labrinth/migrations/20250523174544_project-versions-environments.sql @@ -0,0 +1,114 @@ +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'); + +END; +$$ diff --git a/apps/labrinth/src/models/v2/projects.rs b/apps/labrinth/src/models/v2/projects.rs index 35a340f0b..0af1e53fe 100644 --- a/apps/labrinth/src/models/v2/projects.rs +++ b/apps/labrinth/src/models/v2/projects.rs @@ -127,7 +127,7 @@ impl LegacyProject { .collect(); 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 .version_fields .iter() @@ -135,10 +135,11 @@ impl LegacyProject { (f.field_name.clone(), f.value.clone().serialize_internal()) }) .collect::>(); - (client_side, server_side) = v2_reroute::convert_side_types_v2( - &fields, - Some(&*og_project_type), - ); + (client_side, server_side) = + v2_reroute::convert_v3_side_types_to_v2_side_types( + &fields, + Some(&*og_project_type), + ); // - if loader is mrpack, this is a modpack // the loaders are whatever the corresponding loader fields are diff --git a/apps/labrinth/src/models/v2/search.rs b/apps/labrinth/src/models/v2/search.rs index dfc9356b7..1aabaca14 100644 --- a/apps/labrinth/src/models/v2/search.rs +++ b/apps/labrinth/src/models/v2/search.rs @@ -102,28 +102,20 @@ impl LegacyResultSearchProject { let project_loader_fields = 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 .get(key) - .cloned() - .unwrap_or_default() + .map_or(&[][..], |values| values.as_slice()) .first() - .and_then(|s| s.as_bool()) + .and_then(|s| s.as_str()) }; - let singleplayer = get_one_bool_loader_field("singleplayer"); - let client_only = - 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 environment = + get_one_string_loader_field("environment").unwrap_or("unknown"); let (client_side, server_side) = - v2_reroute::convert_side_types_v2_bools( - singleplayer, - client_only, - server_only, - client_and_server, + v2_reroute::convert_v3_environment_to_v2_side_types( + environment, Some(&*og_project_type), ); let client_side = client_side.to_string(); diff --git a/apps/labrinth/src/queue/moderation.rs b/apps/labrinth/src/queue/moderation.rs index 49c2e38c2..3aaa932fc 100644 --- a/apps/labrinth/src/queue/moderation.rs +++ b/apps/labrinth/src/queue/moderation.rs @@ -242,7 +242,7 @@ impl AutomatedModerationQueue { 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); } diff --git a/apps/labrinth/src/routes/v2/project_creation.rs b/apps/labrinth/src/routes/v2/project_creation.rs index 540b3a6e1..9a4562430 100644 --- a/apps/labrinth/src/routes/v2/project_creation.rs +++ b/apps/labrinth/src/routes/v2/project_creation.rs @@ -158,10 +158,12 @@ pub async fn project_create( .into_iter() .map(|v| { let mut fields = HashMap::new(); - fields.extend(v2_reroute::convert_side_types_v3( - client_side, - server_side, - )); + fields.extend( + v2_reroute::convert_v2_side_types_to_v3_side_types( + client_side, + server_side, + ), + ); fields.insert( "game_versions".to_string(), json!(v.game_versions), diff --git a/apps/labrinth/src/routes/v2/projects.rs b/apps/labrinth/src/routes/v2/projects.rs index 4915b3ad0..dae61bd9c 100644 --- a/apps/labrinth/src/routes/v2/projects.rs +++ b/apps/labrinth/src/routes/v2/projects.rs @@ -547,10 +547,12 @@ pub async fn project_edit( let version = Version::from(version); let mut fields = version.fields; 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 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, server_side, )); diff --git a/apps/labrinth/src/routes/v2/version_creation.rs b/apps/labrinth/src/routes/v2/version_creation.rs index 093f1736b..24f893f22 100644 --- a/apps/labrinth/src/routes/v2/version_creation.rs +++ b/apps/labrinth/src/routes/v2/version_creation.rs @@ -105,7 +105,7 @@ pub async fn version_create( 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 = match v3::tags::loader_list(client.clone(), redis.clone()) .await @@ -136,53 +136,32 @@ pub async fn version_create( .collect::>(); // 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, - // so the 'missing' ones can't be easily accessed, and versions do need to have these fields explicitly set. - let side_type_loader_field_names = [ - "singleplayer", - "client_and_server", - "client_only", - "server_only", - ]; + // so the 'missing' ones can't be easily accessed, and versions do need to have that field explicitly set. - // 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. if loader_fields_aggregate .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. - fields.extend( - side_type_loader_field_names - .iter() - .map(|f| (f.to_string(), json!(false))), - ); - if let Some(example_version_fields) = + // If so, we get the field of an example version of the project, and set the side types to match. + fields.insert( + "environment".into(), get_example_version_fields( legacy_create.project_id, client, &redis, ) .await? - { - fields.extend( - example_version_fields.into_iter().filter_map( - |f| { - if side_type_loader_field_names - .contains(&f.field_name.as_str()) - { - Some(( - f.field_name, - f.value.serialize_internal(), - )) - } else { - None - } - }, - ), - ); - } + .into_iter() + .flatten() + .find(|f| f.field_name == "environment") + .map_or(json!("unknown"), |f| { + f.value.serialize_internal() + }), + ); } // Handle project type via file extension prediction let mut project_type = None; diff --git a/apps/labrinth/src/routes/v2_reroute.rs b/apps/labrinth/src/routes/v2_reroute.rs index a73baac4d..18cf774d6 100644 --- a/apps/labrinth/src/routes/v2_reroute.rs +++ b/apps/labrinth/src/routes/v2_reroute.rs @@ -164,69 +164,46 @@ where Ok(new_multipart) } -// Converts a "client_side" and "server_side" pair into the new v3 corresponding fields -pub fn convert_side_types_v3( +/// Converts V2 side types to V3 side types. +pub fn convert_v2_side_types_to_v3_side_types( client_side: LegacySideType, server_side: LegacySideType, ) -> HashMap { - use LegacySideType::{Optional, Required}; - - let singleplayer = client_side == Required - || client_side == Optional - || server_side == Required - || server_side == Optional; - let client_and_server = singleplayer; - let client_only = (client_side == Required || client_side == Optional) - && server_side != Required; - let server_only = (server_side == Required || server_side == Optional) - && client_side != Required; + use LegacySideType::{Optional, Required, Unsupported}; + + let environment = match (client_side, server_side) { + (Required, Required) => "client_and_server", // Or "singleplayer_only" + (Required, Unsupported) => "client_only", + (Required, Optional) => "client_only_server_optional", + (Unsupported, Required) => "server_only", // Or "dedicated_server_only" + (Optional, Required) => "server_only_client_optional", + (Optional, Optional) => "client_or_server", // Or "client_or_server_prefers_both" + _ => "unknown", + }; - let mut fields = HashMap::new(); - fields.insert("singleplayer".to_string(), json!(singleplayer)); - fields.insert("client_and_server".to_string(), json!(client_and_server)); - fields.insert("client_only".to_string(), json!(client_only)); - fields.insert("server_only".to_string(), json!(server_only)); - fields + [("environment".to_string(), json!(environment))] + .into_iter() + .collect() } -// Convert search facets from V3 back to v2 -// this is not lossless. (See tests) -pub fn convert_side_types_v2( +/// Converts a V3 side types map into the corresponding V2 side types. +pub fn convert_v3_side_types_to_v2_side_types( side_types: &HashMap, project_type: Option<&str>, ) -> (LegacySideType, LegacySideType) { - let client_and_server = side_types - .get("client_and_server") - .and_then(|x| x.as_bool()) - .unwrap_or(false); - let singleplayer = side_types - .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), + convert_v3_environment_to_v2_side_types( + side_types + .get("environment") + .and_then(|x| x.as_str()) + .unwrap_or("unknown"), project_type, ) } -// Client side, server side -pub fn convert_side_types_v2_bools( - singleplayer: Option, - client_only: bool, - server_only: bool, - client_and_server: Option, +/// Converts a V3 environment and project type into the corresponding V2 side types. +/// The first side type is for the client, the second is for the server. +pub fn convert_v3_environment_to_v2_side_types( + environment: &str, project_type: Option<&str>, ) -> (LegacySideType, LegacySideType) { use LegacySideType::{Optional, Required, Unknown, Unsupported}; @@ -236,30 +213,18 @@ pub fn convert_side_types_v2_bools( Some("datapack") => (Optional, Required), Some("shader") => (Required, Unsupported), Some("resourcepack") => (Required, Unsupported), - _ => { - let singleplayer = - singleplayer.or(client_and_server).unwrap_or(false); - - match (singleplayer, client_only, server_only) { - // Only singleplayer - (true, false, false) => (Required, Required), - - // Client only and not server only - (false, true, false) => (Required, Unsupported), - (true, true, false) => (Required, Unsupported), - - // 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), - } - } + _ => match environment { + "client_and_server" => (Required, Required), + "client_only" => (Required, Unsupported), + "client_only_server_optional" => (Required, Optional), + "singleplayer_only" => (Required, Required), + "server_only" => (Unsupported, Required), + "server_only_client_optional" => (Optional, Required), + "dedicated_server_only" => (Unsupported, Required), + "client_or_server" => (Optional, Optional), + "client_or_server_prefers_both" => (Optional, Optional), + _ => (Unknown, Unknown), // "unknown" + }, } } @@ -279,13 +244,14 @@ mod tests { }; #[test] - fn convert_types() { - // Converting types from V2 to V3 and back should be idempotent- for certain pairs + fn v2_v3_side_type_conversion() { + // 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 = [ (Optional, Unsupported), (Unsupported, Optional), - (Required, Optional), - (Optional, Required), (Unsupported, Unsupported), ]; @@ -294,10 +260,13 @@ mod tests { if lossy_pairs.contains(&(client_side, server_side)) { continue; } - let side_types = - convert_side_types_v3(client_side, server_side); + let side_types = convert_v2_side_types_to_v3_side_types( + client_side, + server_side, + ); 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!(server_side, server_side2); } diff --git a/apps/labrinth/src/search/indexing/local_import.rs b/apps/labrinth/src/search/indexing/local_import.rs index c6d2e408c..4a3463572 100644 --- a/apps/labrinth/src/search/indexing/local_import.rs +++ b/apps/labrinth/src/search/indexing/local_import.rs @@ -362,7 +362,7 @@ pub async fn index_local( let (_, v2_og_project_type) = LegacyProject::get_project_type(&project_types); let (client_side, server_side) = - v2_reroute::convert_side_types_v2( + v2_reroute::convert_v3_side_types_to_v2_side_types( &unvectorized_loader_fields, Some(&v2_og_project_type), ); diff --git a/apps/labrinth/src/search/indexing/mod.rs b/apps/labrinth/src/search/indexing/mod.rs index 3f4dcdee9..6ae58eb49 100644 --- a/apps/labrinth/src/search/indexing/mod.rs +++ b/apps/labrinth/src/search/indexing/mod.rs @@ -327,11 +327,8 @@ const DEFAULT_DISPLAYED_ATTRIBUTES: &[&str] = &[ "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). // TODO: remove these- as they should be automatically populated. This is a band-aid fix. - "server_only", - "client_only", + "environment", "game_versions", - "singleplayer", - "client_and_server", "mrpack_loaders", // V2 legacy fields for logical consistency "client_side", @@ -374,11 +371,8 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[ "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). // TODO: remove these- as they should be automatically populated. This is a band-aid fix. - "server_only", - "client_only", + "environment", "game_versions", - "singleplayer", - "client_and_server", "mrpack_loaders", // V2 legacy fields for logical consistency "client_side", diff --git a/apps/labrinth/tests/common/api_v3/request_data.rs b/apps/labrinth/tests/common/api_v3/request_data.rs index 3e2586f45..f1a6902cb 100644 --- a/apps/labrinth/tests/common/api_v3/request_data.rs +++ b/apps/labrinth/tests/common/api_v3/request_data.rs @@ -74,10 +74,7 @@ pub fn get_public_version_creation_data_json( // Loader fields "game_versions": ["1.20.1"], - "singleplayer": true, - "client_and_server": true, - "client_only": true, - "server_only": false, + "environment": "client_only_server_optional", }); if is_modpack { j["mrpack_loaders"] = json!(["fabric"]); diff --git a/apps/labrinth/tests/common/search.rs b/apps/labrinth/tests/common/search.rs index daf7a9eb5..0f7559b29 100644 --- a/apps/labrinth/tests/common/search.rs +++ b/apps/labrinth/tests/common/search.rs @@ -65,7 +65,7 @@ pub async fn setup_search_projects( let id = 0; let modify_json = serde_json::from_value(json!([ { "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" }, ])) .unwrap(); @@ -80,7 +80,7 @@ pub async fn setup_search_projects( let id = 1; let modify_json = serde_json::from_value(json!([ { "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(); project_creation_futures.push(create_async_future( @@ -94,7 +94,7 @@ pub async fn setup_search_projects( let id = 2; let modify_json = serde_json::from_value(json!([ { "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" }, ])) .unwrap(); @@ -109,7 +109,7 @@ pub async fn setup_search_projects( let id = 3; let modify_json = serde_json::from_value(json!([ { "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": "/name", "value": "Mysterious Project" }, { "op": "add", "path": "/license_id", "value": "LicenseRef-All-Rights-Reserved" }, @@ -126,7 +126,7 @@ pub async fn setup_search_projects( let id = 4; let modify_json = serde_json::from_value(json!([ { "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"] }, ])) .unwrap(); @@ -141,7 +141,7 @@ pub async fn setup_search_projects( let id = 5; let modify_json = serde_json::from_value(json!([ { "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": "/license_id", "value": "LGPL-3.0-or-later" }, ])) @@ -157,8 +157,7 @@ pub async fn setup_search_projects( let id = 6; let modify_json = serde_json::from_value(json!([ { "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/server_only", "value": true }, + { "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server_prefers_both" }, { "op": "add", "path": "/license_id", "value": "LGPL-3.0-or-later" }, ])) .unwrap(); @@ -175,8 +174,7 @@ pub async fn setup_search_projects( let id = 7; let modify_json = serde_json::from_value(json!([ { "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/server_only", "value": true }, + { "op": "add", "path": "/initial_versions/0/environment", "value": "client_or_server_prefers_both" }, { "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/game_versions", "value": ["1.20.2"] }, diff --git a/apps/labrinth/tests/files/dummy_data.sql b/apps/labrinth/tests/files/dummy_data.sql index f3fb1e47d..9866a9d45 100644 --- a/apps/labrinth/tests/files/dummy_data.sql +++ b/apps/labrinth/tests/files/dummy_data.sql @@ -67,8 +67,8 @@ VALUES (2, 'Ordering_Negative1', '{"type":"release","major":false}', -1); INSERT INTO loader_field_enum_values(enum_id, value, metadata, ordering) VALUES (2, 'Ordering_Positive100', '{"type":"release","major":false}', 100); -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; +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','environment') ON CONFLICT DO NOTHING; INSERT INTO categories (id, category, project_type) VALUES (51, 'combat', 1), @@ -108,6 +108,6 @@ VALUES ( INSERT INTO oauth_client_redirect_uris (id, client_id, uri) VALUES (1, 1, 'https://modrinth.com/oauth_callback'); -- Create dummy data table to mark that this file has been run -CREATE TABLE dummy_data ( +CREATE TABLE dummy_data ( update_id bigint PRIMARY KEY ); diff --git a/apps/labrinth/tests/loader_fields.rs b/apps/labrinth/tests/loader_fields.rs index 7caf8beb4..4b9ac1e2e 100644 --- a/apps/labrinth/tests/loader_fields.rs +++ b/apps/labrinth/tests/loader_fields.rs @@ -115,7 +115,7 @@ async fn creating_loader_fields() { Some( serde_json::from_value(json!([{ "op": "remove", - "path": "/singleplayer" + "path": "/environment" }])) .unwrap(), ), @@ -274,12 +274,8 @@ async fn creating_loader_fields() { "value": ["1.20.1", "1.20.2"] }, { "op": "add", - "path": "/singleplayer", - "value": false - }, { - "op": "add", - "path": "/server_only", - "value": true + "path": "/environment", + "value": "client_or_server_prefers_both" }])) .unwrap(), ), @@ -290,16 +286,17 @@ async fn creating_loader_fields() { v.fields.get("game_versions").unwrap(), &json!(["1.20.1", "1.20.2"]) ); - assert_eq!(v.fields.get("singleplayer").unwrap(), &json!(false)); - assert_eq!(v.fields.get("server_only").unwrap(), &json!(true)); + assert_eq!( + v.fields.get("environment").unwrap(), + &json!("client_or_server_prefers_both") + ); // - Patch let resp = api .edit_version( alpha_version_id, json!({ "game_versions": ["1.20.1", "1.20.2"], - "singleplayer": false, - "server_only": true + "environment": "client_or_server_prefers_both" }), USER_USER_PAT, ) @@ -327,8 +324,8 @@ async fn creating_loader_fields() { "value": ["1.20.5"] }, { "op": "add", - "path": "/singleplayer", - "value": false + "path": "/environment", + "value": "client_or_server" }])) .unwrap(), ), @@ -367,16 +364,16 @@ async fn creating_loader_fields() { assert!( project .fields - .get("singleplayer") + .get("environment") .unwrap() - .contains(&json!(false)) + .contains(&json!("client_or_server")) ); assert!( project .fields - .get("singleplayer") + .get("environment") .unwrap() - .contains(&json!(true)) + .contains(&json!("client_or_server_prefers_both")) ); }) .await @@ -440,10 +437,7 @@ async fn get_available_loader_fields() { fabric_loader_fields, [ "game_versions", - "singleplayer", - "client_and_server", - "client_only", - "server_only", + "environment", "test_fabric_optional" // exists for testing ] .iter() @@ -463,10 +457,7 @@ async fn get_available_loader_fields() { mrpack_loader_fields, [ "game_versions", - "singleplayer", - "client_and_server", - "client_only", - "server_only", + "environment", // mrpack has all the general fields as well as this "mrpack_loaders" ] diff --git a/apps/labrinth/tests/search.rs b/apps/labrinth/tests/search.rs index e05b13def..b11ad6e0f 100644 --- a/apps/labrinth/tests/search.rs +++ b/apps/labrinth/tests/search.rs @@ -52,8 +52,11 @@ async fn search_projects() { vec![1, 2, 3, 4], ), (json!([["project_types:modpack"]]), vec![4]), - (json!([["client_only:true"]]), vec![0, 2, 3, 7, 9]), - (json!([["server_only:true"]]), vec![0, 2, 3, 6, 7]), + (json!([["environment:server_only"]]), vec![0, 2, 3]), + ( + json!([["environment:client_or_server_prefers_both"]]), + vec![6, 7], + ), (json!([["open_source:true"]]), vec![0, 1, 2, 4, 5, 6, 7, 9]), (json!([["license:MIT"]]), vec![1, 2, 4, 9]), (json!([[r#"name:'Mysterious Project'"#]]), vec![2, 3]),