1use chrono::{DateTime, Utc};
2use serde::Deserialize;
3use url::Url;
4
5use crate::{Embed, Itchio, ItchioError, SmallUser, parsers::{date_from_str, option_date_from_str, empty_object_as_empty_array, small_date_from_str}};
6
7#[derive(Clone, Debug, Deserialize)]
9struct Games {
10 games: Vec<Game>,
11}
12
13#[derive(Clone, Debug, Deserialize)]
15pub struct Game {
16 pub id: u32,
17 pub title: String,
18 pub short_text: Option<String>,
19 pub url: Url,
20 pub cover_url: Option<Url>,
21 pub still_cover_url: Option<Url>,
22 pub r#type: String,
23 pub classification: String,
24 pub p_linux: bool,
25 pub p_android: bool,
26 pub p_windows: bool,
27 pub p_osx: bool,
28 #[serde(deserialize_with = "date_from_str")]
29 pub created_at: DateTime<Utc>,
30 pub min_price: u32,
31 pub can_be_bought: bool,
32 pub published: bool,
33 #[serde(default, deserialize_with = "option_date_from_str")]
36 pub published_at: Option<DateTime<Utc>>,
37 pub has_demo: bool,
38 pub embed: Option<Embed>,
39 pub user: SmallUser,
40 pub views_count: u64,
41 pub purchases_count: u64,
42 pub downloads_count: u64,
43 pub in_press_system: bool,
44 pub earnings: Option<Vec<Earning>>,
45}
46
47#[derive(Clone, Debug, Deserialize)]
49pub struct Earning {
50 pub currency: String,
51 pub amount_formatted: String,
52 pub amount: u64,
53}
54
55#[derive(Clone, Debug, Deserialize)]
57pub struct Graphs {
58 #[serde(deserialize_with = "empty_object_as_empty_array")]
59 pub downloads: Vec<Download>,
60 #[serde(deserialize_with = "empty_object_as_empty_array")]
61 pub purchases: Vec<Purchase>,
62 #[serde(deserialize_with = "empty_object_as_empty_array")]
63 pub views: Vec<View>,
64}
65
66#[derive(Clone, Debug, Deserialize)]
68pub struct Download {}
69
70#[derive(Clone, Debug, Deserialize)]
72pub struct Purchase {}
73
74#[derive(Clone, Debug, Deserialize)]
76pub struct View {
77 #[serde(deserialize_with = "small_date_from_str")]
78 pub date: DateTime<Utc>,
79 pub count: u64,
80}
81
82impl Itchio {
83 pub async fn get_my_games(&self) -> Result<Vec<Game>, ItchioError> {
85 let response = self.request::<Games>("my-games".to_string()).await?;
86 Ok(response.games)
87 }
88
89 pub async fn get_my_games_graphs(&self) -> Result<Graphs, ItchioError> {
91 Ok(self.request::<Graphs>("my-games/graphs".to_string()).await?)
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use std::env;
99 use dotenv::dotenv;
100
101 #[tokio::test]
102 async fn good_games() {
103 dotenv().ok();
104 let client_secret = env::var("KEY").expect("KEY has to be set");
105 let api = Itchio::new(client_secret).unwrap();
106 let games = api.get_my_games().await.inspect_err(|err| eprintln!("Error spotted: {}", err));
107 assert!(games.is_ok())
108 }
109
110 #[tokio::test]
111 async fn bad_key_games() {
112 let api = Itchio::new("bad_key".to_string()).unwrap();
113 let games = api.get_my_games().await;
114 assert!(games.is_err_and(|err| matches!(err, ItchioError::BadKey)))
115 }
116
117 #[tokio::test]
118 async fn good_graphs() {
119 dotenv().ok();
120 let client_secret = env::var("KEY").expect("KEY has to be set");
121 let api = Itchio::new(client_secret).unwrap();
122 let graphs = api.get_my_games_graphs().await.inspect_err(|err| eprintln!("Error spotted: {}", err));
123 assert!(graphs.is_ok())
124 }
125
126 #[tokio::test]
127 async fn bad_key_graphs() {
128 let api = Itchio::new("bad_key".to_string()).unwrap();
129 let graphs = api.get_my_games_graphs().await.inspect_err(|err| eprintln!("Error spotted: {}", err));
130 assert!(graphs.is_err_and(|err| matches!(err, ItchioError::BadKey)))
131 }
132}