game_manager_lib/services/
subscriptions.rs1use crate::constants::{
4 AMAZON_LUNA_CACHE_KEY, AMAZON_LUNA_CACHE_SOURCE, EA_PLAY_CACHE_KEY, EA_PLAY_CACHE_SOURCE,
5 GAME_PASS_CACHE_SOURCE, GAME_PASS_FULL_CACHE_KEY, UBISOFT_PLUS_CACHE_KEY,
6 UBISOFT_PLUS_CACHE_SOURCE,
7};
8use crate::database::AppState;
9use crate::scrapers::amazon_luna::{fetch_amazon_luna_catalog, LunaGame};
10use crate::scrapers::game_pass::{fetch_game_pass_pc_catalog, GamePassGame};
11use crate::scrapers::ubisoft_plus::{fetch_ubisoft_plus_catalog, UbisoftGame};
12use crate::scrapers::{fetch_ea_play_catalog, EAPlayGame};
13use crate::services::cache;
14use rusqlite::params;
15use tauri::State;
16
17fn normalize_lang_tag(lang: &str) -> String {
19 let lower = lang.to_lowercase();
20 if lower.starts_with("pt") {
21 "pt-BR".to_string()
22 } else {
23 "en-US".to_string() }
25}
26
27fn amazon_luna_lang_tag(lang: &str) -> String {
29 let normalized = normalize_lang_tag(lang);
30 if normalized.starts_with("pt") {
31 "pt-PT".to_string() } else {
33 "en-US".to_string()
34 }
35}
36
37fn catalog_cache_key(base_key: &str, lang: &str) -> String {
39 let normalized = normalize_lang_tag(lang);
40 format!("{}_{}", base_key, normalized)
41}
42
43pub async fn get_amazon_luna_games(
45 state: &State<'_, AppState>,
46 lang: &str,
47) -> Result<Vec<LunaGame>, String> {
48 let _normalized_lang = normalize_lang_tag(lang);
49 let cache_key = catalog_cache_key(AMAZON_LUNA_CACHE_KEY, lang);
50 let luna_lang = amazon_luna_lang_tag(lang);
51
52 let cached = {
53 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
54 cache::get_cached_api_data(&conn, AMAZON_LUNA_CACHE_SOURCE, &cache_key)
55 };
56
57 if let Some(cached) = cached {
58 if let Ok(games) = serde_json::from_str::<Vec<LunaGame>>(&cached) {
59 return Ok(games);
60 }
61 }
62
63 let games = fetch_amazon_luna_catalog(&luna_lang).await?;
64
65 {
66 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
67 let payload = serde_json::to_string(&games).map_err(|e| e.to_string())?;
68 cache::save_cached_api_data(&conn, AMAZON_LUNA_CACHE_SOURCE, &cache_key, &payload)?;
69 }
70
71 Ok(games)
72}
73
74pub async fn get_game_pass_games(
77 state: &State<'_, AppState>,
78 exclude_ea_play: bool,
79 lang: &str,
80) -> Result<Vec<GamePassGame>, String> {
81 let normalized_lang = normalize_lang_tag(lang);
82 let cache_key = catalog_cache_key(GAME_PASS_FULL_CACHE_KEY, lang);
83
84 let cached = {
86 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
87 cache::get_cached_api_data(&conn, GAME_PASS_CACHE_SOURCE, &cache_key)
88 };
89
90 let all_games: Vec<GamePassGame> = if let Some(data) = cached {
91 serde_json::from_str(&data).unwrap_or_default()
92 } else {
93 let games = fetch_game_pass_pc_catalog(false, &normalized_lang).await?;
95 let payload = serde_json::to_string(&games).map_err(|e| e.to_string())?;
96 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
97 cache::save_cached_api_data(&conn, GAME_PASS_CACHE_SOURCE, &cache_key, &payload)?;
98 games
99 };
100
101 if exclude_ea_play {
103 Ok(all_games.into_iter().filter(|g| !g.is_ea_play).collect())
104 } else {
105 Ok(all_games)
106 }
107}
108
109pub async fn get_ea_play_games(
111 state: &State<'_, AppState>,
112 lang: &str,
113) -> Result<Vec<EAPlayGame>, String> {
114 let normalized_lang = normalize_lang_tag(lang);
115 let cache_key = catalog_cache_key(EA_PLAY_CACHE_KEY, lang);
116
117 let cached = {
118 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
119 cache::get_cached_api_data(&conn, EA_PLAY_CACHE_SOURCE, &cache_key)
120 };
121
122 let all_games: Vec<EAPlayGame> = if let Some(data) = cached {
123 serde_json::from_str(&data).unwrap_or_default()
124 } else {
125 let games = fetch_ea_play_catalog(&normalized_lang).await?;
126 let payload = serde_json::to_string(&games).map_err(|e| e.to_string())?;
127 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
128 cache::save_cached_api_data(&conn, EA_PLAY_CACHE_SOURCE, &cache_key, &payload)?;
129 games
130 };
131
132 Ok(all_games)
133}
134
135pub async fn get_ubisoft_plus_games(
137 state: &State<'_, AppState>,
138) -> Result<Vec<UbisoftGame>, String> {
139 let cached = {
140 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
141 cache::get_cached_api_data(&conn, UBISOFT_PLUS_CACHE_SOURCE, UBISOFT_PLUS_CACHE_KEY)
142 };
143
144 if let Some(cached) = cached {
145 if let Ok(games) = serde_json::from_str::<Vec<UbisoftGame>>(&cached) {
146 return Ok(games);
147 }
148 }
149
150 let games = fetch_ubisoft_plus_catalog().await?;
151
152 {
153 let conn = state.cache_db.lock().map_err(|e| e.to_string())?;
154 let payload = serde_json::to_string(&games).map_err(|e| e.to_string())?;
155 cache::save_cached_api_data(
156 &conn,
157 UBISOFT_PLUS_CACHE_SOURCE,
158 UBISOFT_PLUS_CACHE_KEY,
159 &payload,
160 )?;
161 }
162
163 Ok(games)
164}
165
166pub fn get_enabled_services(state: &State<'_, AppState>) -> Result<Vec<String>, String> {
168 let conn = state.games_db.lock().map_err(|e| e.to_string())?;
169 let mut stmt = conn
170 .prepare("SELECT service FROM subscriptions WHERE enabled = 1")
171 .map_err(|e| e.to_string())?;
172
173 let services = stmt
174 .query_map([], |row| row.get::<_, String>(0))
175 .map_err(|e| e.to_string())?
176 .filter_map(|r| r.ok())
177 .collect();
178
179 Ok(services)
180}
181
182pub fn set_enabled_services(
184 state: &State<'_, AppState>,
185 services: Vec<String>,
186) -> Result<(), String> {
187 let conn = state.games_db.lock().map_err(|e| e.to_string())?;
188
189 conn.execute("UPDATE subscriptions SET enabled = 0", [])
191 .map_err(|e| e.to_string())?;
192
193 for service in services {
195 conn.execute(
196 "INSERT INTO subscriptions (service, enabled)
197 VALUES (?1, 1)
198 ON CONFLICT(service) DO UPDATE SET enabled = 1",
199 params![service],
200 )
201 .map_err(|e| e.to_string())?;
202 }
203
204 Ok(())
205}