game_manager_lib/services/integration/gamebrain/
search.rs1use crate::database;
4use crate::services::integration::gemini;
5use crate::utils::http_client::HTTP_CLIENT;
6
7use std::collections::HashSet;
8use tauri::AppHandle;
9
10use super::models::{
11 GameBrainFilter, GameBrainFilterValue, GameBrainSearchParams, GameBrainSearchResult,
12};
13use super::raw::RawSearchResponse;
14
15pub async fn search_games_by_features(
24 app: &AppHandle,
25 query: &str,
26 params: GameBrainSearchParams,
27) -> Result<Vec<GameBrainSearchResult>, String> {
28 let api_key = database::get_secret(app, "gamebrain_api_key").map_err(|e| e.to_string())?;
29
30 if api_key.trim().is_empty() {
31 return Err("GameBrain API key não configurada".into());
32 }
33
34 let cleaned_query = query.trim();
35
36 if cleaned_query.is_empty() {
37 return Ok(vec![]);
38 }
39
40 let english_query = match database::get_secret(app, "gemini_api_key") {
42 Ok(gemini_key) if !gemini_key.trim().is_empty() => {
43 gemini::translate_query_to_english(&gemini_key, cleaned_query)
44 .await
45 .unwrap_or_else(|_| cleaned_query.to_string())
46 }
47 _ => cleaned_query.to_string(),
48 };
49
50 tracing::debug!(
51 "GameBrain search => original='{}' translated='{}'",
52 cleaned_query,
53 english_query
54 );
55
56 let url = "https://api.gamebrain.co/v1/games";
57
58 tracing::debug!(
59 "GameBrain search => query='{}' filters={} sort={:?} limit={:?} offset={:?}",
60 cleaned_query,
61 params.filters.len(),
62 params.sort.as_ref().map(|s| s.as_str()),
63 params.limit,
64 params.offset,
65 );
66
67 let mut request = HTTP_CLIENT
68 .get(url)
69 .header("x-api-key", api_key)
70 .query(&[("query", english_query.as_str())]);
71
72 if !params.filters.is_empty() {
75 let filters_json = serde_json::to_string(¶ms.filters).map_err(|e| e.to_string())?;
76
77 request = request.query(&[("filters", filters_json)]);
78 }
79
80 if let Some(sort) = ¶ms.sort {
81 request = request.query(&[("sort", sort.as_str())]);
82 }
83
84 if let Some(order) = ¶ms.sort_order {
85 request = request.query(&[("sort-order", order.as_str())]);
86 }
87
88 if let Some(limit) = params.limit {
89 request = request.query(&[("limit", limit.to_string())]);
90 }
91
92 if let Some(offset) = params.offset {
93 request = request.query(&[("offset", offset.to_string())]);
94 }
95
96 let response = request.send().await.map_err(|e| {
97 tracing::error!("GameBrain request error: {}", e);
98 e.to_string()
99 })?;
100
101 let status = response.status();
102
103 if !status.is_success() {
104 let body = response.text().await.unwrap_or_default();
105
106 tracing::error!("GameBrain HTTP Error => status={} body={}", status, body);
107
108 return Err(format!("Erro GameBrain: {}", status));
109 }
110
111 let response_text = response.text().await.map_err(|e| e.to_string())?;
112
113 tracing::debug!("GameBrain response size: {} bytes", response_text.len());
114
115 let raw_response: RawSearchResponse = serde_json::from_str(&response_text).map_err(|e| {
116 tracing::error!("GameBrain JSON parse error: {}", e);
117
118 format!(
119 "Erro JSON GameBrain (linha {}, coluna {}): {}",
120 e.line(),
121 e.column(),
122 e
123 )
124 })?;
125
126 let results: Vec<GameBrainSearchResult> = raw_response
127 .results
128 .into_iter()
129 .map(|game| {
130 let cover_url = game
132 .image
133 .or_else(|| game.cover.filter(|c| !c.url.is_empty()).map(|c| c.url));
134
135 let platforms: Vec<String> = {
137 let mut seen = HashSet::new();
138 game.platforms
139 .into_iter()
140 .filter(|p| !p.name.is_empty() && seen.insert(p.name.clone()))
141 .map(|p| p.name)
142 .collect()
143 };
144
145 GameBrainSearchResult {
146 id: format!("gamebrain:{}", game.id),
148 name: game.name,
149 cover_url,
150 genre: game.genre,
151 year: game.year.map(|y| y as u32),
152 rating: game
154 .rating
155 .and_then(|r| r.mean)
156 .map(|m| (m * 100.0).round()),
157 platforms,
158 link: game.link,
159 }
160 })
161 .collect();
162
163 tracing::debug!("GameBrain parsed {} results", results.len());
164
165 Ok(results)
166}
167
168pub async fn search_pc_games_by_features(
173 app: &AppHandle,
174 query: &str,
175 mut params: GameBrainSearchParams,
176) -> Result<Vec<GameBrainSearchResult>, String> {
177 let already_has_platform = params.filters.iter().any(|f| f.key == "platform");
179
180 if !already_has_platform {
181 params.filters.push(GameBrainFilter {
182 key: "platform".into(),
183 values: vec![GameBrainFilterValue { value: "pc".into() }],
184 connection: Some("OR".into()),
185 });
186 }
187
188 search_games_by_features(app, query, params).await
189}