Skip to main content

hvz/endpoints/
player.rs

1use actix_files::NamedFile;
2use actix_multipart::Multipart;
3use actix_web::{HttpRequest, HttpResponse, Responder, delete, get, post, web};
4use chrono::NaiveDate;
5use futures::future::join_all;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    player::{Player, PlayerRole, PlayerState},
10    time::CustomDateTime,
11    token::Token,
12};
13
14/// Struct representing a new player to be created.
15#[derive(Debug, Serialize, Deserialize)]
16pub struct NewPlayer {
17    pub name: String,
18    pub surname: String,
19    pub pronouns: String,
20    pub alias: String,
21    pub niu: String,
22    pub email: String,
23    pub phone: String,
24    pub faculty: String,
25    pub isuab: bool,
26    pub how_did_you_hear_about_us: String,
27    pub social_media: Option<String>,
28    pub password: String,
29    pub birth_date: NaiveDate,
30    pub accept_info_other_editions: bool,
31    pub wants_to_be_zombie: i32,
32}
33
34/// Struct representing a player to be edited.
35#[derive(Debug, Serialize, Deserialize)]
36pub struct EditPlayer {
37    pub bio: String,
38    pub pronouns: String,
39    pub alias: String,
40    pub email: String,
41    pub phone: String,
42    pub social_media: Option<String>,
43    pub accept_info_other_editions: bool,
44}
45
46/// Struct representing a player to be logged in.
47#[derive(Debug, Serialize, Deserialize)]
48pub struct Login {
49    pub username: String,
50    pub password: String,
51}
52
53/// Struct returned from a successful login.
54#[derive(Debug, Serialize, Deserialize)]
55pub struct LoginReturn {
56    pub uuid: String,
57    pub token: String,
58}
59
60/// Struct representing a player to change their password.
61#[derive(Debug, Serialize, Deserialize)]
62pub struct ChangePassword {
63    pub old_password: String,
64    pub new_password: String,
65}
66
67/// Struct representing a player to be returned.
68#[derive(Debug, Serialize, Deserialize)]
69pub struct PlayerReturnInfo {
70    pub name: String,
71    pub surname: String,
72    pub bio: String,
73    pub pronouns: String,
74    pub alias: String,
75    pub social_media: Option<String>,
76    pub player_number: i32,
77    pub state: PlayerState,
78    pub role: PlayerRole,
79    pub next_feeding: Option<CustomDateTime>,
80}
81
82/// **POST /new** - Create a new player
83#[post("/new")]
84pub async fn new_player(form: web::Json<NewPlayer>) -> impl Responder {
85    let player = form.into_inner();
86    let new_db_player = Player::new(
87        player.name,
88        player.surname,
89        player.pronouns,
90        player.alias,
91        player.niu,
92        player.email,
93        player.phone,
94        player.faculty,
95        player.isuab,
96        player.how_did_you_hear_about_us,
97        player.social_media,
98        player.password,
99        player.birth_date,
100        player.accept_info_other_editions,
101        player.wants_to_be_zombie,
102    )
103    .await;
104
105    let insert_result = new_db_player.signup().await;
106
107    match insert_result {
108        Ok(player) => {
109            let token: Token = match Token::generate(&player.id) {
110                Ok(t) => t,
111                Err(e) => return HttpResponse::InternalServerError().body(e),
112            };
113            HttpResponse::Ok().json(LoginReturn { token: token.token, uuid: player.id })
114        }
115        Err(_) => HttpResponse::InternalServerError().body("Failed to create player\n"),
116    }
117}
118
119/// **POST /login** - Logs in a player
120#[post("/login")]
121pub async fn login(form: web::Json<Login>) -> impl Responder {
122    let Login { username, password } = form.into_inner();
123
124    let result = Player::login(&username, password).await;
125
126    match result {
127        Ok(player) => {
128            let token: Token = match Token::generate(&player.id) {
129                Ok(t) => t,
130                Err(e) => return HttpResponse::InternalServerError().body(e),
131            };
132            HttpResponse::Ok().json(LoginReturn {
133                uuid: player.id,
134                token: token.token,
135            })
136        }
137        Err(_) => HttpResponse::InternalServerError().body("Player not found\n"),
138    }
139}
140
141/// **POST /logout** - Logs out a player
142#[post("/logout/{url_id}")]
143pub async fn logout(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
144    if let Err(e) = Token::check_token(&req, &url_id).await {
145        return HttpResponse::Forbidden().body(e);
146    }
147
148    let player = match Player::get_player(url_id.into_inner()).await {
149        Ok(Some(player)) => player,
150        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
151        Err(err) => return HttpResponse::InternalServerError().body(err),
152    };
153
154    let result = Token::clear_player_tokens(&player);
155
156    match result {
157        Ok(_) => HttpResponse::Ok().finish(),
158        Err(err) => HttpResponse::InternalServerError().body(err),
159    }
160}
161
162/// **POST /change_password** - Changes a player's password
163#[post("/change_password/{url_id}")]
164pub async fn change_password(
165    url_id: web::Path<String>,
166    req: HttpRequest,
167    form: web::Json<ChangePassword>,
168) -> impl Responder {
169    if let Err(e) = Token::check_token(&req, &url_id).await {
170        return HttpResponse::Forbidden().body(e);
171    }
172
173    let player = match Player::get_player(url_id.into_inner()).await {
174        Ok(Some(player)) => player,
175        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
176        Err(err) => return HttpResponse::InternalServerError().body(err),
177    };
178
179    let ChangePassword {
180        old_password,
181        new_password,
182    } = form.into_inner();
183
184    if let Err(e) = Token::clear_player_tokens(&player) {
185        return HttpResponse::InternalServerError().body(e);
186    };
187    let token = match Token::generate(&player.id) {
188        Ok(t) => t,
189        Err(e) => return HttpResponse::InternalServerError().body(e),
190    };
191
192    let result = player.change_password(old_password, new_password).await;
193
194    match result {
195        Ok(_) => HttpResponse::Ok().json((token.token, player.id)),
196        Err(e) => HttpResponse::InternalServerError().body(e),
197    }
198}
199
200/// **POST /edit_player/{url_id}** - Edits a player's information
201#[post("/edit_player/{url_id}")]
202pub async fn edit_player(
203    url_id: web::Path<String>,
204    req: HttpRequest,
205    form: web::Json<EditPlayer>,
206) -> impl Responder {
207    if let Err(e) = Token::check_token(&req, &url_id).await {
208        return HttpResponse::Forbidden().body(e);
209    }
210
211    let player = match Player::get_player(url_id.into_inner()).await {
212        Ok(Some(player)) => player,
213        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
214        Err(err) => return HttpResponse::InternalServerError().body(err),
215    };
216
217    let edit_data = form.into_inner();
218    let result = player
219        .edit(
220            edit_data.bio,
221            edit_data.pronouns,
222            edit_data.alias,
223            edit_data.email,
224            edit_data.phone,
225            edit_data.social_media,
226            edit_data.accept_info_other_editions,
227        )
228        .await;
229
230    match result {
231        Ok(_) => HttpResponse::Ok().body("Player edited\n"),
232        Err(_) => HttpResponse::InternalServerError().body("Failed to edit player\n"),
233    }
234}
235
236/// **POST /set_pfp/{url_id}** - Sets a player's profile picture
237#[post("/set_pfp/{url_id}")]
238pub async fn set_pfp(
239    url_id: web::Path<String>,
240    req: HttpRequest,
241    mut img: Multipart,
242) -> impl Responder {
243    if let Err(e) = Token::check_token(&req, &url_id).await {
244        return HttpResponse::Forbidden().body(e);
245    }
246
247    let player = match Player::get_player(url_id.into_inner()).await {
248        Ok(Some(player)) => player,
249        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
250        Err(err) => return HttpResponse::InternalServerError().body(err),
251    };
252
253    let result = player.set_pfp(&mut img).await;
254
255    match result {
256        Ok(_) => HttpResponse::Ok().body("Profile picture set\n"),
257        Err(err) => HttpResponse::InternalServerError()
258            .body(format!("Failed to set profile picture: {}", err)),
259    }
260}
261
262/// **DELETE /remove_pfp/{url_id}** - Removes a player's profile picture
263#[delete("/remove_pfp/{url_id}")]
264pub async fn remove_pfp(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
265    if let Err(e) = Token::check_token(&req, &url_id).await {
266        return HttpResponse::Forbidden().body(e);
267    }
268
269    let player = match Player::get_player(url_id.into_inner()).await {
270        Ok(Some(player)) => player,
271        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
272        Err(err) => return HttpResponse::InternalServerError().body(err),
273    };
274
275    let result = player.remove_pfp().await;
276
277    match result {
278        Ok(_) => HttpResponse::Ok().body("Profile picture removed\n"),
279        Err(err) => HttpResponse::InternalServerError()
280            .body(format!("Failed to remove profile picture: {}", err)),
281    }
282}
283
284/// **GET /pfp/{url_id}** - Gets a player's profile picture
285#[get("/pfp/{url_id}")]
286pub async fn pfp(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
287    if let Err(e) = Token::check_token(&req, &url_id).await {
288        return HttpResponse::Forbidden().body(e);
289    }
290
291    let player = match Player::get_player(url_id.into_inner()).await {
292        Ok(Some(player)) => player,
293        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
294        Err(err) => return HttpResponse::InternalServerError().body(err),
295    };
296
297    let result = player.get_pfp().await;
298
299    match result {
300        Ok(named_file) => NamedFile::into_response(named_file, &req),
301        Err(_) => HttpResponse::InternalServerError().body("Failed to get profile picture\n"),
302    }
303}
304
305/// **POST /get_pfp/{url_id}** - Get someone else's profile picture
306#[post("/get_pfp/{url_id}")]
307pub async fn get_pfp(
308    url_id: web::Path<String>,
309    req: HttpRequest,
310    form: web::Json<String>,
311) -> impl Responder {
312    if let Err(e) = Token::check_token(&req, &url_id).await {
313        return HttpResponse::Forbidden().body(e);
314    }
315
316    match Player::get_player(url_id.into_inner()).await {
317        Ok(Some(player)) => player,
318        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
319        Err(err) => return HttpResponse::InternalServerError().body(err),
320    };
321
322    let other = match Player::get_player_by_alias(form.into_inner()).await {
323        Ok(Some(player)) => player,
324        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
325        Err(err) => return HttpResponse::InternalServerError().body(err),
326    };
327
328    let result = other.get_pfp().await;
329
330    match result {
331        Ok(named_file) => NamedFile::into_response(named_file, &req),
332        Err(_) => HttpResponse::InternalServerError().body("Failed to get profile picture\n"),
333    }
334}
335
336/// **GET /player/{url_id}** - Gets the current player by id
337#[get("/player/{url_id}")]
338pub async fn get_player(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
339    if let Err(e) = Token::check_token(&req, &url_id).await {
340        return HttpResponse::Forbidden().body(e);
341    }
342
343    match Player::get_player(url_id.into_inner()).await {
344        Ok(Some(player)) => HttpResponse::Ok().json(player),
345        Ok(None) => HttpResponse::NotFound().body("Player not found"),
346        Err(err) => HttpResponse::InternalServerError().body(err),
347    }
348}
349
350/// *POST /get_username/{url_id}** - Get the username of a player by id
351#[post("/get_username/{url_id}")]
352pub async fn get_username(url_id: web::Path<String>, req: HttpRequest, form: web::Json<String>) -> impl Responder {
353    if let Err(e) = Token::check_token(&req, &url_id).await {
354        return HttpResponse::Forbidden().body(e);
355    }
356
357    let player = match Player::get_player(url_id.into_inner()).await {
358        Ok(Some(player)) => player,
359        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
360        Err(err) => return HttpResponse::InternalServerError().body(err),
361    };
362
363    let other = Player::get_player(form.into_inner()).await;
364
365    match other {
366        Ok(Some(other)) => HttpResponse::Ok().body(other.alias),
367        Ok(None) => HttpResponse::NotFound().body("Player not found"),
368        Err(err) => HttpResponse::InternalServerError().body(err),
369    }
370}
371
372/// **POST /get_player/{url_id}** - Get another player by alias
373#[post("/get_player/{url_id}")]
374pub async fn get_other_player(
375    url_id: web::Path<String>,
376    req: HttpRequest,
377    form: web::Json<String>,
378) -> impl Responder {
379    if let Err(e) = Token::check_token(&req, &url_id).await {
380        return HttpResponse::Forbidden().body(e);
381    }
382
383    let player = match Player::get_player(url_id.into_inner()).await {
384        Ok(Some(player)) => player,
385        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
386        Err(err) => return HttpResponse::InternalServerError().body(err),
387    };
388
389    let other = Player::get_player_by_alias(form.into_inner()).await;
390
391    match other {
392        Ok(Some(p)) => {
393            let player_response = PlayerReturnInfo {
394                name: p.name,
395                surname: p.surname,
396                bio: p.bio,
397                pronouns: p.pronouns,
398                alias: p.alias,
399                social_media: p.social_media,
400                player_number: p.player_number,
401                state: if p.state == PlayerState::Zombie0 {
402                    PlayerState::Human
403                } else {
404                    p.state
405                },
406                role: p.role,
407                next_feeding: if player.is_zombie().await {
408                    p.next_feeding
409                } else {
410                    None
411                },
412            };
413            HttpResponse::Ok().json(player_response)
414        }
415        Ok(None) => HttpResponse::NotFound().body("Player not found"),
416        Err(err) => HttpResponse::InternalServerError().body(err),
417    }
418}
419
420/// **GET /users/{url_id}** - Get all users
421#[get("/users/{url_id}")]
422pub async fn get_users(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
423    if let Err(e) = Token::check_token(&req, &url_id).await {
424        return HttpResponse::Forbidden().body(e);
425    }
426
427    let player = match Player::get_player(url_id.into_inner()).await {
428        Ok(Some(player)) => player,
429        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
430        Err(err) => return HttpResponse::InternalServerError().body(err),
431    };
432    let is_zombie = player.is_zombie().await;
433
434    let result = Player::get_all_players().await;
435
436    match result {
437        Ok(players) => {
438            let futures = players.into_iter().map(|p| async move {
439                PlayerReturnInfo {
440                    name: p.name,
441                    surname: p.surname,
442                    bio: p.bio,
443                    pronouns: p.pronouns,
444                    alias: p.alias,
445                    social_media: p.social_media,
446                    player_number: p.player_number,
447                    state: if p.state == PlayerState::Zombie0 {
448                        PlayerState::Human
449                    } else {
450                        p.state
451                    },
452                    role: p.role,
453                    next_feeding: if is_zombie { p.next_feeding } else { None },
454                }
455            });
456
457            let return_players: Vec<PlayerReturnInfo> = join_all(futures).await;
458            HttpResponse::Ok().json(return_players)
459        }
460        Err(e) => {
461            HttpResponse::InternalServerError().body("Failed to get players\n")
462        }
463    }
464}
465
466/// **DELETE /delete_player/{url_id}** - Deletes a player by UUID
467#[delete("/delete_player/{url_id}")]
468pub async fn delete_player(url_id: web::Path<String>, req: HttpRequest) -> impl Responder {
469    if let Err(e) = Token::check_token(&req, &url_id).await {
470        return HttpResponse::Forbidden().body(e);
471    }
472
473    let player = match Player::get_player(url_id.into_inner()).await {
474        Ok(Some(player)) => player,
475        Ok(None) => return HttpResponse::NotFound().body("Player not found"),
476        Err(err) => return HttpResponse::InternalServerError().body(err),
477    };
478
479    if let Err(e) = Token::clear_player_tokens(&player) {
480        return HttpResponse::InternalServerError().body(e);
481    }
482
483    let result = Player::delete_player(player.id).await;
484
485    match result {
486        Ok(_) => HttpResponse::Ok().body("Player deleted\n"),
487        Err(_) => HttpResponse::InternalServerError().body("Failed to delete player\n"),
488    }
489}