game_manager_lib/commands/metadata/
search.rs1use crate::database;
7use crate::errors::AppError;
8use crate::models::Game;
9use crate::services::integration::gamebrain::{GameMedia, SimilarGame};
10use crate::services::integration::gamerpower::{self, Giveaway};
11use crate::services::integration::{gamebrain, rawg};
12use crate::services::recommendation::core::calculate_game_weight;
13use serde::{Deserialize, Serialize};
14use tauri::AppHandle;
15
16#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct ProfileSimilarGame {
23 #[serde(flatten)]
24 pub game: SimilarGame,
25 #[serde(rename = "becauseOf")]
27 pub because_of: String,
28}
29
30#[tauri::command]
34pub async fn fetch_game_details(
35 app: AppHandle,
36 query: String,
37) -> Result<rawg::GameDetails, AppError> {
38 let api_key = database::get_secret(&app, "rawg_api_key")?;
39 rawg::fetch_game_details(&api_key, query)
40 .await
41 .map_err(AppError::NetworkError)
42}
43
44#[tauri::command]
46pub async fn get_active_giveaways(app: AppHandle) -> Result<Vec<Giveaway>, AppError> {
47 gamerpower::fetch_giveaways(&app)
48 .await
49 .map_err(AppError::NetworkError)
50}
51
52#[tauri::command]
62pub async fn get_profile_similar_games(
63 app: AppHandle,
64 user_games: Vec<Game>,
66) -> Result<Vec<ProfileSimilarGame>, String> {
67 if user_games.is_empty() {
68 return Ok(vec![]);
69 }
70
71 let mut weighted: Vec<(&Game, f32)> = user_games
73 .iter()
74 .map(|g| (g, calculate_game_weight(g)))
75 .collect();
76
77 weighted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
78
79 let anchors: Vec<&Game> = weighted.iter().take(2).map(|(g, _)| *g).collect();
80
81 let (result_a, result_b) = tokio::join!(
83 gamebrain::fetch_similar_games(&app, &anchors[0].id, &anchors[0].name, Some(5)),
84 gamebrain::fetch_similar_games(
86 &app,
87 &anchors.get(1).unwrap_or(&anchors[0]).id,
88 &anchors.get(1).unwrap_or(&anchors[0]).name,
89 Some(5),
90 ),
91 );
92
93 let library_names: std::collections::HashSet<String> = user_games
95 .iter()
96 .map(|g| g.name.trim().to_lowercase())
97 .collect();
98
99 let mut seen_ids = std::collections::HashSet::new();
101 let mut results: Vec<ProfileSimilarGame> = Vec::new();
102
103 let anchor_a_name = anchors[0].name.clone();
104 let anchor_b_name = anchors.get(1).map(|g| g.name.clone()).unwrap_or_default();
105
106 let games_a = result_a.unwrap_or_default();
108 let games_b = result_b.unwrap_or_default();
109 let max_len = games_a.len().max(games_b.len());
110
111 for i in 0..max_len {
112 for (games, because_of) in [(&games_a, &anchor_a_name), (&games_b, &anchor_b_name)] {
113 if let Some(similar) = games.get(i) {
114 if library_names.contains(&similar.name.trim().to_lowercase()) {
116 continue;
117 }
118 if !seen_ids.insert(similar.id.clone()) {
120 continue;
121 }
122 results.push(ProfileSimilarGame {
123 game: similar.clone(),
124 because_of: because_of.clone(),
125 });
126 }
127 }
128 }
129
130 Ok(results)
131}
132
133#[tauri::command]
135pub async fn get_trending_games(app: AppHandle) -> Result<Vec<rawg::RawgGame>, AppError> {
136 let api_key = database::get_secret(&app, "rawg_api_key")?;
137 rawg::fetch_trending_games(&app, &api_key)
138 .await
139 .map_err(AppError::NetworkError)
140}
141
142#[tauri::command]
144pub async fn get_upcoming_games(app: AppHandle) -> Result<Vec<rawg::RawgGame>, AppError> {
145 let api_key = database::get_secret(&app, "rawg_api_key")?;
146 rawg::fetch_upcoming_games(&app, &api_key)
147 .await
148 .map_err(AppError::NetworkError)
149}
150
151#[tauri::command]
153pub async fn get_similar_games(
154 app: AppHandle,
155 game_id: String,
156 game_name: String,
157) -> Result<Vec<SimilarGame>, String> {
158 gamebrain::fetch_similar_games(&app, &game_id, &game_name, Some(12)).await
159}
160
161#[tauri::command]
163pub async fn get_game_media(
164 app: AppHandle,
165 game_id: String,
166 game_name: String,
167) -> Result<GameMedia, String> {
168 gamebrain::fetch_game_media(&app, &game_id, &game_name).await
169}