nil_server_database/database/
game.rs1use super::BlockingDatabase;
5use crate::error::{Error, Result};
6use crate::model::game::{Game, GameWithBlob, NewGame};
7use crate::sql_types::duration::Duration;
8use crate::sql_types::game_id::GameId;
9use crate::sql_types::hashed_password::HashedPassword;
10use crate::sql_types::id::UserId;
11use crate::sql_types::player_id::PlayerId;
12use crate::sql_types::zoned::Zoned;
13use diesel::prelude::*;
14use diesel::result::Error as DieselError;
15use nil_crypto::password::Password;
16use tap::Pipe;
17
18macro_rules! decl_get {
19 ($fn_name:ident, $model:ident) => {
20 pub fn $fn_name(&self, id: GameId) -> Result<$model> {
21 use $crate::schema::game;
22
23 let result = game::table
24 .find(&id)
25 .select($model::as_select())
26 .first(&mut *self.conn());
27
28 if let Err(DieselError::NotFound) = &result {
29 Err(Error::GameNotFound(id))
30 } else {
31 Ok(result?)
32 }
33 }
34 };
35}
36
37macro_rules! decl_get_all {
38 ($fn_name:ident, $model:ident) => {
39 pub fn $fn_name(&self) -> Result<Vec<$model>> {
40 use $crate::schema::game;
41
42 game::table
43 .select($model::as_select())
44 .load(&mut *self.conn())
45 .map_err(Into::into)
46 }
47 };
48}
49
50impl BlockingDatabase {
51 decl_get!(get_game, Game);
52 decl_get!(get_game_with_blob, GameWithBlob);
53
54 decl_get_all!(get_games, Game);
55 decl_get_all!(get_games_with_blob, GameWithBlob);
56
57 pub fn count_games(&self) -> Result<i64> {
58 use crate::schema::game;
59
60 game::table
61 .count()
62 .get_result(&mut *self.conn())
63 .map_err(Into::into)
64 }
65
66 pub fn create_game(&self, new: &NewGame) -> Result<usize> {
67 use crate::schema::game;
68
69 diesel::insert_into(game::table)
70 .values(new)
71 .on_conflict(game::id)
72 .do_update()
73 .set((
74 game::world_blob.eq(new.blob()),
75 game::updated_at.eq(Zoned::now()),
76 ))
77 .execute(&mut *self.conn())
78 .map_err(Into::into)
79 }
80
81 pub fn delete_game(&self, id: GameId) -> Result<usize> {
82 use crate::schema::game;
83
84 diesel::delete(game::table.find(id))
85 .execute(&mut *self.conn())
86 .map_err(Into::into)
87 }
88
89 pub fn delete_games(&self, ids: &[GameId]) -> Result<usize> {
90 use crate::schema::game;
91
92 if ids.is_empty() {
93 Ok(0)
94 } else {
95 diesel::delete(game::table.filter(game::id.eq_any(ids)))
96 .execute(&mut *self.conn())
97 .map_err(Into::into)
98 }
99 }
100
101 pub fn game_exists(&self, id: GameId) -> Result<bool> {
102 use crate::schema::game;
103 use diesel::dsl::{exists, select};
104
105 select(exists(game::table.find(id)))
106 .get_result(&mut *self.conn())
107 .map_err(Into::into)
108 }
109
110 pub fn get_game_creator(&self, id: GameId) -> Result<PlayerId> {
111 use crate::schema::game;
112
113 let user_id = game::table
114 .find(&id)
115 .select(game::created_by)
116 .first::<UserId>(&mut *self.conn())?;
117
118 self.get_user_player_id(user_id)
119 }
120
121 pub fn get_game_ids(&self) -> Result<Vec<GameId>> {
122 use crate::schema::game;
123
124 game::table
125 .select(game::id)
126 .load(&mut *self.conn())
127 .map_err(Into::into)
128 }
129
130 pub fn get_game_password(&self, id: GameId) -> Result<Option<HashedPassword>> {
131 use crate::schema::game;
132
133 let result = game::table
134 .find(&id)
135 .select(game::password)
136 .first(&mut *self.conn());
137
138 if let Err(DieselError::NotFound) = &result {
139 Err(Error::GameNotFound(id))
140 } else {
141 Ok(result?)
142 }
143 }
144
145 pub fn get_game_round_duration(&self, id: GameId) -> Result<Option<Duration>> {
146 use crate::schema::game;
147
148 let result = game::table
149 .find(&id)
150 .select(game::round_duration)
151 .first(&mut *self.conn());
152
153 if let Err(DieselError::NotFound) = &result {
154 Err(Error::GameNotFound(id))
155 } else {
156 Ok(result?)
157 }
158 }
159
160 pub fn update_game_blob(&self, id: GameId, blob: &[u8]) -> Result<usize> {
161 use crate::schema::game;
162
163 let n = diesel::update(game::table.find(&id))
164 .set((game::world_blob.eq(blob), game::updated_at.eq(Zoned::now())))
165 .execute(&mut *self.conn())?;
166
167 if n == 0 { Err(Error::GameNotFound(id)) } else { Ok(n) }
168 }
169
170 pub fn verify_game_password(&self, id: GameId, password: Option<&Password>) -> Result<bool> {
171 if let Some(hash) = self.get_game_password(id)? {
172 password
173 .filter(|it| !it.trim().is_empty())
174 .is_some_and(|it| matches!(hash.verify(it), Ok(true)))
175 .pipe(Ok)
176 } else {
177 Ok(true)
178 }
179 }
180
181 pub fn was_game_created_by(&self, game_id: GameId, player_id: &PlayerId) -> Result<bool> {
182 Ok(&self.get_game_creator(game_id)? == player_id)
183 }
184}