use std::collections::HashMap; use chrono::{DateTime, Utc}; use rosu_v2::prelude::User as RosuUser; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Default)] pub struct Event { pub users: Vec, pub end_date: Option>, pub snapshot_times: Vec>, } impl Event { pub fn add_snapshot(&mut self, snapshot: Snapshot) -> Result<(), ()> { let first_user = match self.users.first() { Some(x) => x, None => { println!("Event has no users"); return Err(()); } }; let expected_id = first_user.pp_history.len(); if expected_id != snapshot.id { println!("Expected snapshot ID {}", expected_id); return Err(()); } self.snapshot_times.push(snapshot.time); let pp_map = snapshot.into_map(); for user in &mut self.users { let Some(pp) = pp_map.get(&user.id) else { println!("Snapshot doesn't have user {}", &user.username); return Err(()); }; user.pp_history.push(*pp); } Ok(()) } pub fn get_snapshot(&self, id: usize) -> Result { let Some(time) = self.snapshot_times.get(id) else { println!("Snapshot doesn't exist"); return Err(()); }; let mut pairs = Vec::new(); for user in &self.users { let pp = user .pp_history .get(id) .expect("id already validated to exist"); pairs.push((user.id, *pp)); } Ok(Snapshot { id, time: *time, id_pp_pairs: pairs, }) } } #[derive(Serialize, Deserialize)] pub struct User { pub id: u32, pub username: String, pub avatar_url: String, pub initial_rank: Option, pub initial_pp: f32, pub pp_history: Vec, } // Using a reference here prevents us from cloning the entire object // when converting using .iter().map() later on impl From<&RosuUser> for User { fn from(value: &RosuUser) -> Self { User { id: value.user_id, username: value.username.clone().into_string(), avatar_url: value.avatar_url.clone(), pp_history: vec![], initial_rank: value .statistics_modes .osu .as_ref() .and_then(|x| x.global_rank), initial_pp: value .statistics_modes .osu .as_ref() .and_then(|x| Some(x.pp)) .unwrap_or(0.0), } } } /// A intermediary struct to convert user arrays to /// sorted sets in redis. Just two parallel arrays #[derive(Serialize)] pub struct Snapshot { id: usize, time: DateTime, id_pp_pairs: Vec<(u32, f32)>, } impl Snapshot { pub fn id(&self) -> &usize { &self.id } pub fn create(event_users: &[User], user_updates: &[RosuUser]) -> Result { let first_user = match event_users.first() { Some(x) => x, None => { println!("Event has no users"); return Err(()); } }; let id = first_user.pp_history.len(); let pp_map: HashMap = HashMap::from_iter(user_updates.iter().map(|x| { ( x.user_id, x.statistics_modes .osu .as_ref() .expect("user should have osu! statistics") .pp, ) })); let mut pairs = Vec::new(); for user in event_users { let Some(current_pp) = pp_map.get(&user.id) else { println!( "User {} does not have an entry in fetched data", &user.username ); return Err(()); }; let pp_growth = current_pp - user.initial_pp; pairs.push((user.id, pp_growth)) } Ok(Self { id, time: Utc::now(), id_pp_pairs: pairs, }) } pub fn sorted_set_pairs(&self) -> Vec<(f32, u32)> { self.id_pp_pairs.iter().map(|x| (x.1, x.0)).collect() } pub fn into_map(self) -> HashMap { HashMap::from_iter(self.id_pp_pairs) } }