game_manager_lib\services/
itad.rs1use crate::constants::ITAD_API_URL;
7use crate::security;
8use crate::utils::http_client::HTTP_CLIENT;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Serialize, Deserialize)]
14pub struct ItadLookupResult {
15 pub found: bool,
16 pub id: Option<String>,
17 pub slug: Option<String>,
18 pub title: Option<String>,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22pub struct ItadShop {
23 pub id: u64,
24 pub name: String,
25}
26
27#[derive(Debug, Serialize, Deserialize)]
28pub struct ItadPrice {
29 pub price: f64,
30 pub currency: String,
31 pub cut: Option<i32>,
32 pub shop: ItadShop,
33 pub url: Option<String>,
34 pub voucher: Option<String>,
35}
36
37#[derive(Debug, Serialize, Deserialize)]
38pub struct ItadGameOverview {
39 pub id: String,
40 pub title: Option<String>,
41 pub current: Option<ItadPrice>,
42 pub lowest: Option<ItadPrice>,
43}
44
45#[derive(Debug, Deserialize)]
48struct RawItadResponse {
49 prices: Vec<RawItadGame>,
50}
51
52#[derive(Debug, Deserialize)]
53struct RawItadGame {
54 id: String,
55 current: Option<RawItadDeal>,
56 lowest: Option<RawItadDeal>,
57}
58
59#[derive(Debug, Deserialize)]
60struct RawItadDeal {
61 shop: ItadShop,
62 price: RawPriceValue,
63 cut: Option<i32>,
64 url: Option<String>,
65 voucher: Option<String>,
66}
67
68#[derive(Debug, Deserialize)]
69struct RawPriceValue {
70 amount: f64,
71 currency: String,
72}
73
74pub async fn find_game_id(title: &str) -> Result<String, String> {
78 let key = security::get_itad_api_key();
79 if key.is_empty() {
80 return Err("API Key da ITAD não configurada".into());
81 }
82
83 let url = format!(
84 "{}/games/lookup/v1?key={}&title={}",
85 ITAD_API_URL,
86 key,
87 urlencoding::encode(title)
88 );
89
90 let res = HTTP_CLIENT
91 .get(&url)
92 .send()
93 .await
94 .map_err(|e| e.to_string())?;
95
96 if !res.status().is_success() {
97 return Err(format!("Erro ITAD Lookup: {}", res.status()));
98 }
99
100 let response_text = res.text().await.map_err(|e| e.to_string())?;
101
102 #[derive(Deserialize)]
103 struct LookupResponse {
104 game: Option<GameInfo>,
105 }
106 #[derive(Deserialize)]
107 struct GameInfo {
108 id: String,
109 }
110
111 let response: LookupResponse = serde_json::from_str(&response_text).map_err(|e| {
112 tracing::error!("Erro JSON Lookup: {}", e);
113 format!("Erro de JSON: {}", e)
114 })?;
115
116 match response.game {
117 Some(game_info) => Ok(game_info.id),
118 None => Err("Jogo não encontrado na ITAD".into()),
119 }
120}
121
122pub async fn get_prices(itad_ids: Vec<String>) -> Result<Vec<ItadGameOverview>, String> {
124 let key = security::get_itad_api_key();
125 if key.is_empty() {
126 return Err("API Key ausente".into());
127 }
128 if itad_ids.is_empty() {
129 return Ok(vec![]);
130 }
131
132 let url = format!("{}/games/overview/v2?key={}&country=BR", ITAD_API_URL, key);
133
134 tracing::debug!("ITAD Prices Request: {} items", itad_ids.len());
135
136 let res = HTTP_CLIENT
137 .post(&url)
138 .json(&itad_ids)
139 .send()
140 .await
141 .map_err(|e| e.to_string())?;
142
143 let status = res.status();
144
145 if !status.is_success() {
146 let body = res.text().await.unwrap_or_default();
147 tracing::error!("Erro ITAD Prices: {}", body);
148 return Err(format!("Erro ITAD Prices: {}", status));
149 }
150
151 let response_text = res.text().await.map_err(|e| e.to_string())?;
152
153 let raw_response: RawItadResponse = serde_json::from_str(&response_text).map_err(|e| {
155 tracing::error!("Erro ao parsear JSON da ITAD: {}", e);
156 format!(
158 "Erro de JSON (linha: {}, col: {}): {}",
159 e.line(),
160 e.column(),
161 e
162 )
163 })?;
164
165 let clean_overview: Vec<ItadGameOverview> = raw_response
167 .prices
168 .into_iter()
169 .map(|raw| {
170 ItadGameOverview {
171 id: raw.id,
172 title: None, current: raw.current.map(convert_deal),
174 lowest: raw.lowest.map(convert_deal),
175 }
176 })
177 .collect();
178
179 Ok(clean_overview)
180}
181
182fn convert_deal(raw: RawItadDeal) -> ItadPrice {
184 ItadPrice {
185 price: raw.price.amount,
186 currency: raw.price.currency,
187 cut: raw.cut,
188 shop: raw.shop,
189 url: raw.url,
190 voucher: raw.voucher,
191 }
192}