mahjong_service/http_server/
user.rs

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(&params.player_id)?;
149
150    // Here it can't use cache because the names might have changed
151    let game_wrapper = GameWrapper::from_storage_no_cache(&storage, &game_id, srv, None).await?;
152
153    game_wrapper.user_load_game(&params.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    // For now only allow getting the information of the current user
395    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    // For now only allow getting the information of the current user
410    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}