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    /// * `time` - The time in hours to feed the players.
103    ///
104    /// # Returns
105    /// * `Ok(String)` if the players were successfully fed.
106    /// * `Err(String)` if an error occurred.
107    pub async fn feed_players(&self, n: i32, time: i32) -> Result<String, String> {
108        let connection = &mut establish_connection();
109
110        let mut query = players::table
111            .filter(players::state.ne(PlayerState::Human))
112            .filter(players::state.ne(PlayerState::Corpse))
113            .order(players::next_feeding.asc())
114            .into_boxed();
115        if n != 0 {
116            query = query.limit(n as i64);
117        }
118        let target_players = query
119            .load::<Player>(connection)
120            .map_err(|e| format!("Error selecting players to feed: {}", e))?;
121
122        for player in target_players.clone() {
123            player.feed(Some(time as i64)).await?;
124        }
125
126        Ok(format!("{} players fed", target_players.len()))
127    }
128
129    /// Feeds a single player.
130    ///
131    /// # Arguments
132    /// * `username` - The username of the player to feed.
133    ///
134    /// # Returns
135    /// * `Ok(String)` if the player was successfully fed.
136    /// * `Err(String)` if an error occurred.
137    pub async fn admin_feed(&self, username: String) -> Result<String, String> {
138        let connection = &mut establish_connection();
139
140        let result = diesel::update(players::table.filter(players::alias.eq(username)))
141            .set(players::next_feeding.eq(Zombie::feeding_time_calc()?))
142            .execute(connection)
143            .map_err(|e| format!("Error feeding player: {}", e))?;
144
145        Ok(format!("{} player fed", result))
146    }
147
148    /// Adds a day to the zombies' feeding time.
149    ///
150    /// # Returns
151    /// * `Ok(String)` if the zombies were successfully fed.
152    /// * `Err(String)` if an error occurred.
153    pub async fn feed_zombies_day(&self) -> Result<String, String> {
154        let connection = &mut establish_connection();
155
156        let mut target_players = players::table
157            .filter(players::next_feeding.is_not_null())
158            .load::<Player>(connection)
159            .map_err(|e| format!("Error loading zombies to feed: {}", e))?;
160
161        for player in &mut target_players {
162            if let Some(next_feeding) = player.next_feeding {
163                let mut new_next_feeding = next_feeding + chrono::Duration::days(1);
164                let weekday = new_next_feeding.weekday().number_from_monday();
165                if weekday == 5 || weekday == 6 {
166                    new_next_feeding += chrono::Duration::days(2);
167                }
168                diesel::update(players::table.filter(players::id.eq(&player.id)))
169                    .set(players::next_feeding.eq(new_next_feeding))
170                    .execute(connection)
171                    .map_err(|e| {
172                        format!(
173                            "Error updating next_feeding for player {}: {}",
174                            player.id, e
175                        )
176                    })?;
177            }
178        }
179
180        Ok(format!("{} zombies fed", target_players.len()))
181    }
182
183    /// Cures a player.
184    ///
185    /// # Arguments
186    /// * `id` - The ID of the player to cure.
187    ///
188    /// # Returns
189    /// * `Ok(String)` if the player was successfully cured.
190    /// * `Err(String)` if an error occurred.
191    pub async fn cure(&self, id: String) -> Result<String, String> {
192        let connection = &mut establish_connection();
193
194        self.transform(id.clone(), PlayerState::Human).await?;
195
196        let result = diesel::update(players::table.filter(players::id.eq(id)))
197            .set(players::next_feeding.eq(Option::<CustomDateTime>::None))
198            .execute(connection)
199            .map_err(|e| format!("Error transforming player: {}", e))?;
200
201        Ok(format!("{} player transformed", result))
202    }
203
204    /// Transforms a player into a certain state.
205    ///
206    /// # Arguments
207    /// * `alias` - The alias of the player to transform.
208    /// * `state` - The state to transform the player into.
209    ///
210    /// # Returns
211    /// * `Ok(String)` if the player was successfully transformed.
212    /// * `Err(String)` if an error occurred.
213    pub async fn transform(&self, alias: String, state: PlayerState) -> Result<String, String> {
214        let connection = &mut establish_connection();
215
216        let new_feeding_time = match state {
217            PlayerState::Zombie | PlayerState::Zombie0 => {
218                let new_feeding_time: CustomDateTime = Self::feeding_time_calc()?;
219                Some(new_feeding_time)
220            }
221            _ => None,
222        };
223
224        let result = diesel::update(players::table.filter(players::alias.eq(alias)))
225            .set((
226                players::state.eq(state),
227                players::next_feeding.eq(new_feeding_time),
228            ))
229            .execute(connection)
230            .map_err(|e| format!("Error transforming player: {}", e))?;
231
232        Ok(format!("{} player transformed", result))
233    }
234
235    /// Sets a player passing through the checkpoint
236    ///
237    /// # Arguments
238    /// * `player_id` (player that is at checkpoint)
239    ///
240    /// # Returns
241    /// * `Ok(String)` if the player was registered at the checkpoint successfully.
242    /// * `Err(String)` if an error occurred.
243    pub async fn player_at_checkpoint(&self, player_id: String) -> Result<String, String> {
244        let connection = &mut establish_connection();
245
246        let mut player = Player::get_player(player_id.clone()).await?;
247        if player.is_none() {
248            player = Player::get_player_by_alias(player_id).await?;
249        }
250
251        match player {
252            Some(player) => {
253                diesel::update(players::table)
254                    .filter(players::id.eq(player.id))
255                    .set(players::checkpoint.eq(Utc::now().naive_local()))
256                    .execute(connection)
257                    .expect("Failed to update player checkpoint");
258                Ok(format!("Player {} is at checkpoint", player.name))
259            }
260            None => Err("Player not found".to_string()),
261        }
262    }
263
264    /// Kills all players did not go through the checkpoint.
265    ///
266    /// # Returns
267    /// * `Ok(String)` if the players were killed successfully.
268    /// * `Err(String)` if an error occurred.
269    pub async fn kill_all_not_at_checkpoint(&self) -> Result<String, String> {
270        let connection = &mut establish_connection();
271
272        let result = diesel::update(players::table)
273            .filter(players::checkpoint.is_null())
274            .filter(players::state.eq(PlayerState::Human))
275            .filter(players::state.eq(PlayerState::Revived))
276            .set(players::state.eq(PlayerState::Corpse))
277            .execute(connection)
278            .map_err(|e| format!("Failed to kill players: {}", e))?;
279
280        Ok(format!("Killed {} players", result))
281    }
282
283    /// Allows an admin to inject SQL commands.
284    ///
285    /// # Arguments
286    /// * `sql_command` - The SQL command to execute.
287    ///
288    /// # Returns
289    /// * `Ok(String)` if the SQL command was successfully executed.
290    /// * `Err(String)` if an error occurred.
291    pub async fn inject_sql(&self, sql_command: String) -> Result<String, String> {
292        let connection = &mut establish_connection();
293
294        let result = sql_query(&sql_command)
295            .execute(connection)
296            .map_err(|e| format!("SQL execution error: {}", e))?;
297
298        Ok(format!("Changed {} entries", result))
299    }
300
301    /// Retrieves the logs from the application.
302    ///
303    /// # Returns
304    /// * `Ok(String)` if the logs were successfully retrieved.
305    /// * `Err(String)` if an error occurred.
306    pub async fn get_logs(&self) -> Result<String, String> {
307        let log_file_path = Some("log/app.log");
308
309        let log_file_path = match log_file_path {
310            Some(path) => path,
311            None => {
312                return Err(
313                    "Could not determine log file path from log4rs configuration.".to_string(),
314                );
315            }
316        };
317
318        let log_content = fs::read_to_string(&log_file_path)
319            .map_err(|e| format!("Failed to read log file: {}", e))?;
320
321        Ok(log_content)
322    }
323}