Two teams try and fill in any horizontal, vertical, or diagonal line on a bingo board by playing maps on osu! osu.bingo
osu
at microservice 291 lines 9.5 kB view raw
1use std::collections::HashMap; 2 3use tokio::sync::{RwLock, RwLockWriteGuard}; 4 5use serde::{Serialize, de::DeserializeOwned}; 6use serde_json::Value; 7use sqlx::PgPool; 8 9use super::schema::*; 10 11pub struct Loader { 12 pool: PgPool, 13 cache: HashMap<String, Value>, 14} 15 16impl<'a> Loader { 17 fn new(pool: PgPool) -> Self { 18 Loader { 19 pool, 20 cache: HashMap::new(), 21 } 22 } 23 24 pub async fn get_public_games(&mut self) -> Result<Vec<BingoGame>, String> { 25 get_cached_value(String::from("all_games"), &mut self.cache, async || { 26 sqlx::query_as("SELECT * FROM bingo_game WHERE public = true;") 27 .fetch_all(&self.pool) 28 .await 29 .map_err(|e| e.to_string()) 30 }) 31 .await 32 } 33 34 pub async fn get_game_squares(&mut self, id: &str) -> Result<Vec<BingoSquare>, String> { 35 get_cached_value(format!("game_squares_{id}"), &mut self.cache, async || { 36 sqlx::query_as("SELECT * FROM bingo_square WHERE game_id = $1;") 37 .bind(id) 38 .fetch_all(&self.pool) 39 .await 40 .map_err(|e| e.to_string()) 41 }) 42 .await 43 } 44 45 pub async fn get_map_squares(&mut self, id: i32) -> Result<Vec<BingoSquare>, String> { 46 get_cached_value(format!("map_squares_{id}"), &mut self.cache, async || { 47 sqlx::query_as("SELECT * FROM bingo_square WHERE map_id = $1;") 48 .bind(id) 49 .fetch_all(&self.pool) 50 .await 51 .map_err(|e| e.to_string()) 52 }) 53 .await 54 } 55 56 // TODO: Authorization 57 pub async fn get_game_by_id(&mut self, id: &str) -> Result<BingoGame, String> { 58 get_cached_value(format!("game_{}", id), &mut self.cache, async || { 59 sqlx::query_as("SELECT * FROM bingo_game WHERE id = $1;") 60 .bind(&id) 61 .fetch_one(&self.pool) 62 .await 63 .map_err(|e| e.to_string()) 64 }) 65 .await 66 } 67 68 pub async fn get_game_users(&mut self, id: &str) -> Result<Vec<GameUser>, String> { 69 get_cached_value( 70 format!("game_gameusers_{id}"), 71 &mut self.cache, 72 async || { 73 sqlx::query_as("SELECT * FROM game_user WHERE game_id = $1;") 74 .bind(id) 75 .fetch_all(&self.pool) 76 .await 77 .map_err(|e| e.to_string()) 78 }, 79 ) 80 .await 81 } 82 83 pub async fn get_template_from_id(&mut self, id: &str) -> Result<Template, String> { 84 get_cached_value(format!("{id}"), &mut self.cache, async || { 85 sqlx::query_as("SELECT * FROM template WHERE id = $1;") 86 .bind(&id) 87 .fetch_one(&self.pool) 88 .await 89 .map_err(|e| e.to_string()) 90 }) 91 .await 92 } 93 94 pub async fn get_square_scores(&mut self, id: &str) -> Result<Vec<Score>, String> { 95 get_cached_value(format!("square_scores_{id}"), &mut self.cache, async || { 96 sqlx::query_as("SELECT * FROM score WHERE square_id = $1;") 97 .bind(id) 98 .fetch_all(&self.pool) 99 .await 100 .map_err(|e| e.to_string()) 101 }) 102 .await 103 } 104 105 pub async fn get_gameuser_by_id(&mut self, id: &str) -> Result<GameUser, String> { 106 get_cached_value(format!("{id}"), &mut self.cache, async || { 107 sqlx::query_as("SELECT * FROM gameuser where id = $1;") 108 .bind(id) 109 .fetch_one(&self.pool) 110 .await 111 .map_err(|e| e.to_string()) 112 }) 113 .await 114 } 115 116 pub async fn get_user_by_id(&mut self, id: i32) -> Result<User, String> { 117 get_cached_value(format!("user_{id}"), &mut self.cache, async || { 118 sqlx::query_as("SELECT * FROM public.user where id = $1;") 119 .bind(id) 120 .fetch_one(&self.pool) 121 .await 122 .map_err(|e| e.to_string()) 123 }) 124 .await 125 } 126 127 pub async fn get_user_gameusers(&mut self, id: i32) -> Result<Vec<GameUser>, String> { 128 get_cached_value( 129 format!("user_gameusers_{id}"), 130 &mut self.cache, 131 async || { 132 sqlx::query_as("SELECT * FROM gameuser where user_id = $1;") 133 .bind(id) 134 .fetch_all(&self.pool) 135 .await 136 .map_err(|e| e.to_string()) 137 }, 138 ) 139 .await 140 } 141 142 pub async fn get_user_chats(&mut self, id: i32) -> Result<Vec<Chat>, String> { 143 get_cached_value(format!("user_chats_{id}"), &mut self.cache, async || { 144 sqlx::query_as("SELECT * FROM chat where user_id = $1;") 145 .bind(id) 146 .fetch_all(&self.pool) 147 .await 148 .map_err(|e| e.to_string()) 149 }) 150 .await 151 } 152 153 pub async fn get_user_templates(&mut self, id: i32) -> Result<Vec<Template>, String> { 154 get_cached_value( 155 format!("user_templates_{id}"), 156 &mut self.cache, 157 async || { 158 sqlx::query_as("SELECT * FROM template where owner_id = $1;") 159 .bind(id) 160 .fetch_all(&self.pool) 161 .await 162 .map_err(|e| e.to_string()) 163 }, 164 ) 165 .await 166 } 167 168 pub async fn get_map_by_id(&mut self, id: i32) -> Result<Map, String> { 169 get_cached_value(format!("map_{id}"), &mut self.cache, async || { 170 sqlx::query_as("SELECT * FROM map where id = $1;") 171 .bind(id) 172 .fetch_one(&self.pool) 173 .await 174 .map_err(|e| e.to_string()) 175 }) 176 .await 177 } 178 179 pub async fn get_map_stats(&mut self, id: i32) -> Result<Vec<MapStats>, String> { 180 get_cached_value(format!("stats_{id}"), &mut self.cache, async || { 181 sqlx::query_as("SELECT * FROM map_stats where map_id = $1;") 182 .bind(id) 183 .fetch_all(&self.pool) 184 .await 185 .map_err(|e| e.to_string()) 186 }) 187 .await 188 } 189 190 pub async fn get_mappool_by_id(&mut self, id: &str) -> Result<Mappool, String> { 191 get_cached_value(format!("{id}"), &mut self.cache, async || { 192 sqlx::query_as("SELECT * FROM mappool where id = $1;") 193 .bind(id) 194 .fetch_one(&self.pool) 195 .await 196 .map_err(|e| e.to_string()) 197 }) 198 .await 199 } 200 201 pub async fn get_square_by_id(&mut self, id: &str) -> Result<BingoSquare, String> { 202 get_cached_value(format!("{id}"), &mut self.cache, async || { 203 sqlx::query_as("SELECT * FROM bingo_square where id = $1;") 204 .bind(id) 205 .fetch_one(&self.pool) 206 .await 207 .map_err(|e| e.to_string()) 208 }) 209 .await 210 } 211 212 pub async fn get_maps_in_mappool(&mut self, id: &str) -> Result<Vec<MapInPool>, String> { 213 get_cached_value(format!("{id}"), &mut self.cache, async || { 214 sqlx::query_as("SELECT * FROM map_in_pool where pool_id = $1;") 215 .bind(id) 216 .fetch_all(&self.pool) 217 .await 218 .map_err(|e| e.to_string()) 219 }) 220 .await 221 } 222 223 pub async fn get_mapinpool_by_map(&mut self, id: i32) -> Result<Vec<MapInPool>, String> { 224 get_cached_value(format!("stats_{id}"), &mut self.cache, async || { 225 sqlx::query_as("SELECT * FROM map_in_pool where map_id = $1;") 226 .bind(id) 227 .fetch_all(&self.pool) 228 .await 229 .map_err(|e| e.to_string()) 230 }) 231 .await 232 } 233 234 pub async fn get_mapinpool_by_pool(&mut self, id: &str) -> Result<Vec<MapInPool>, String> { 235 get_cached_value(format!("stats_{id}"), &mut self.cache, async || { 236 sqlx::query_as("SELECT * FROM map_in_pool where pool_id = $1;") 237 .bind(id) 238 .fetch_all(&self.pool) 239 .await 240 .map_err(|e| e.to_string()) 241 }) 242 .await 243 } 244} 245 246async fn get_cached_value<T, F>( 247 query_key: String, 248 cache: &mut HashMap<String, Value>, 249 fetcher: F, 250) -> Result<T, String> 251where 252 T: Serialize + DeserializeOwned, 253 F: AsyncFnOnce() -> Result<T, String>, 254{ 255 if !cache.contains_key(&query_key) { 256 let value: T = fetcher().await?; 257 258 cache.insert( 259 query_key.clone(), 260 serde_json::to_value(value).map_err(|x| x.to_string())?, 261 ); 262 } 263 264 let query = cache.get(&query_key); 265 if let Some(content) = query { 266 return serde_json::from_value(content.clone()).map_err(|x| x.to_string()); 267 } 268 269 return Err(String::from("Insert into hashmap must have failed")); 270} 271 272pub async fn pool() -> PgPool { 273 let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| { 274 eprintln!("Environment variable DATABASE_URL should be set"); 275 std::process::exit(1); 276 }); 277 278 PgPool::connect(&db_url).await.unwrap() 279} 280 281pub struct DataContext(RwLock<Loader>); 282impl DataContext { 283 pub fn new(pool: PgPool) -> Self { 284 DataContext(RwLock::new(Loader::new(pool))) 285 } 286 pub async fn acquire(&self) -> RwLockWriteGuard<'_, Loader> { 287 self.0.write().await 288 } 289} 290 291impl juniper::Context for DataContext {}