Skip to main content

hvz/
admin.rs

1use std::fs;
2
3use chrono::{Datelike, Utc};
4use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, dsl::count_star, sql_query};
5use password_auth::generate_hash;
6
7use crate::{
8    establish_connection,
9    player::{Player, PlayerState},
10    schema::players,
11    settings::{CHECKPOINT, Settings},
12    time::CustomDateTime,
13    zombie::Zombie,
14};
15
16/// Represents an admin user.
17pub type Admin = Player;
18
19impl Admin {
20    /// Allows an admin to recover a player's password.
21    ///
22    /// # Arguments
23    /// * `username` - The username of the player to recover the password for.
24    /// * `password` - The new password to set for the player.
25    ///
26    /// # Returns
27    /// * `Ok(())` if the password was successfully recovered.
28    /// * `Err(String)` if an error occurred.
29    pub async fn recover_password(&self, username: String, password: String) -> Result<(), String> {
30        let connection = &mut establish_connection();
31
32        diesel::update(players::table.filter(players::alias.eq(username)))
33            .set(players::password.eq(generate_hash(password)))
34            .execute(connection)
35            .expect("Failed to update player");
36
37        Ok(())
38    }
39
40    /// Returns the number of players of each possible player state.
41    ///
42    /// # Returns
43    /// * `Ok(String)` if the count was successfully retrieved.
44    /// * `Err(String)` if an error occurred.
45    pub async fn count_player(&self) -> Result<String, String> {
46        let connection = &mut establish_connection();
47
48        let total = players::table
49            .count()
50            .get_result::<i64>(connection)
51            .expect("Error counting the players.");
52        let count = players::table
53            .group_by(players::state)
54            .select((players::state, count_star()))
55            .load::<(PlayerState, i64)>(connection)
56            .expect("Error loading the roles");
57
58        let mut result = format!("Total players: {}", total.to_string());
59
60        let state_string = count
61            .iter()
62            .map(|state| {
63                format!(
64                    "{}: {}, ",
65                    match state.0 {
66                        PlayerState::Human => "Humans",
67                        PlayerState::Zombie => "Zombies",
68                        PlayerState::Zombie0 => "Zombie0",
69                        PlayerState::Corpse => "Corpse",
70                        _ => "Others",
71                    },
72                    state.1
73                )
74            })
75            .collect::<String>();
76
77        result.push_str(&state_string[..state_string.len() - 2]);
78
79        Ok(result)
80    }
81
82    /// Get all players for admins
83    ///
84    /// # Returns
85    /// * `Ok(Vec<Player>)` if the players were successfully retrieved.
86    /// * `Err(String)` if an error occurred.
87    pub async fn admin_get_players(&self) -> Result<Vec<Player>, String> {
88        let connection = &mut establish_connection();
89
90        let players = players::table
91            .order(players::player_number.asc())
92            .load::<Player>(connection)
93            .map_err(|e| format!("Error selecting players: {}", e))?;
94
95        Ok(players)
96    }
97
98    /// Feeds a certain amount of players
99    ///
100    /// # Arguments
101    /// * `n` - The amount of players to feed.
102    ///
103    /// # Returns
104    /// * `Ok(String)` if the players were successfully fed.
105    /// * `Err(String)` if an error occurred.
106    pub async fn feed_players(&self, n: i32) -> Result<String, String> {
107        let connection = &mut establish_connection();
108
109        let target_ids = players::table
110            .filter(players::state.ne(PlayerState::Human))
111            .order(players::next_feeding.asc())
112            .select(players::id)
113            .limit(n as i64)
114            .load::<String>(connection)
115            .map_err(|e| format!("Error selecting players to feed: {}", e))?;
116
117        let result = diesel::update(players::table.filter(players::id.eq_any(&target_ids)))
118            .set(players::next_feeding.eq(Zombie::feeding_time_calc()?))
119            .execute(connection)
120            .map_err(|e| format!("Error feeding players: {}", e))?;
121
122        Ok(format!("{} players fed", result))
123    }
124
125    /// Feeds a single player.
126    ///
127    /// # Arguments
128    /// * `id` - The ID of the player to feed.
129    ///
130    /// # Returns
131    /// * `Ok(String)` if the player was successfully fed.
132    /// * `Err(String)` if an error occurred.
133    pub async fn admin_feed(&self, id: String) -> Result<String, String> {
134        let connection = &mut establish_connection();
135
136        let result = diesel::update(players::table.filter(players::id.eq(id)))
137            .set(players::next_feeding.eq(Zombie::feeding_time_calc()?))
138            .execute(connection)
139            .map_err(|e| format!("Error feeding player: {}", e))?;
140
141        Ok(format!("{} player fed", result))
142    }
143
144    /// Adds a day to the zombies' feeding time.
145    ///
146    /// # Returns
147    /// * `Ok(String)` if the zombies were successfully fed.
148    /// * `Err(String)` if an error occurred.
149    pub async fn feed_zombies_day(&self) -> Result<String, String> {
150        let connection = &mut establish_connection();
151
152        let mut target_players = players::table
153            .filter(players::next_feeding.is_not_null())
154            .load::<Player>(connection)
155            .map_err(|e| format!("Error loading zombies to feed: {}", e))?;
156
157        for player in &mut target_players {
158            if let Some(next_feeding) = player.next_feeding {
159                let mut new_next_feeding = next_feeding + chrono::Duration::days(1);
160                let weekday = new_next_feeding.weekday().number_from_monday();
161                if weekday == 5 || weekday == 6 {
162                    new_next_feeding += chrono::Duration::days(2);
163                }
164                diesel::update(players::table.filter(players::id.eq(&player.id)))
165                    .set(players::next_feeding.eq(new_next_feeding))
166                    .execute(connection)
167                    .map_err(|e| {
168                        format!(
169                            "Error updating next_feeding for player {}: {}",
170                            player.id, e
171                        )
172                    })?;
173            }
174        }
175
176        Ok(format!("{} zombies fed", target_players.len()))
177    }
178
179    /// Cures a player.
180    ///
181    /// # Arguments
182    /// * `id` - The ID of the player to cure.
183    ///
184    /// # Returns
185    /// * `Ok(String)` if the player was successfully cured.
186    /// * `Err(String)` if an error occurred.
187    pub async fn cure(&self, id: String) -> Result<String, String> {
188        let connection = &mut establish_connection();
189
190        self.transform(id.clone(), PlayerState::Human).await?;
191
192        let result = diesel::update(players::table.filter(players::id.eq(id)))
193            .set(players::next_feeding.eq(Option::<CustomDateTime>::None))
194            .execute(connection)
195            .map_err(|e| format!("Error transforming player: {}", e))?;
196
197        Ok(format!("{} player transformed", result))
198    }
199
200    /// Transforms a player into a certain state.
201    ///
202    /// # Arguments
203    /// * `alias` - The alias of the player to transform.
204    /// * `state` - The state to transform the player into.
205    ///
206    /// # Returns
207    /// * `Ok(String)` if the player was successfully transformed.
208    /// * `Err(String)` if an error occurred.
209    pub async fn transform(&self, alias: String, state: PlayerState) -> Result<String, String> {
210        let connection = &mut establish_connection();
211
212        let result = diesel::update(players::table.filter(players::alias.eq(alias)))
213            .set(players::state.eq(state))
214            .execute(connection)
215            .map_err(|e| format!("Error transforming player: {}", e))?;
216
217        Ok(format!("{} player transformed", result))
218    }
219
220    /// Sets the checkpoint coordinates.
221    ///
222    /// # Arguments
223    /// * `coordinates` - The coordinates to set as the checkpoint.
224    ///
225    /// # Returns
226    /// * `Ok(String)` if the checkpoint was successfully set.
227    /// * `Err(String)` if an error occurred.
228    pub async fn set_checkpoint(&self, x: String, y: String) -> Result<String, String> {
229        let coordinates = format!("{} {}", x, y);
230
231        match Settings::set_settings(CHECKPOINT, coordinates.clone()).await {
232            Ok(_) => Ok(coordinates),
233            Err(_) => Err("Error setting the coordinates!".to_string()),
234        }
235    }
236
237    /// Sets a player passing through the checkpoint
238    ///
239    /// # Arguments
240    /// * `player_id` (player that is at checkpoint)
241    ///
242    /// # Returns
243    /// * `Ok(String)` if the player was registered at the checkpoint successfully.
244    /// * `Err(String)` if an error occurred.
245    pub async fn player_at_checkpoint(&self, player_id: String) -> Result<String, String> {
246        let connection = &mut establish_connection();
247
248        let player = Player::get_player(player_id).await?;
249
250        match player {
251            Some(player) => {
252                diesel::update(players::table)
253                    .filter(players::id.eq(player.id))
254                    .set(players::checkpoint.eq(Utc::now().naive_local()))
255                    .execute(connection)
256                    .expect("Failed to update player checkpoint");
257                Ok(format!("Player {} is at checkpoint", player.name))
258            }
259            None => Err("Player not found".to_string()),
260        }
261    }
262
263    /// Kills all players did not go through the checkpoint.
264    ///
265    /// # Returns
266    /// * `Ok(String)` if the players were killed successfully.
267    /// * `Err(String)` if an error occurred.
268    pub async fn kill_all_not_at_checkpoint(&self) -> Result<String, String> {
269        let connection = &mut establish_connection();
270
271        let result = diesel::update(players::table)
272            .filter(players::checkpoint.is_null())
273            .filter(players::state.eq(PlayerState::Human))
274            .filter(players::state.eq(PlayerState::Revived))
275            .set(players::state.eq(PlayerState::Corpse))
276            .execute(connection)
277            .map_err(|e| format!("Failed to kill players: {}", e))?;
278
279        Ok(format!("Killed {} players", result))
280    }
281
282    /// Allows an admin to inject SQL commands.
283    ///
284    /// # Arguments
285    /// * `sql_command` - The SQL command to execute.
286    ///
287    /// # Returns
288    /// * `Ok(String)` if the SQL command was successfully executed.
289    /// * `Err(String)` if an error occurred.
290    pub async fn inject_sql(&self, sql_command: String) -> Result<String, String> {
291        let connection = &mut establish_connection();
292
293        let result = sql_query(&sql_command)
294            .execute(connection)
295            .map_err(|e| format!("SQL execution error: {}", e))?;
296
297        Ok(format!("Changed {} entries", result))
298    }
299
300    /// Retrieves the logs from the application.
301    ///
302    /// # Returns
303    /// * `Ok(String)` if the logs were successfully retrieved.
304    /// * `Err(String)` if an error occurred.
305    pub async fn get_logs(&self) -> Result<String, String> {
306        let log_file_path = Some("log/app.log");
307
308        let log_file_path = match log_file_path {
309            Some(path) => path,
310            None => {
311                return Err(
312                    "Could not determine log file path from log4rs configuration.".to_string(),
313                );
314            }
315        };
316
317        let log_content = fs::read_to_string(&log_file_path)
318            .map_err(|e| format!("Failed to read log file: {}", e))?;
319
320        Ok(log_content)
321    }
322}