1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use actix_web::{get, post, put, web, HttpResponse, Responder};
use de_lobby_model::{Game, GamePlayer, GamePlayerInfo, GameSetup, Validatable};
use log::{error, warn};

use super::db::{AdditionError, CreationError, Games, RemovalError};
use crate::auth::Claims;

/// Registers all authentication endpoints.
pub(super) fn configure(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/games")
            .service(create)
            .service(get)
            .service(list)
            .service(join)
            .service(leave),
    );
}

#[post("")]
async fn create(
    claims: web::ReqData<Claims>,
    games: web::Data<Games>,
    game_setup: web::Json<GameSetup>,
) -> impl Responder {
    let game_setup = game_setup.into_inner();
    if let Err(error) = game_setup.validate() {
        warn!("Invalid game setup: {:?}", error);
        return HttpResponse::BadRequest().json(format!("{error}"));
    }

    let game = Game::from_author(game_setup, claims.username().to_owned());
    match games.create(game).await {
        Ok(_) => HttpResponse::Ok().json(()),
        Err(CreationError::NameTaken) => {
            warn!("Game creation error: game name is already taken.");
            HttpResponse::Conflict().json("Game name is already taken.")
        }
        Err(CreationError::AdditionError(AdditionError::AlreadyInAGame)) => {
            warn!("Game creation error: a user is already in a different game.");
            HttpResponse::Forbidden().json("User is already in different game.")
        }
        Err(error) => {
            error!("Game creation error: {:?}", error);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[get("/{name}")]
async fn get(path: web::Path<String>, games: web::Data<Games>) -> impl Responder {
    let name = path.into_inner();
    match games.get(&name).await {
        Ok(Some(game)) => HttpResponse::Ok().json(game),
        Ok(None) => HttpResponse::NotFound().json("Game not found"),
        Err(error) => {
            error!("Game get error: {:?}", error);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[get("")]
async fn list(games: web::Data<Games>) -> impl Responder {
    match games.list().await {
        Ok(games) => HttpResponse::Ok().json(games),
        Err(error) => {
            error!("Game listing error: {:?}", error);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[put("/{name}/join")]
async fn join(
    claims: web::ReqData<Claims>,
    games: web::Data<Games>,
    path: web::Path<String>,
    player_info: web::Json<GamePlayerInfo>,
) -> impl Responder {
    let name = path.into_inner();

    let ordinal = player_info.ordinal();
    if ordinal == 0 {
        warn!("Game joining error: got ordinal equal to 0.");
        return HttpResponse::BadRequest().json("Ordinals start with 0, got 1.");
    }

    let player = GamePlayer::new(claims.username().to_owned(), player_info.0);
    match games.add_player(&player, name.as_str()).await {
        Ok(_) => HttpResponse::Ok().json(()),
        Err(AdditionError::AlreadyInAGame) => {
            warn!("Game joining error: a user is already in a different game.");
            HttpResponse::Forbidden().json("User is already in a different game.")
        }
        Err(AdditionError::OrdinalConflict) => {
            warn!("Game joining error: player ordinal conflict.");
            HttpResponse::Conflict()
                .json("Another player has already joined the game under the given ordinal.")
        }
        Err(AdditionError::OrdinalTooLarge) => {
            warn!("Game joining error: too large ordinal: {ordinal}");
            HttpResponse::Conflict()
                .json("The given ordinal is larger than maximum number of players.")
        }
        Err(AdditionError::UserOrGameDoesNotExist) => {
            warn!("Game joining error: the game or the user does not exist");
            HttpResponse::NotFound().json("Game not found.")
        }
        Err(error) => {
            error!("Error while adding a player to a game: {:?}", error);
            HttpResponse::InternalServerError().finish()
        }
    }
}

#[put("/{name}/leave")]
async fn leave(
    claims: web::ReqData<Claims>,
    games: web::Data<Games>,
    path: web::Path<String>,
) -> impl Responder {
    let name = path.into_inner();

    match games.remove_player(claims.username(), name.as_str()).await {
        Ok(_) => HttpResponse::Ok().json(()),
        Err(RemovalError::NotInTheGame) => {
            warn!("Game leaving error: the user is not in the game.");
            HttpResponse::Forbidden().json("The user is not in the game.")
        }
        Err(error) => {
            error!("Error while removing a player from a game: {:?}", error);
            HttpResponse::InternalServerError().finish()
        }
    }
}