use itertools::Itertools; use serde::{Deserialize, Serialize}; use crate::{routes::v2_reroute, search::ResultSearchProject}; #[derive(Serialize, Deserialize, Debug)] pub struct LegacySearchResults { pub hits: Vec, pub offset: usize, pub limit: usize, pub total_hits: usize, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct LegacyResultSearchProject { pub project_id: String, pub project_type: String, pub slug: Option, pub author: String, pub title: String, pub description: String, pub categories: Vec, pub display_categories: Vec, pub versions: Vec, pub downloads: i32, pub follows: i32, pub icon_url: String, /// RFC 3339 formatted creation date of the project pub date_created: String, /// RFC 3339 formatted modification date of the project pub date_modified: String, pub latest_version: String, pub license: String, pub client_side: String, pub server_side: String, pub gallery: Vec, pub featured_gallery: Option, pub color: Option, } // TODO: In other PR, when these are merged, make sure the v2 search testing functions use these impl LegacyResultSearchProject { pub fn from(result_search_project: ResultSearchProject) -> Self { let mut categories = result_search_project.categories; if categories.contains(&"mrpack".to_string()) { if let Some(mrpack_loaders) = result_search_project.loader_fields.get("mrpack_loaders") { categories.extend( mrpack_loaders .iter() .filter_map(|c| c.as_str()) .map(String::from), ); categories.retain(|c| c != "mrpack"); } } let mut display_categories = result_search_project.display_categories; if display_categories.contains(&"mrpack".to_string()) { if let Some(mrpack_loaders) = result_search_project.loader_fields.get("mrpack_loaders") { categories.extend( mrpack_loaders .iter() .filter_map(|c| c.as_str()) .map(String::from), ); display_categories.retain(|c| c != "mrpack"); } } // Sort then remove duplicates categories.sort(); categories.dedup(); display_categories.sort(); display_categories.dedup(); // V2 versions only have one project type- v3 versions can rarely have multiple. // We'll prioritize 'modpack' first, then 'mod', and if neither are found, use the first one. // If there are no project types, default to 'project' let mut project_types = result_search_project.project_types; if project_types.contains(&"modpack".to_string()) { project_types = vec!["modpack".to_string()]; } else if project_types.contains(&"mod".to_string()) { project_types = vec!["mod".to_string()]; } let project_type = project_types .first() .cloned() .unwrap_or("project".to_string()); // Default to 'project' if none are found let project_type = if project_type == "datapack" || project_type == "plugin" { // These are not supported in V2, so we'll just use 'mod' instead "mod".to_string() } else { project_type }; let loader_fields = result_search_project.loader_fields.clone(); let get_one_bool_loader_field = |key: &str| { loader_fields .get(key) .cloned() .unwrap_or_default() .first() .and_then(|s| s.as_bool()) }; 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 (client_side, server_side) = v2_reroute::convert_side_types_v2_bools( singleplayer, client_only, server_only, client_and_server, ); let client_side = client_side.to_string(); let server_side = server_side.to_string(); let versions = result_search_project .loader_fields .get("game_versions") .cloned() .unwrap_or_default() .into_iter() .filter_map(|s| s.as_str().map(String::from)) .collect_vec(); Self { project_type, client_side, server_side, versions, latest_version: result_search_project.version_id, categories, project_id: result_search_project.project_id, slug: result_search_project.slug, author: result_search_project.author, title: result_search_project.name, description: result_search_project.summary, display_categories, downloads: result_search_project.downloads, follows: result_search_project.follows, icon_url: result_search_project.icon_url.unwrap_or_default(), license: result_search_project.license, date_created: result_search_project.date_created, date_modified: result_search_project.date_modified, gallery: result_search_project.gallery, featured_gallery: result_search_project.featured_gallery, color: result_search_project.color, } } } impl LegacySearchResults { pub fn from(search_results: crate::search::SearchResults) -> Self { Self { hits: search_results .hits .into_iter() .map(LegacyResultSearchProject::from) .collect(), offset: search_results.offset, limit: search_results.limit, total_hits: search_results.total_hits, } } }