game_manager_lib/services/integration/
rawg.rs1use crate::constants::{RAWG_SEARCH_PAGE_SIZE, RAWG_TRENDING_PAGE_SIZE, RAWG_UPCOMING_PAGE_SIZE};
10use crate::database::AppState;
11use crate::services::cache;
12use crate::utils::http_client::HTTP_CLIENT;
13use chrono::Datelike;
14use serde::{Deserialize, Serialize};
15use tauri::{AppHandle, Manager};
16
17#[derive(Debug, Serialize, Deserialize)]
20pub struct RawgTag {
21 pub id: i32,
22 pub name: String,
23 pub slug: String,
24 pub language: Option<String>,
25 pub games_count: i32,
26 pub image_background: Option<String>,
27}
28#[derive(Debug, Serialize, Deserialize)]
29pub struct RawgDeveloper {
30 pub id: i32,
31 pub name: String,
32 pub slug: String,
33}
34#[derive(Debug, Serialize, Deserialize)]
35pub struct RawgPublisher {
36 pub id: i32,
37 pub name: String,
38 pub slug: String,
39}
40#[derive(Debug, Deserialize, Serialize)]
41pub struct RawgGenre {
42 pub name: String,
43}
44
45#[derive(Debug, Serialize, Deserialize)]
46pub struct EsrbRating {
47 pub id: Option<i32>,
48 pub name: String,
49 pub slug: Option<String>,
50}
51
52#[derive(Debug, Serialize, Deserialize)]
54pub struct StoreInfo {
55 pub id: i32,
56 pub name: String,
57 pub slug: String,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
62pub struct StoreWrapper {
63 pub id: i32,
64 pub url: String,
65 pub store: StoreInfo,
66}
67
68#[derive(Debug, Deserialize, Serialize)]
70struct RawgResponse {
71 results: Vec<RawgGame>,
72}
73
74#[derive(Debug, Deserialize, Serialize)]
76pub struct RawgGame {
77 pub id: u32,
78 pub name: String,
79 #[serde(rename(deserialize = "background_image", serialize = "backgroundImage"))]
80 pub background_image: Option<String>,
81 pub rating: f32,
82 pub released: Option<String>,
83 pub genres: Vec<RawgGenre>,
84 #[serde(default)]
85 pub tags: Vec<RawgTag>,
86 pub slug: String,
87}
88
89#[derive(Debug, Serialize, Deserialize)]
93pub struct RawgGameDetails {
94 pub id: i32,
95 pub name: String,
96 #[serde(default)]
97 pub alternative_names: Vec<String>,
98 #[serde(rename(deserialize = "description_raw", serialize = "descriptionRaw"))]
99 pub description_raw: Option<String>,
100 pub metacritic: Option<i32>,
101 pub website: Option<String>,
102 pub released: Option<String>,
103 pub background_image: Option<String>,
104 #[serde(default)]
105 pub genres: Vec<RawgGenre>,
106 #[serde(default)]
107 pub tags: Vec<RawgTag>,
108 #[serde(default)]
109 pub developers: Vec<RawgDeveloper>,
110 #[serde(default)]
111 pub publishers: Vec<RawgPublisher>,
112 #[serde(default)]
113 pub reddit_url: Option<String>,
114 #[serde(default)]
115 pub metacritic_url: Option<String>,
116 #[serde(default)]
117 pub stores: Vec<StoreWrapper>,
118 #[serde(default)]
119 pub esrb_rating: Option<EsrbRating>,
120}
121
122pub async fn search_games(api_key: &str, query: &str) -> Result<Vec<RawgGame>, String> {
128 let url = format!(
129 "https://api.rawg.io/api/games?key={}&search={}&page_size={}",
130 api_key,
131 urlencoding::encode(query),
132 RAWG_SEARCH_PAGE_SIZE
133 );
134
135 let res = HTTP_CLIENT
136 .get(&url)
137 .send()
138 .await
139 .map_err(|e| e.to_string())?;
140
141 if !res.status().is_success() {
142 return Err(format!("Erro RAWG Search: {}", res.status()));
143 }
144
145 let data: RawgResponse = res.json().await.map_err(|e| e.to_string())?;
146 Ok(data.results)
147}
148
149pub async fn fetch_game_details(api_key: &str, query: String) -> Result<RawgGameDetails, String> {
153 let identifier = if query.chars().all(char::is_numeric) {
154 query
155 } else {
156 query
157 .to_lowercase()
158 .replace(" ", "-")
159 .replace(":", "")
160 .replace("'", "")
161 .replace("&", "")
162 .replace(".", "")
163 };
164
165 let url = format!(
166 "https://api.rawg.io/api/games/{}?key={}",
167 identifier, api_key
168 );
169
170 let res = HTTP_CLIENT
171 .get(&url)
172 .send()
173 .await
174 .map_err(|e| e.to_string())?;
175
176 if res.status().is_success() {
177 let details: RawgGameDetails = res.json().await.map_err(|e| e.to_string())?;
178 Ok(details)
179 } else if res.status().as_u16() == 404 {
180 Err("Jogo não encontrado na RAWG".into())
181 } else {
182 Err(format!("Erro RAWG Details: {}", res.status()))
183 }
184}
185
186pub async fn fetch_trending_games(app: &AppHandle, api_key: &str) -> Result<Vec<RawgGame>, String> {
191 let current_year = chrono::Utc::now().year();
192 let last_year = current_year - 1;
193 let cache_key = "rawg_list_trending";
194
195 let url = format!(
196 "https://api.rawg.io/api/games?key={}&dates={}-01-01,{}-12-31&ordering=-added&page_size={}",
197 api_key, last_year, current_year, RAWG_TRENDING_PAGE_SIZE
198 );
199
200 match HTTP_CLIENT.get(&url).send().await {
202 Ok(res) => {
203 if res.status().is_success() {
204 let data: RawgResponse = res.json().await.map_err(|e| e.to_string())?;
205
206 if let Ok(conn) = app.state::<AppState>().cache_db.lock() {
208 if let Ok(json) = serde_json::to_string(&data.results) {
209 let _ = cache::save_cached_api_data(&conn, "rawg", cache_key, &json);
210 }
211 }
212
213 return Ok(data.results);
214 }
215 }
216 Err(_) => {
217 }
219 }
220
221 if let Ok(conn) = app.state::<AppState>().cache_db.lock() {
223 if let Some(payload) = cache::get_stale_api_data(&conn, "rawg", cache_key) {
224 if let Ok(cached_games) = serde_json::from_str::<Vec<RawgGame>>(&payload) {
225 return Ok(cached_games);
226 }
227 }
228 }
229
230 Err("Não foi possível carregar os jogos em alta (sem conexão e sem cache).".to_string())
231}
232
233pub async fn fetch_upcoming_games(app: &AppHandle, api_key: &str) -> Result<Vec<RawgGame>, String> {
238 let current_date = chrono::Utc::now();
239 let next_year = current_date.year() + 1;
240 let date_start = current_date.format("%Y-%m-%d").to_string();
241 let date_end = format!("{}-12-31", next_year);
242 let cache_key = "rawg_list_upcoming";
243
244 let url = format!(
245 "https://api.rawg.io/api/games?key={}&dates={},{}&ordering=-added&page_size={}",
246 api_key, date_start, date_end, RAWG_UPCOMING_PAGE_SIZE
247 );
248
249 if let Ok(res) = HTTP_CLIENT.get(&url).send().await {
251 if res.status().is_success() {
252 let data: RawgResponse = res.json().await.map_err(|e| e.to_string())?;
253
254 if let Ok(conn) = app.state::<AppState>().cache_db.lock() {
256 if let Ok(json) = serde_json::to_string(&data.results) {
257 let _ = cache::save_cached_api_data(&conn, "rawg", cache_key, &json);
258 }
259 }
260
261 return Ok(data.results);
262 }
263 }
264
265 if let Ok(conn) = app.state::<AppState>().cache_db.lock() {
267 if let Some(payload) = cache::get_stale_api_data(&conn, "rawg", cache_key) {
268 if let Ok(cached_games) = serde_json::from_str::<Vec<RawgGame>>(&payload) {
269 return Ok(cached_games);
270 }
271 }
272 }
273
274 Err("Não foi possível carregar lançamentos (sem conexão e sem cache).".to_string())
275}