Skip to content

Commit

Permalink
add leave game endpoint and made sure to leave games when logging out
Browse files Browse the repository at this point in the history
also make login return the user object
  • Loading branch information
Timtam committed Mar 4, 2024
1 parent f62b175 commit 7635c2c
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 20 deletions.
1 change: 1 addition & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn rocket_from_config(figment: Figment) -> Rocket<Build> {
games_routes::create_game,
games_routes::get_all_games,
games_routes::join_game,
games_routes::leave_game,
],
)
.mount(
Expand Down
61 changes: 60 additions & 1 deletion server/src/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl OpenApiResponderInner for JoinGameError {
"409".to_string(),
RefOr::Object(OpenApiResponse {
description: "\
# [409 Conflicted](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409)\n\
# [409 Conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409)\n\
That user is already part of this game.\
"
.to_string(),
Expand Down Expand Up @@ -75,6 +75,65 @@ impl<'r> Responder<'r, 'static> for JoinGameError {
}
}

#[derive(Debug, Serialize, JsonSchema)]
pub struct LeaveGameError {
pub message: String,
#[serde(skip)]
pub http_status_code: u16,
}

impl OpenApiResponderInner for LeaveGameError {
fn responses(_generator: &mut OpenApiGenerator) -> Result<Responses, OpenApiError> {
let mut responses = Map::new();
responses.insert(
"404".to_string(),
RefOr::Object(OpenApiResponse {
description: "\
# [404 Not Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404)\n\
A game with that ID doesn't exist.\
"
.to_string(),
..Default::default()
}),
);
responses.insert(
"409".to_string(),
RefOr::Object(OpenApiResponse {
description: "\
# [409 Conflict](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409)\n\
That user isn't part of this game.\
"
.to_string(),
..Default::default()
}),
);
Ok(Responses {
responses,
..Default::default()
})
}
}

impl std::fmt::Display for LeaveGameError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "Join game error `{}`", self.message,)
}
}

impl std::error::Error for LeaveGameError {}

impl<'r> Responder<'r, 'static> for LeaveGameError {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
// Convert object to json
let body = serde_json::to_string(&self).unwrap();
Response::build()
.sized_body(body.len(), std::io::Cursor::new(body))
.header(ContentType::JSON)
.status(Status::new(self.http_status_code))
.ok()
}
}

#[derive(Serialize, Deserialize, JsonSchema)]
pub struct GamesResponse {
pub games: Vec<GameResponse>,
Expand Down
167 changes: 166 additions & 1 deletion server/src/routes/games.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
responses::{GameResponse, GamesResponse, JoinGameError, MessageResponse},
responses::{GameResponse, GamesResponse, JoinGameError, LeaveGameError, MessageResponse},
services::{GameService, UserService},
users::User,
};
Expand Down Expand Up @@ -85,6 +85,32 @@ pub async fn join_game(
}
}

#[openapi(tag = "Games")]
#[patch("/games/<game_id>/leave")]
pub async fn leave_game(
game_id: u32,
user: User,
games: &State<GameService>,
) -> Result<Json<MessageResponse>, LeaveGameError> {
if let Some(game) = games.get(game_id) {
match games.leave(game.id, user.id) {
Ok(_) => Ok(Json(MessageResponse {
message: "left the game successfully".into(),
r#type: "success".into(),
})),
Err(e) => Err(LeaveGameError {
message: e.into(),
http_status_code: 409,
}),
}
} else {
Err(LeaveGameError {
message: "game not found".into(),
http_status_code: 404,
})
}
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down Expand Up @@ -246,7 +272,146 @@ mod tests {
.dispatch()
.await
.status(),
Status::Conflict
);
}

#[sqlx::test]
async fn can_leave_game() {
let client = mocked_client().await;

create_test_users(&client).await;

let user = client
.post(uri!(user_routes::user_login))
.header(ContentType::JSON)
.body(
serde_json::to_string(&UserLoginPayload {
username: "testuser1".into(),
password: "abc1234".into(), // don't do this in practice!
})
.unwrap(),
)
.dispatch()
.await;

let game = client
.post(uri!(super::create_game))
.private_cookie(user.cookies().get_private("login").unwrap())
.dispatch()
.await;

assert_eq!(
client
.patch(uri!(super::leave_game(
game_id = game.into_json::<GameResponse>().await.unwrap().id
)))
.private_cookie(user.cookies().get_private("login").unwrap())
.dispatch()
.await
.status(),
Status::Ok
);
}

#[sqlx::test]
async fn cannot_leave_game_twice() {
let client = mocked_client().await;

create_test_users(&client).await;

let user = client
.post(uri!(user_routes::user_login))
.header(ContentType::JSON)
.body(
serde_json::to_string(&UserLoginPayload {
username: "testuser1".into(),
password: "abc1234".into(), // don't do this in practice!
})
.unwrap(),
)
.dispatch()
.await;

let game_id = client
.post(uri!(super::create_game))
.private_cookie(user.cookies().get_private("login").unwrap())
.dispatch()
.await
.into_json::<GameResponse>()
.await
.unwrap()
.id;

assert_eq!(
client
.patch(uri!(super::leave_game(game_id = game_id)))
.private_cookie(user.cookies().get_private("login").unwrap())
.dispatch()
.await
.status(),
Status::Ok
);

assert_eq!(
client
.patch(uri!(super::leave_game(game_id = game_id)))
.private_cookie(user.cookies().get_private("login").unwrap())
.dispatch()
.await
.status(),
Status::NotFound
);
}

#[sqlx::test]
async fn cannot_leave_game_without_joining() {
let client = mocked_client().await;

create_test_users(&client).await;

let response = client
.post(uri!(user_routes::user_login))
.header(ContentType::JSON)
.body(
serde_json::to_string(&UserLoginPayload {
username: "testuser1".into(),
password: "abc1234".into(), // don't do this in practice!
})
.unwrap(),
)
.dispatch()
.await;

let game = client
.post(uri!(super::create_game))
.private_cookie(response.cookies().get_private("login").unwrap())
.dispatch()
.await;

let response = client
.post(uri!(user_routes::user_login))
.header(ContentType::JSON)
.body(
serde_json::to_string(&UserLoginPayload {
username: "testuser2".into(),
password: "abc1234".into(), // don't do this in practice!
})
.unwrap(),
)
.dispatch()
.await;

assert_eq!(
client
.patch(uri!(super::leave_game(
game_id = game.into_json::<GameResponse>().await.unwrap().id
)))
.private_cookie(response.cookies().get_private("login").unwrap())
.dispatch()
.await
.status(),
Status::Conflict
);
}
}
Loading

0 comments on commit 7635c2c

Please sign in to comment.