mahjong_service/db_storage/
models_translation.rs

1use super::models::{
2    DieselAuthInfo, DieselAuthInfoAnonymous, DieselAuthInfoEmail, DieselAuthInfoGithub, DieselGame,
3    DieselGameBoard, DieselGameDrawWall, DieselGameHand, DieselGamePlayer, DieselGameScore,
4    DieselGameSettings, DieselPlayer,
5};
6use super::schema;
7use crate::auth::{AuthInfo, AuthInfoAnonymous, AuthInfoData, AuthInfoEmail, AuthInfoGithub};
8use diesel::prelude::*;
9use diesel::PgConnection;
10use mahjong_core::deck::DEFAULT_DECK;
11use mahjong_core::hand::KongTile;
12use mahjong_core::{
13    game::GameStyle,
14    round::{Round, RoundTileClaimed},
15};
16use mahjong_core::{
17    Board, BonusTiles, DrawWall, DrawWallPlace, Game, GameId, Hand, HandTile, Hands, PlayerId,
18    Score, ScoreMap, TileId,
19};
20use rustc_hash::FxHashMap;
21use schema::player::dsl as player_dsl;
22use service_contracts::{
23    AuthProvider, GameSettings, ServiceGame, ServicePlayer, ServicePlayerGame,
24};
25use std::str::FromStr;
26use tracing::debug;
27
28pub fn wait_common() {
29    std::thread::sleep(std::time::Duration::from_millis(1));
30}
31
32fn db_request<A, B, C>(mut func: A)
33where
34    A: FnMut() -> Result<B, C>,
35    B: std::fmt::Debug,
36    C: std::fmt::Debug,
37{
38    loop {
39        let result = func();
40
41        if result.is_ok() {
42            break;
43        }
44        debug!("DB request failed: {:?}", result);
45        wait_common();
46    }
47}
48
49impl DieselAuthInfo {
50    pub fn into_raw(self, data: &AuthInfoData) -> AuthInfo {
51        AuthInfo {
52            data: data.clone(),
53            role: serde_json::from_str(&self.role).unwrap(),
54            user_id: self.user_id,
55        }
56    }
57
58    pub fn into_raw_get_data(self, connection: &mut PgConnection) -> AuthInfo {
59        let data = match self.provider.as_str() {
60            val if val == AuthProvider::Email.to_string() => {
61                let data = DieselAuthInfoEmail::get_by_id(connection, &self.user_id)
62                    .expect("User not found");
63
64                AuthInfoData::Email(data.into_raw())
65            }
66            val if val == AuthProvider::Github.to_string() => {
67                let data = DieselAuthInfoGithub::get_by_id(connection, &self.user_id)
68                    .expect("Github not found");
69
70                AuthInfoData::Github(data.into_raw())
71            }
72            val if val == AuthProvider::Anonymous.to_string() => {
73                let data = DieselAuthInfoAnonymous::get_by_id(connection, &self.user_id)
74                    .expect("Anonymous not found");
75
76                AuthInfoData::Anonymous(data.into_raw())
77            }
78            _ => panic!("Unknown provider"),
79        };
80
81        AuthInfo {
82            data,
83            role: serde_json::from_str(&self.role).unwrap(),
84            user_id: self.user_id,
85        }
86    }
87
88    pub fn from_raw(raw: &AuthInfo) -> Self {
89        Self {
90            provider: match raw.data {
91                AuthInfoData::Email(_) => AuthProvider::Email.to_string(),
92                AuthInfoData::Github(_) => AuthProvider::Github.to_string(),
93                AuthInfoData::Anonymous(_) => AuthProvider::Anonymous.to_string(),
94            },
95            role: serde_json::to_string(&raw.role).unwrap(),
96            user_id: raw.user_id.clone(),
97        }
98    }
99
100    pub fn get_info_by_id(
101        connection: &mut PgConnection,
102        id: &String,
103    ) -> Result<Option<AuthInfo>, String> {
104        use schema::auth_info::dsl;
105
106        let auth_info = dsl::auth_info
107            .filter(dsl::user_id.eq(&id))
108            .limit(1)
109            .load::<Self>(connection)
110            .unwrap()
111            .first()
112            .map(|auth_info| auth_info.clone().into_raw_get_data(connection));
113
114        Ok(auth_info)
115    }
116
117    pub fn get_by_id_with_data(
118        connection: &mut PgConnection,
119        id: &String,
120        data: &AuthInfoData,
121    ) -> Option<AuthInfo> {
122        use super::schema::auth_info::dsl;
123
124        let auth_info = loop {
125            if let Ok(db_data) = dsl::auth_info
126                .filter(dsl::user_id.eq(id))
127                .limit(1)
128                .load::<Self>(connection)
129            {
130                break db_data;
131            }
132            wait_common();
133        }
134        .first()
135        .cloned();
136
137        auth_info.map(|auth_info_content| auth_info_content.into_raw(data))
138    }
139}
140
141impl DieselAuthInfoEmail {
142    pub fn into_raw(self) -> AuthInfoEmail {
143        AuthInfoEmail {
144            hashed_pass: self.hashed_pass,
145            id: self.user_id,
146            username: self.username,
147        }
148    }
149
150    pub fn from_raw(raw: &AuthInfoEmail) -> Self {
151        Self {
152            hashed_pass: raw.hashed_pass.clone(),
153            user_id: raw.id.clone(),
154            username: raw.username.clone(),
155        }
156    }
157
158    pub fn get_by_id(connection: &mut PgConnection, id: &String) -> Option<Self> {
159        use super::schema::auth_info_email::dsl;
160
161        loop {
162            if let Ok(data) = dsl::auth_info_email
163                .filter(dsl::user_id.eq(id))
164                .limit(1)
165                .load::<Self>(connection)
166            {
167                break data;
168            }
169            wait_common();
170        }
171        .first()
172        .cloned()
173    }
174
175    pub fn get_info_by_username(
176        connection: &mut PgConnection,
177        username: &String,
178    ) -> Result<Option<AuthInfo>, String> {
179        use schema::auth_info_email::dsl as email_dsl;
180
181        let auth_info_email = email_dsl::auth_info_email
182            .filter(email_dsl::username.eq(&username))
183            .limit(1)
184            .load::<Self>(connection)
185            .unwrap()
186            .first()
187            .map(|auth_info| auth_info.clone().into_raw());
188
189        if auth_info_email.is_none() {
190            return Ok(None);
191        }
192
193        let auth_info_email = auth_info_email.unwrap();
194
195        let auth_info = DieselAuthInfo::get_by_id_with_data(
196            connection,
197            &auth_info_email.id,
198            &AuthInfoData::Email(auth_info_email.clone()),
199        );
200
201        Ok(auth_info)
202    }
203}
204
205impl DieselAuthInfoAnonymous {
206    pub fn into_raw(self) -> AuthInfoAnonymous {
207        AuthInfoAnonymous {
208            hashed_token: self.hashed_token,
209            id: self.user_id,
210        }
211    }
212
213    pub fn from_raw(raw: &AuthInfoAnonymous) -> Self {
214        Self {
215            hashed_token: raw.hashed_token.clone(),
216            user_id: raw.id.clone(),
217        }
218    }
219
220    pub fn get_by_id(connection: &mut PgConnection, id: &String) -> Option<Self> {
221        use super::schema::auth_info_anonymous::dsl;
222
223        loop {
224            if let Ok(data) = dsl::auth_info_anonymous
225                .filter(dsl::user_id.eq(id))
226                .limit(1)
227                .load::<Self>(connection)
228            {
229                break data;
230            }
231            wait_common();
232        }
233        .first()
234        .cloned()
235    }
236
237    pub fn get_info_by_hashed_token(
238        connection: &mut PgConnection,
239        hashed_token: &String,
240    ) -> Result<Option<AuthInfo>, String> {
241        use schema::auth_info_anonymous::dsl as anonymous_dsl;
242
243        let auth_info_email = anonymous_dsl::auth_info_anonymous
244            .filter(anonymous_dsl::hashed_token.eq(&hashed_token))
245            .limit(1)
246            .load::<Self>(connection)
247            .unwrap()
248            .first()
249            .map(|auth_info| auth_info.clone().into_raw());
250
251        if auth_info_email.is_none() {
252            return Ok(None);
253        }
254
255        let auth_info_anonymous = auth_info_email.unwrap();
256
257        let auth_info = DieselAuthInfo::get_by_id_with_data(
258            connection,
259            &auth_info_anonymous.id,
260            &AuthInfoData::Anonymous(auth_info_anonymous.clone()),
261        );
262
263        Ok(auth_info)
264    }
265}
266
267impl DieselAuthInfoGithub {
268    pub fn into_raw(self) -> AuthInfoGithub {
269        AuthInfoGithub {
270            id: self.user_id.clone(),
271            token: self.token.clone(),
272            username: self.username,
273        }
274    }
275
276    pub fn from_raw(raw: &AuthInfoGithub) -> Self {
277        Self {
278            token: raw.token.clone(),
279            user_id: raw.id.clone(),
280            username: raw.username.clone(),
281        }
282    }
283
284    pub fn get_by_id(connection: &mut PgConnection, id: &String) -> Option<Self> {
285        use super::schema::auth_info_github::dsl;
286
287        loop {
288            if let Ok(data) = dsl::auth_info_github
289                .filter(dsl::user_id.eq(id))
290                .limit(1)
291                .load::<Self>(connection)
292            {
293                break data;
294            }
295            wait_common();
296        }
297        .first()
298        .cloned()
299    }
300
301    pub fn get_info_by_username(
302        connection: &mut PgConnection,
303        username: &String,
304    ) -> Result<Option<AuthInfo>, String> {
305        use schema::auth_info_github::dsl as github_dsl;
306
307        let auth_info_github = github_dsl::auth_info_github
308            .filter(github_dsl::username.eq(&username))
309            .limit(1)
310            .load::<Self>(connection)
311            .unwrap()
312            .first()
313            .map(|auth_info| auth_info.clone().into_raw());
314
315        if auth_info_github.is_none() {
316            return Ok(None);
317        }
318
319        let auth_info_github = auth_info_github.unwrap();
320
321        let auth_info = DieselAuthInfo::get_by_id_with_data(
322            connection,
323            &auth_info_github.id,
324            &AuthInfoData::Github(auth_info_github.clone()),
325        );
326
327        Ok(auth_info)
328    }
329}
330
331impl DieselPlayer {
332    pub fn into_raw(self) -> ServicePlayer {
333        ServicePlayer {
334            created_at: self.created_at.and_utc().timestamp_millis().to_string(),
335            id: self.id,
336            is_ai: self.is_ai == 1,
337            name: self.name,
338        }
339    }
340
341    pub fn from_raw(raw: &ServicePlayer) -> Self {
342        Self {
343            created_at: chrono::DateTime::from_timestamp_millis(
344                raw.created_at.parse::<i64>().unwrap(),
345            )
346            .unwrap()
347            .naive_utc(),
348            id: raw.id.clone(),
349            is_ai: if raw.is_ai { 1 } else { 0 },
350            name: raw.name.clone(),
351        }
352    }
353
354    pub fn read_from_ids(
355        connection: &mut PgConnection,
356        ids: &Vec<PlayerId>,
357    ) -> FxHashMap<PlayerId, ServicePlayer> {
358        loop {
359            if let Ok(data) = player_dsl::player
360                .filter(player_dsl::id.eq_any(ids))
361                .load::<Self>(connection)
362            {
363                break data;
364            }
365            wait_common();
366        }
367        .into_iter()
368        .map(|player| (player.id.clone(), player.into_raw()))
369        .collect::<FxHashMap<PlayerId, ServicePlayer>>()
370    }
371
372    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
373        use super::schema::player::table as player_table;
374
375        let players = service_game
376            .players
377            .values()
378            .map(Self::from_raw)
379            .collect::<Vec<Self>>();
380
381        for player in players {
382            loop {
383                let result = diesel::insert_into(player_table)
384                    .values(&player)
385                    .on_conflict(player_dsl::id)
386                    .do_update()
387                    .set(&player)
388                    .execute(connection);
389
390                if result.is_ok() {
391                    break;
392                }
393                debug!("Error saving player: {:?}", result.err());
394                wait_common();
395            }
396        }
397    }
398
399    pub fn save(connection: &mut PgConnection, player: &ServicePlayer) {
400        use super::schema::player::table as player_table;
401
402        let player = Self::from_raw(player);
403
404        loop {
405            if diesel::insert_into(player_table)
406                .values(&player)
407                .on_conflict(player_dsl::id)
408                .do_update()
409                .set(&player)
410                .execute(connection)
411                .is_ok()
412            {
413                break;
414            }
415            wait_common();
416        }
417    }
418
419    pub fn read_from_id_raw(connection: &mut PgConnection, player_id: &PlayerId) -> Option<Self> {
420        loop {
421            if let Ok(player) = player_dsl::player
422                .filter(player_dsl::id.eq(player_id))
423                .limit(1)
424                .load::<Self>(connection)
425            {
426                break player;
427            }
428            wait_common();
429        }
430        .first()
431        .cloned()
432    }
433
434    pub fn read_from_id(
435        connection: &mut PgConnection,
436        player_id: &PlayerId,
437    ) -> Option<ServicePlayer> {
438        loop {
439            if let Ok(player) = player_dsl::player
440                .filter(player_dsl::id.eq(player_id))
441                .limit(1)
442                .load::<Self>(connection)
443            {
444                break player;
445            }
446            wait_common();
447        }
448        .first()
449        .map(|player| player.clone().into_raw())
450    }
451}
452
453pub struct DieselGameExtra {
454    pub game: Game,
455    pub created_at: chrono::NaiveDateTime,
456    pub updated_at: chrono::NaiveDateTime,
457}
458
459impl DieselGame {
460    pub fn into_raw(self) -> DieselGameExtra {
461        let default_game = Game::new(None);
462        let game_style = GameStyle::from_str(&self.style);
463
464        let round = Round {
465            dealer_player_index: self.round_dealer_index as usize,
466            player_index: self.round_player_index as usize,
467            round_index: self.round_index as u32,
468            tile_claimed: self.round_claimed_id.map(|id| RoundTileClaimed {
469                by: self.round_claimed_by,
470                from: self.round_claimed_from.unwrap(),
471                id: id as TileId,
472            }),
473            wall_tile_drawn: self.round_wall_tile_drawn.map(|tile_id| tile_id as TileId),
474            wind: serde_json::from_str(&self.round_wind).unwrap(),
475            style: game_style.clone().unwrap(),
476            consecutive_same_seats: self.round_consecutive_same_seats as usize,
477            east_player_index: self.round_east_player_index as usize,
478            initial_winds: self.round_initial_winds.map(|w| w as u8),
479        };
480        let game = Game {
481            name: self.name,
482            version: self.version,
483            id: self.id,
484            phase: serde_json::from_str(&self.phase).unwrap(),
485            round,
486            style: game_style.unwrap(),
487            // For now the deck is not persisted
488            ..default_game
489        };
490
491        DieselGameExtra {
492            game,
493            created_at: self.created_at,
494            updated_at: self.updated_at,
495        }
496    }
497
498    pub fn read_from_id(
499        connection: &mut PgConnection,
500        game_id: &GameId,
501    ) -> Option<DieselGameExtra> {
502        use schema::game::dsl as game_dsl;
503
504        loop {
505            if let Ok(game) = game_dsl::game
506                .filter(game_dsl::id.eq(game_id))
507                .limit(1)
508                .load::<Self>(connection)
509            {
510                break game;
511            }
512            wait_common();
513        }
514        .first()
515        .map(|game| game.clone().into_raw())
516    }
517
518    pub fn read_player_games(connection: &mut PgConnection) -> Vec<ServicePlayerGame> {
519        use schema::game::dsl as game_dsl;
520
521        loop {
522            if let Ok(game) = game_dsl::game.load::<Self>(connection) {
523                break game;
524            }
525            wait_common();
526        }
527        .into_iter()
528        .map(|game| ServicePlayerGame {
529            created_at: game.created_at.and_utc().timestamp_millis().to_string(),
530            id: game.id,
531            updated_at: game.updated_at.and_utc().timestamp_millis().to_string(),
532        })
533        .collect()
534    }
535
536    pub fn update(&self, connection: &mut PgConnection) {
537        use super::schema::game::table as game_table;
538
539        loop {
540            if diesel::insert_into(game_table)
541                .values(self)
542                .on_conflict(super::schema::game::dsl::id)
543                .do_update()
544                .set(self)
545                .execute(connection)
546                .is_ok()
547            {
548                break;
549            }
550            wait_common();
551        }
552    }
553
554    pub fn from_raw(extra: &DieselGameExtra) -> Self {
555        let raw = &extra.game;
556
557        Self {
558            created_at: extra.created_at,
559            id: raw.id.clone(),
560            name: raw.name.clone(),
561            phase: serde_json::to_string(&raw.phase).unwrap(),
562            round_claimed_by: raw.round.tile_claimed.clone().and_then(|t| t.by),
563            round_claimed_from: raw.round.tile_claimed.clone().map(|t| t.from),
564            round_claimed_id: raw.round.tile_claimed.clone().map(|t| t.id as i32),
565            round_dealer_index: raw.round.dealer_player_index as i32,
566            round_index: raw.round.round_index as i32,
567            round_player_index: raw.round.player_index as i32,
568            round_wall_tile_drawn: raw.round.wall_tile_drawn.map(|t| t as i32),
569            round_wind: serde_json::to_string(&raw.round.wind).unwrap(),
570            updated_at: extra.updated_at,
571            version: raw.version.clone(),
572            style: raw.style.to_string(),
573            round_consecutive_same_seats: raw.round.consecutive_same_seats as i32,
574            round_east_player_index: raw.round.east_player_index as i32,
575            round_initial_winds: raw.round.initial_winds.map(|w| w as i32),
576        }
577    }
578
579    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
580        db_request(|| {
581            diesel::delete(schema::game::table)
582                .filter(schema::game::dsl::id.eq_any(game_ids))
583                .execute(connection)
584        });
585    }
586}
587
588impl DieselGamePlayer {
589    pub fn from_game(game: &Game) -> Vec<Self> {
590        game.players
591            .iter()
592            .enumerate()
593            .map(|(index, player_id)| Self {
594                game_id: game.id.clone(),
595                player_id: player_id.clone(),
596                player_index: index as i32,
597            })
598            .collect::<Vec<Self>>()
599    }
600
601    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> Vec<PlayerId> {
602        use schema::game_player::dsl as game_player_dsl;
603        loop {
604            if let Ok(data) = game_player_dsl::game_player
605                .filter(game_player_dsl::game_id.eq(game_id))
606                .order(game_player_dsl::player_index.asc())
607                .load::<Self>(connection)
608            {
609                break data;
610            }
611            wait_common();
612        }
613        .into_iter()
614        .map(|game| game.player_id)
615        .collect::<Vec<PlayerId>>()
616    }
617
618    pub fn read_from_player(
619        connection: &mut PgConnection,
620        player_id: &PlayerId,
621    ) -> Vec<ServicePlayerGame> {
622        let player = DieselPlayer::read_from_id_raw(connection, player_id);
623
624        if player.is_none() {
625            return vec![];
626        }
627
628        let player = player.unwrap();
629
630        loop {
631            if let Ok(data) = Self::belonging_to(&player)
632                .inner_join(super::schema::game::table)
633                .select(DieselGame::as_select())
634                .order(super::schema::game::dsl::updated_at.desc())
635                .load(connection)
636            {
637                break data;
638            }
639            wait_common();
640        }
641        .into_iter()
642        .map(|game| ServicePlayerGame {
643            created_at: game.created_at.and_utc().timestamp_millis().to_string(),
644            id: game.id,
645            updated_at: game.updated_at.and_utc().timestamp_millis().to_string(),
646        })
647        .collect::<Vec<_>>()
648    }
649
650    pub fn update(connection: &mut PgConnection, diesel_game_players: &Vec<Self>, game: &Game) {
651        use schema::game_player::table as game_player_table;
652
653        db_request(|| {
654            connection.transaction(|t_connection| {
655                diesel::delete(game_player_table)
656                    .filter(schema::game_player::dsl::game_id.eq(&game.id))
657                    .execute(t_connection)?;
658
659                diesel::insert_into(game_player_table)
660                    .values(diesel_game_players)
661                    .execute(t_connection)?;
662
663                diesel::result::QueryResult::Ok(())
664            })
665        });
666    }
667
668    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
669        db_request(|| {
670            diesel::delete(schema::game_player::table)
671                .filter(schema::game_player::dsl::game_id.eq_any(game_ids))
672                .execute(connection)
673        });
674    }
675}
676
677impl DieselGameScore {
678    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
679        use schema::game_score::table as game_score_table;
680
681        db_request(|| {
682            connection.transaction(|t_connection| {
683                loop {
684                    if diesel::delete(game_score_table)
685                        .filter(schema::game_score::dsl::game_id.eq(&service_game.game.id))
686                        .execute(t_connection)
687                        .is_ok()
688                    {
689                        break;
690                    }
691                    wait_common();
692                }
693
694                let scores = service_game
695                    .game
696                    .score
697                    .iter()
698                    .map(|(player_id, score)| Self {
699                        game_id: service_game.game.id.clone(),
700                        player_id: player_id.clone(),
701                        score: *score as i32,
702                    })
703                    .collect::<Vec<Self>>();
704
705                loop {
706                    if diesel::insert_into(game_score_table)
707                        .values(&scores)
708                        .execute(t_connection)
709                        .is_ok()
710                    {
711                        break;
712                    }
713                    wait_common();
714                }
715
716                diesel::result::QueryResult::Ok(())
717            })
718        })
719    }
720
721    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> Score {
722        use schema::game_score::dsl as game_score_dsl;
723
724        let score_map = loop {
725            if let Ok(data) = game_score_dsl::game_score
726                .filter(game_score_dsl::game_id.eq(game_id))
727                .load::<Self>(connection)
728            {
729                break data;
730            }
731            wait_common();
732        }
733        .into_iter()
734        .map(|game_score| {
735            let score = game_score.score as u32;
736
737            (game_score.player_id, score)
738        })
739        .collect::<ScoreMap>();
740
741        Score(score_map)
742    }
743
744    pub fn read_total_from_player(connection: &mut PgConnection, player_id: &PlayerId) -> i32 {
745        use schema::game_score::dsl as game_score_dsl;
746
747        loop {
748            if let Ok(data) = game_score_dsl::game_score
749                .filter(game_score_dsl::player_id.eq(player_id))
750                .load::<Self>(connection)
751            {
752                break data;
753            }
754            wait_common();
755        }
756        .into_iter()
757        .map(|game_score| game_score.score)
758        .sum()
759    }
760
761    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
762        db_request(|| {
763            diesel::delete(schema::game_score::table)
764                .filter(schema::game_score::dsl::game_id.eq_any(game_ids))
765                .execute(connection)
766        });
767    }
768}
769
770impl DieselGameBoard {
771    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
772        use schema::game_board::table as game_board_table;
773
774        loop {
775            if diesel::delete(game_board_table)
776                .filter(schema::game_board::dsl::game_id.eq(&service_game.game.id))
777                .execute(connection)
778                .is_ok()
779            {
780                break;
781            }
782            wait_common();
783        }
784
785        let board = service_game
786            .game
787            .table
788            .board
789            .0
790            .iter()
791            .enumerate()
792            .map(|(tile_index, tile_id)| Self {
793                game_id: service_game.game.id.clone(),
794                tile_id: *tile_id as i32,
795                tile_index: tile_index as i32,
796            })
797            .collect::<Vec<Self>>();
798
799        loop {
800            if diesel::insert_into(game_board_table)
801                .values(&board)
802                .execute(connection)
803                .is_ok()
804            {
805                break;
806            }
807            wait_common();
808        }
809    }
810
811    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> Board {
812        use schema::game_board::dsl as game_board_dsl;
813
814        let board_content = loop {
815            if let Ok(data) = game_board_dsl::game_board
816                .filter(game_board_dsl::game_id.eq(game_id))
817                .order(game_board_dsl::tile_index.asc())
818                .load::<Self>(connection)
819            {
820                break data;
821            }
822            wait_common()
823        }
824        .into_iter()
825        .map(|game_board| game_board.tile_id as TileId)
826        .collect::<Vec<TileId>>();
827
828        Board(board_content)
829    }
830
831    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
832        db_request(|| {
833            diesel::delete(schema::game_board::table)
834                .filter(schema::game_board::dsl::game_id.eq_any(game_ids))
835                .execute(connection)
836        });
837    }
838}
839
840impl DieselGameDrawWall {
841    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
842        use schema::game_draw_wall::table as game_draw_wall_table;
843
844        loop {
845            if diesel::delete(game_draw_wall_table)
846                .filter(schema::game_draw_wall::dsl::game_id.eq(&service_game.game.id))
847                .execute(connection)
848                .is_ok()
849            {
850                break;
851            }
852            wait_common();
853        }
854
855        let mut draw_wall_vec = vec![];
856
857        let draw_wall = service_game
858            .game
859            .table
860            .draw_wall
861            .iter_all(&mut draw_wall_vec)
862            .enumerate()
863            .map(|(tile_index, draw_wall_tile)| Self {
864                game_id: service_game.game.id.clone(),
865                tile_id: draw_wall_tile.0 as i32,
866                tile_index: tile_index as i32,
867                place: draw_wall_tile.1.to_string(),
868            })
869            .collect::<Vec<Self>>();
870
871        loop {
872            if diesel::insert_into(game_draw_wall_table)
873                .values(&draw_wall)
874                .execute(connection)
875                .is_ok()
876            {
877                break;
878            }
879            wait_common();
880        }
881    }
882
883    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> DrawWall {
884        use schema::game_draw_wall::dsl as game_draw_wall_dsl;
885
886        let draw_wall_content = loop {
887            if let Ok(data) = game_draw_wall_dsl::game_draw_wall
888                .filter(game_draw_wall_dsl::game_id.eq(game_id))
889                .order(game_draw_wall_dsl::tile_index.asc())
890                .load::<Self>(connection)
891            {
892                break data;
893            }
894            wait_common();
895        }
896        .into_iter()
897        .map(|game_draw_wall| {
898            (
899                game_draw_wall.tile_id as usize,
900                DrawWallPlace::from_str(&game_draw_wall.place).unwrap_or_else(|_| {
901                    panic!("Unknown draw wall place: {}", game_draw_wall.place)
902                }),
903            )
904        })
905        .collect::<Vec<(TileId, DrawWallPlace)>>();
906
907        DrawWall::new_full(draw_wall_content)
908    }
909
910    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
911        db_request(|| {
912            diesel::delete(schema::game_draw_wall::table)
913                .filter(schema::game_draw_wall::dsl::game_id.eq_any(game_ids))
914                .execute(connection)
915        });
916    }
917}
918
919impl DieselGameHand {
920    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
921        use schema::game_hand::table as game_hand_table;
922
923        loop {
924            if diesel::delete(game_hand_table)
925                .filter(schema::game_hand::dsl::game_id.eq(&service_game.game.id))
926                .execute(connection)
927                .is_ok()
928            {
929                break;
930            }
931            wait_common();
932        }
933
934        let mut hands: Vec<Self> = vec![];
935
936        service_game
937            .game
938            .table
939            .hands
940            .0
941            .iter()
942            .for_each(|(player_id, hand)| {
943                hand.list.iter().enumerate().for_each(|(tile_index, tile)| {
944                    let tile_id = tile.id;
945                    let concealed = if tile.concealed { 1 } else { 0 };
946                    let set_id = tile.set_id.clone();
947
948                    let game_hand = Self {
949                        concealed,
950                        game_id: service_game.game.id.clone(),
951                        is_kong: false,
952                        player_id: player_id.clone(),
953                        set_id,
954                        tile_id: tile_id as i32,
955                        tile_index: tile_index as i32,
956                    };
957
958                    hands.push(game_hand);
959                });
960
961                hand.kong_tiles
962                    .iter()
963                    .enumerate()
964                    .for_each(|(tile_index, tile)| {
965                        let tile_id = tile.id;
966                        let concealed = if tile.concealed { 1 } else { 0 };
967                        let set_id = tile.set_id.clone();
968
969                        let game_hand = Self {
970                            concealed,
971                            game_id: service_game.game.id.clone(),
972                            is_kong: true,
973                            player_id: player_id.clone(),
974                            set_id: Some(set_id),
975                            tile_id: tile_id as i32,
976                            tile_index: tile_index as i32,
977                        };
978
979                        hands.push(game_hand);
980                    });
981            });
982
983        service_game
984            .game
985            .table
986            .bonus_tiles
987            .0
988            .iter()
989            .for_each(|(player_id, player_bonus_tiles)| {
990                player_bonus_tiles
991                    .iter()
992                    .enumerate()
993                    .for_each(|(tile_index, tile_id)| {
994                        let game_hand = Self {
995                            concealed: 0,
996                            game_id: service_game.game.id.clone(),
997                            is_kong: false,
998                            player_id: player_id.clone(),
999                            set_id: None,
1000                            tile_id: *tile_id as i32,
1001                            tile_index: tile_index as i32,
1002                        };
1003
1004                        hands.push(game_hand);
1005                    });
1006            });
1007
1008        loop {
1009            if diesel::insert_into(game_hand_table)
1010                .values(&hands)
1011                .execute(connection)
1012                .is_ok()
1013            {
1014                break;
1015            }
1016            wait_common();
1017        }
1018    }
1019
1020    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> (Hands, BonusTiles) {
1021        use schema::game_hand::dsl as game_hand_dsl;
1022        let mut hands = Hands::default();
1023        let mut bonus_tiles = BonusTiles::default();
1024
1025        loop {
1026            if let Ok(data) = game_hand_dsl::game_hand
1027                .filter(game_hand_dsl::game_id.eq(game_id))
1028                .order(game_hand_dsl::tile_index.asc())
1029                .load::<Self>(connection)
1030            {
1031                break data;
1032            }
1033            wait_common();
1034        }
1035        .into_iter()
1036        .for_each(|game_hand| {
1037            let tile_id = game_hand.tile_id as TileId;
1038            if DEFAULT_DECK.get_sure(tile_id).is_bonus() {
1039                let player_bonus_tiles = bonus_tiles.get_or_create(&game_hand.player_id);
1040                player_bonus_tiles.push(tile_id);
1041            } else {
1042                let player_id = game_hand.player_id;
1043                let concealed = game_hand.concealed == 1;
1044                let set_id = game_hand.set_id;
1045                let mut current_hand = hands
1046                    .0
1047                    .get(&player_id)
1048                    .unwrap_or(&Hand::new(Vec::new()))
1049                    .clone();
1050
1051                if game_hand.is_kong {
1052                    current_hand.kong_tiles.insert(KongTile {
1053                        id: tile_id,
1054                        concealed,
1055                        set_id: set_id.unwrap(),
1056                    });
1057                } else {
1058                    current_hand.push(HandTile {
1059                        id: tile_id,
1060                        concealed,
1061                        set_id,
1062                    });
1063                }
1064
1065                hands.0.insert(player_id, current_hand);
1066            }
1067        });
1068
1069        (hands, bonus_tiles)
1070    }
1071
1072    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
1073        db_request(|| {
1074            diesel::delete(schema::game_hand::table)
1075                .filter(schema::game_hand::dsl::game_id.eq_any(game_ids))
1076                .execute(connection)
1077        });
1078    }
1079}
1080
1081impl DieselGameSettings {
1082    pub fn update_from_game(connection: &mut PgConnection, service_game: &ServiceGame) {
1083        use schema::game_settings::table as game_settings_table;
1084
1085        let auto_sort_players = service_game
1086            .settings
1087            .auto_sort_players
1088            .clone()
1089            .into_iter()
1090            .collect::<Vec<_>>()
1091            .join(&','.to_string());
1092
1093        let auto_stop_claim_meld = service_game
1094            .settings
1095            .auto_stop_claim_meld
1096            .clone()
1097            .into_iter()
1098            .collect::<Vec<_>>()
1099            .join(&','.to_string());
1100
1101        let settings = Self {
1102            last_discard_time: service_game.settings.last_discard_time as i64,
1103            ai_enabled: service_game.settings.ai_enabled,
1104            discard_wait_ms: service_game.settings.discard_wait_ms,
1105            game_id: service_game.game.id.clone(),
1106            fixed_settings: service_game.settings.fixed_settings,
1107            auto_sort_players,
1108            auto_stop_claim_meld,
1109            dead_wall: service_game.settings.dead_wall,
1110        };
1111
1112        loop {
1113            if diesel::insert_into(game_settings_table)
1114                .values(&settings)
1115                .on_conflict(schema::game_settings::dsl::game_id)
1116                .do_update()
1117                .set(&settings)
1118                .execute(connection)
1119                .is_ok()
1120            {
1121                break;
1122            }
1123            wait_common();
1124        }
1125    }
1126
1127    pub fn read_from_game(connection: &mut PgConnection, game_id: &GameId) -> Option<GameSettings> {
1128        use schema::game_settings::dsl as game_settings_dsl;
1129
1130        loop {
1131            if let Ok(data) = game_settings_dsl::game_settings
1132                .filter(game_settings_dsl::game_id.eq(game_id))
1133                .limit(1)
1134                .load::<Self>(connection)
1135            {
1136                break data;
1137            }
1138            wait_common();
1139        }
1140        .first()
1141        .map(|game_settings| GameSettings {
1142            ai_enabled: game_settings.ai_enabled,
1143            discard_wait_ms: game_settings.discard_wait_ms,
1144            fixed_settings: game_settings.fixed_settings,
1145            last_discard_time: game_settings.last_discard_time as i128,
1146            auto_stop_claim_meld: game_settings
1147                .auto_stop_claim_meld
1148                .split(',')
1149                .map(|s| s.to_string())
1150                .collect(),
1151            auto_sort_players: game_settings
1152                .auto_sort_players
1153                .split(',')
1154                .map(|s| s.to_string())
1155                .collect(),
1156            dead_wall: game_settings.dead_wall,
1157        })
1158    }
1159
1160    pub fn delete_games(connection: &mut PgConnection, game_ids: &[GameId]) {
1161        db_request(|| {
1162            diesel::delete(schema::game_settings::table)
1163                .filter(schema::game_settings::dsl::game_id.eq_any(game_ids))
1164                .execute(connection)
1165        });
1166    }
1167}