1#![allow(clippy::await_holding_lock)]
2use crate::auth::{AuthHandler, UnauthorizedError, UserRole};
3use crate::game_wrapper::{CreateGameOpts, GameWrapper};
4use crate::http_server::base::{get_lock, DataSocketServer, DataStorage, GamesManagerData};
5use crate::service_error::{ResponseCommon, ServiceError};
6use crate::user_wrapper::UserWrapper;
7use actix_web::{get, patch, post, web, HttpRequest, HttpResponse};
8use mahjong_core::GameId;
9use service_contracts::{
10 Queries, QueriesResponses, UserLoadGameQuery, UserPatchInfoRequest, UserPostAIContinueRequest,
11 UserPostClaimTileRequest, UserPostPassRoundRequest, UserPostSayMahjongRequest,
12 UserPostSetAuthAnonRequest, UserPostSetAuthRequest, UserPostSetAuthResponse,
13 UserPostSetGameSettingsRequest, UserPostSortHandRequest,
14};
15use tracing::debug;
16
17#[post("/game")]
18async fn user_game_handler(
19 storage: DataStorage,
20 req: HttpRequest,
21 srv: DataSocketServer,
22 manager: GamesManagerData,
23 body: web::Json<Queries>,
24) -> ResponseCommon {
25 let player_id = match &body.0 {
26 Queries::UserCreateGame { player_id, .. } => player_id,
27 Queries::UserDiscardTile { .. } => {
28 &AuthHandler::new(&storage, &req).get_user_from_token()?
29 }
30 Queries::UserCreateMeld { player_id, .. } => player_id,
31 Queries::UserDrawTile { player_id, .. } => player_id,
32 Queries::UserGetDashboard => &AuthHandler::new(&storage, &req).get_user_from_token()?,
33 Queries::UserMovePlayer { player_id, .. } => player_id,
34 Queries::UserBreakMeld { player_id, .. } => player_id,
35 };
36 AuthHandler::new(&storage, &req).verify_user(player_id)?;
37
38 let response = match &body.0 {
39 Queries::UserBreakMeld {
40 game_id, set_id, ..
41 } => {
42 get_lock!(manager, game_id);
43
44 let mut game_wrapper = GameWrapper::from_storage(&storage, game_id, srv, None).await?;
45
46 QueriesResponses::UserBreakMeld {
47 game: game_wrapper
48 .handle_user_break_meld(player_id, set_id)
49 .await?,
50 }
51 }
52 Queries::UserCreateGame {
53 ai_player_names,
54 auto_sort_own,
55 dead_wall,
56 ..
57 } => {
58 debug!("Creating game for user: {:?}", player_id);
59 let create_game_opts = CreateGameOpts {
60 ai_player_names: ai_player_names.as_ref(),
61 auto_sort_own: auto_sort_own.as_ref(),
62 dead_wall: dead_wall.as_ref(),
63 player_id: Some(player_id),
64 };
65 let game_wrapper = GameWrapper::from_new_game(&storage, srv, &create_game_opts).await?;
66
67 debug!("Saving game for user: {:?}", player_id);
68
69 QueriesResponses::UserCreateGame {
70 game: game_wrapper.handle_user_new_game(player_id).await?,
71 }
72 }
73 Queries::UserCreateMeld {
74 game_id,
75 tiles,
76 is_upgrade,
77 is_concealed,
78 ..
79 } => {
80 get_lock!(manager, game_id);
81
82 let mut game_wrapper = GameWrapper::from_storage(&storage, game_id, srv, None).await?;
83
84 QueriesResponses::UserCreateMeld {
85 game: game_wrapper
86 .handle_user_create_meld(player_id, tiles, *is_upgrade, *is_concealed)
87 .await?,
88 }
89 }
90 Queries::UserDiscardTile { game_id, tile_id } => {
91 debug!("Discarding tile");
92 get_lock!(manager, game_id);
93 let mut game_wrapper = GameWrapper::from_storage(&storage, game_id, srv, None).await?;
94 let current_user_id = &game_wrapper.get_current_player_id()?;
95 AuthHandler::new(&storage, &req).verify_user(current_user_id)?;
96
97 QueriesResponses::UserDiscardTile {
98 game: game_wrapper.handle_discard_tile_user(tile_id).await?,
99 }
100 }
101 Queries::UserDrawTile {
102 game_id,
103 game_version,
104 ..
105 } => {
106 get_lock!(manager, game_id);
107
108 let mut game_wrapper =
109 GameWrapper::from_storage(&storage, game_id, srv, Some(game_version)).await?;
110
111 QueriesResponses::UserDrawTile {
112 game: game_wrapper.handle_user_draw_tile(player_id).await?,
113 }
114 }
115 Queries::UserGetDashboard => {
116 let user_wrapper = UserWrapper::from_storage(&storage, player_id).await?;
117 let auth_handler = AuthHandler::new(&storage, &req);
118 let auth_info_summary = auth_handler.get_auth_info_summary().await?;
119
120 QueriesResponses::UserGetDashboard {
121 dashboard: user_wrapper.get_dashboard(&auth_info_summary).await?,
122 }
123 }
124 Queries::UserMovePlayer { game_id, .. } => {
125 get_lock!(manager, game_id);
126
127 let mut game_wrapper = GameWrapper::from_storage(&storage, game_id, srv, None).await?;
128
129 QueriesResponses::UserMovePlayer {
130 game: game_wrapper.handle_user_move_player(player_id).await?,
131 }
132 }
133 };
134
135 Ok(HttpResponse::Ok().json(response))
136}
137
138#[get("/game/{game_id}")]
139async fn user_get_game_load(
140 storage: DataStorage,
141 game_id: web::Path<String>,
142 req: HttpRequest,
143 srv: DataSocketServer,
144) -> ResponseCommon {
145 let params = web::Query::<UserLoadGameQuery>::from_query(req.query_string())
146 .map_err(|_| ServiceError::Custom("Invalid player id"))?;
147
148 AuthHandler::new(&storage, &req).verify_user(¶ms.player_id)?;
149
150 let game_wrapper = GameWrapper::from_storage_no_cache(&storage, &game_id, srv, None).await?;
152
153 game_wrapper.user_load_game(¶ms.player_id)
154}
155
156#[post("/game/{game_id}/ai-continue")]
157async fn user_post_game_ai_continue(
158 storage: DataStorage,
159 game_id: web::Path<String>,
160 body: web::Json<UserPostAIContinueRequest>,
161 manager: GamesManagerData,
162 req: HttpRequest,
163 srv: DataSocketServer,
164) -> ResponseCommon {
165 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
166
167 get_lock!(manager, game_id);
168
169 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
170
171 game_wrapper.handle_user_ai_continue(&body).await
172}
173
174#[post("/game/{game_id}/join")]
175async fn user_post_game_join(
176 storage: DataStorage,
177 game_id: web::Path<GameId>,
178 srv: DataSocketServer,
179 req: HttpRequest,
180 manager: GamesManagerData,
181) -> ResponseCommon {
182 let player_id = AuthHandler::new(&storage, &req).get_user_from_token()?;
183
184 get_lock!(manager, game_id);
185
186 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
187
188 game_wrapper.handle_user_join_game(&player_id).await
189}
190
191#[post("/game/{game_id}/sort-hand")]
192async fn user_post_game_sort_hand(
193 storage: DataStorage,
194 game_id: web::Path<GameId>,
195 body: web::Json<UserPostSortHandRequest>,
196 srv: DataSocketServer,
197 req: HttpRequest,
198) -> ResponseCommon {
199 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
200
201 debug!("Sorting hand for user: {:?}", &body.player_id);
202
203 let mut game_wrapper =
204 GameWrapper::from_storage(&storage, &game_id, srv, Some(&body.game_version)).await?;
205
206 game_wrapper
207 .handle_user_sort_hand(&body.player_id, &body.tiles)
208 .await
209}
210
211#[post("/game/{game_id}/claim-tile")]
212async fn user_post_game_claim_tile(
213 storage: DataStorage,
214 body: web::Json<UserPostClaimTileRequest>,
215 game_id: web::Path<String>,
216 srv: DataSocketServer,
217 manager: GamesManagerData,
218 req: HttpRequest,
219) -> ResponseCommon {
220 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
221
222 get_lock!(manager, game_id);
223
224 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
225
226 game_wrapper.handle_user_claim_tile(&body.player_id).await
227}
228
229#[post("/game/{game_id}/say-mahjong")]
230async fn user_post_game_say_mahjong(
231 storage: DataStorage,
232 body: web::Json<UserPostSayMahjongRequest>,
233 game_id: web::Path<String>,
234 manager: GamesManagerData,
235 srv: DataSocketServer,
236 req: HttpRequest,
237) -> ResponseCommon {
238 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
239
240 get_lock!(manager, game_id);
241
242 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
243
244 game_wrapper.handle_user_say_mahjong(&body.player_id).await
245}
246
247#[post("/game/{game_id}/pass-round")]
248async fn user_post_game_pass_round(
249 storage: DataStorage,
250 body: web::Json<UserPostPassRoundRequest>,
251 game_id: web::Path<GameId>,
252 manager: GamesManagerData,
253 srv: DataSocketServer,
254 req: HttpRequest,
255) -> ResponseCommon {
256 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
257
258 get_lock!(manager, game_id);
259
260 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
261
262 game_wrapper.handle_user_pass_round(&body.player_id).await
263}
264
265#[post("/game/{game_id}/settings")]
266async fn user_post_game_settings(
267 storage: DataStorage,
268 body: web::Json<UserPostSetGameSettingsRequest>,
269 game_id: web::Path<GameId>,
270 manager: GamesManagerData,
271 srv: DataSocketServer,
272 req: HttpRequest,
273) -> ResponseCommon {
274 AuthHandler::new(&storage, &req).verify_user(&body.player_id)?;
275
276 get_lock!(manager, game_id);
277
278 let mut game_wrapper = GameWrapper::from_storage(&storage, &game_id, srv, None).await?;
279
280 game_wrapper
281 .handle_user_set_game_settings(&body.player_id, &body.settings)
282 .await
283}
284
285#[post("/")]
286async fn user_post_auth(
287 storage: DataStorage,
288 body: web::Json<UserPostSetAuthRequest>,
289 req: HttpRequest,
290) -> ResponseCommon {
291 let username = body.username.clone();
292 let username = username.to_lowercase();
293 let mut auth_handler = AuthHandler::new(&storage, &req);
294
295 let user = auth_handler
296 .validate_email_user(&username, &body.password)
297 .await
298 .map_err(|_| UnauthorizedError)?;
299
300 if user.is_none() {
301 debug!("Creating new username: {username}");
302 auth_handler
303 .create_email_user(
304 &username,
305 &body.password,
306 if username == "admin" {
307 UserRole::Admin
308 } else {
309 UserRole::Player
310 },
311 )
312 .await
313 .map_err(|_| ServiceError::Custom("Error creating user"))?;
314
315 let data = auth_handler
316 .generate_token()
317 .map_err(|_| ServiceError::Custom("Error generating token"))?;
318
319 return Ok(HttpResponse::Ok().json(data));
320 }
321
322 debug!("Handling existing user: {username}");
323
324 let is_valid = user.unwrap();
325
326 if is_valid {
327 let data = auth_handler.generate_token();
328
329 if data.is_err() {
330 let err = data.err().unwrap();
331 debug!("Error generating token: {err}");
332 return Ok(HttpResponse::InternalServerError().json("Error generating json"));
333 }
334
335 Ok(HttpResponse::Ok().json(data.unwrap()))
336 } else {
337 debug!("Invalid password for username: {username}");
338 Ok(HttpResponse::Unauthorized().json("E_INVALID_USER_PASS"))
339 }
340}
341
342#[post("/anonymous")]
343async fn user_post_auth_anonymous(
344 storage: DataStorage,
345 body: web::Json<UserPostSetAuthAnonRequest>,
346 req: HttpRequest,
347) -> ResponseCommon {
348 let id_token = body.id_token.clone();
349 let mut auth_handler = AuthHandler::new(&storage, &req);
350
351 let user = auth_handler.validate_anon_user(&id_token).await?;
352
353 if user.is_none() {
354 debug!("Creating new anonymous user");
355
356 auth_handler
357 .create_anonymous_user(&id_token, UserRole::Player)
358 .await
359 .map_err(|_| ServiceError::Custom("Error creating user"))?;
360
361 let data: UserPostSetAuthResponse = auth_handler
362 .generate_token()
363 .map_err(|_| ServiceError::Custom("Error generating json"))?;
364
365 return Ok(HttpResponse::Ok().json(data));
366 }
367
368 debug!("Handling existing anonymous user: {id_token}");
369
370 let is_valid = user.unwrap();
371
372 if is_valid {
373 let data = auth_handler.generate_token();
374
375 if data.is_err() {
376 let err = data.err().unwrap();
377 debug!("Error generating token: {err}");
378 return Ok(HttpResponse::InternalServerError().json("Error generating json"));
379 }
380
381 Ok(HttpResponse::Ok().json(data.unwrap()))
382 } else {
383 debug!("Invalid anonymous token");
384 Ok(HttpResponse::Unauthorized().json("E_INVALID_USER_PASS"))
385 }
386}
387
388#[get("/info/{user_id}")]
389async fn user_get_info(
390 storage: DataStorage,
391 req: HttpRequest,
392 user_id: web::Path<String>,
393) -> ResponseCommon {
394 AuthHandler::new(&storage, &req).verify_user(&user_id)?;
396
397 let user_wrapper = UserWrapper::from_storage(&storage, &user_id).await?;
398
399 user_wrapper.get_info().await
400}
401
402#[patch("/info/{player_id}")]
403async fn user_patch_info(
404 storage: DataStorage,
405 body: web::Json<UserPatchInfoRequest>,
406 user_id: web::Path<String>,
407 req: HttpRequest,
408) -> ResponseCommon {
409 AuthHandler::new(&storage, &req).verify_user(&user_id)?;
411
412 let mut user_wrapper = UserWrapper::from_storage(&storage, &user_id).await?;
413
414 user_wrapper.update_info(&body).await
415}
416
417pub fn get_user_scope() -> actix_web::Scope {
418 web::scope("/api/v1/user")
419 .service(user_get_game_load)
420 .service(user_game_handler)
421 .service(user_get_info)
422 .service(user_patch_info)
423 .service(user_post_auth)
424 .service(user_post_auth_anonymous)
425 .service(user_post_game_ai_continue)
426 .service(user_post_game_claim_tile)
427 .service(user_post_game_join)
428 .service(user_post_game_pass_round)
429 .service(user_post_game_say_mahjong)
430 .service(user_post_game_settings)
431 .service(user_post_game_sort_hand)
432}