From 661a63af8f1ace15d6a809a022beb366854a6373 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Fri, 2 Jan 2026 15:24:38 +0100 Subject: [PATCH] Implemented deleting games as admin. --- backend-actix/gamenight-api.yaml | 22 +++++++++-- backend-actix/src/main.rs | 1 + backend-actix/src/request/authorization.rs | 7 ---- backend-actix/src/request/game.rs | 31 +++++++++++++-- backend-actix/src/request/mod.rs | 1 + gamenight-api-client-rs/README.md | 1 + gamenight-api-client-rs/docs/DefaultApi.md | 29 ++++++++++++++ .../src/apis/default_api.rs | 38 +++++++++++++++++++ gamenight-cli/src/flows/add_game.rs | 10 ++--- gamenight-cli/src/flows/mod.rs | 3 +- gamenight-cli/src/flows/remove_game.rs | 38 +++++++++++++++++++ gamenight-cli/src/flows/view_game.rs | 3 +- gamenight-database/src/game.rs | 7 ++++ 13 files changed, 168 insertions(+), 23 deletions(-) create mode 100644 gamenight-cli/src/flows/remove_game.rs diff --git a/backend-actix/gamenight-api.yaml b/backend-actix/gamenight-api.yaml index b7ccc00..d150a01 100644 --- a/backend-actix/gamenight-api.yaml +++ b/backend-actix/gamenight-api.yaml @@ -201,9 +201,21 @@ paths: $ref: '#/components/requestBodies/AddGameRequest' security: - JWT-Auth: [] + delete: + responses: + '200': + description: "Ok" + '401': + $ref: '#/components/responses/FailureResponse' + '422': + $ref: '#/components/responses/FailureResponse' + requestBody: + $ref: '#/components/requestBodies/RemoveGameRequest' + security: + - JWT-Auth: [ ] /rename_game: post: - responses: + responses: '200': description: "OK" '401': @@ -315,9 +327,6 @@ paths: $ref: '#/components/requestBodies/AuthorizedLocationUserIdsRequest' security: - JWT-Auth: [] - - - components: schemas: Gamenight: @@ -590,6 +599,11 @@ components: application/json: schema: $ref: '#/components/schemas/RenameGameRequestBody' + RemoveGameRequest: + content: + application/json: + schema: + $ref: '#/components/schemas/GameId' OwnGameRequest: content: application/json: diff --git a/backend-actix/src/main.rs b/backend-actix/src/main.rs index 96ef763..b107635 100644 --- a/backend-actix/src/main.rs +++ b/backend-actix/src/main.rs @@ -56,6 +56,7 @@ async fn main() -> std::io::Result<()> { .service(post_own_game) .service(post_disown_game) .service(get_owned_games) + .service(delete_game) .service(get_locations) .service(post_location) .service(post_location_authorize) diff --git a/backend-actix/src/request/authorization.rs b/backend-actix/src/request/authorization.rs index bea9f0a..1c2cf25 100644 --- a/backend-actix/src/request/authorization.rs +++ b/backend-actix/src/request/authorization.rs @@ -21,13 +21,6 @@ pub struct Claims { pub struct AuthUser(pub User); -// pub struct AuthUser { -// pub id: Uuid, -// pub username: String, -// pub email: String, -// pub role: Role, -// } - impl From for AuthUser { fn from(value: User) -> Self { Self(value) diff --git a/backend-actix/src/request/game.rs b/backend-actix/src/request/game.rs index b42f357..4628b38 100644 --- a/backend-actix/src/request/game.rs +++ b/backend-actix/src/request/game.rs @@ -1,10 +1,17 @@ -use actix_web::{get, http::header::ContentType, post, web, HttpResponse, Responder}; +use crate::game::rename_game; +use crate::owned_game::own_game; +use crate::owned_game::owned_games; +use crate::owned_game::disown_game; +use crate::owned_game::OwnedGame; +use gamenight_database::game::load_game; +use crate::game::insert_game; +use uuid::Uuid; +use crate::game::remove_game; +use actix_web::{delete, get, http::header::ContentType, post, web, HttpResponse, Responder}; use gamenight_database::{ - game::{insert_game, load_game, rename_game}, - owned_game::{disown_game, own_game, owned_games, OwnedGame}, + user::Role, DbPool, GetConnection, }; -use uuid::Uuid; use crate::{ models::{ @@ -77,6 +84,22 @@ pub async fn post_game( .body(serde_json::to_string(&GameId{game_id: game.id.to_string()})?)) } +#[delete("/game")] +pub async fn delete_game( + pool: web::Data, + user: AuthUser, + game_id: web::Json, +) -> Result { + if user.0.role != Role::Admin { + Ok(HttpResponse::Unauthorized()) + } else { + let mut conn = pool.get_conn(); + remove_game(&mut conn, Uuid::parse_str(&game_id.0.game_id)?)?; + + Ok(HttpResponse::Ok()) + } +} + #[post("/rename_game")] pub async fn post_rename_game( pool: web::Data, diff --git a/backend-actix/src/request/mod.rs b/backend-actix/src/request/mod.rs index e2f40e9..6ae8db0 100644 --- a/backend-actix/src/request/mod.rs +++ b/backend-actix/src/request/mod.rs @@ -15,6 +15,7 @@ pub use game::post_disown_game; pub use game::post_game; pub use game::post_own_game; pub use game::post_rename_game; +pub use game::delete_game; pub use gamenight_handlers::gamenight_get; pub use gamenight_handlers::gamenight_post; pub use gamenight_handlers::gamenights; diff --git a/gamenight-api-client-rs/README.md b/gamenight-api-client-rs/README.md index 355f48b..e4f17de 100644 --- a/gamenight-api-client-rs/README.md +++ b/gamenight-api-client-rs/README.md @@ -29,6 +29,7 @@ Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- *DefaultApi* | [**authorized_location_user_ids_get**](docs/DefaultApi.md#authorized_location_user_ids_get) | **GET** /authorized_location_user_ids | *DefaultApi* | [**disown_post**](docs/DefaultApi.md#disown_post) | **POST** /disown | +*DefaultApi* | [**game_delete**](docs/DefaultApi.md#game_delete) | **DELETE** /game | *DefaultApi* | [**game_get**](docs/DefaultApi.md#game_get) | **GET** /game | *DefaultApi* | [**game_post**](docs/DefaultApi.md#game_post) | **POST** /game | *DefaultApi* | [**games_get**](docs/DefaultApi.md#games_get) | **GET** /games | diff --git a/gamenight-api-client-rs/docs/DefaultApi.md b/gamenight-api-client-rs/docs/DefaultApi.md index 8912504..703f377 100644 --- a/gamenight-api-client-rs/docs/DefaultApi.md +++ b/gamenight-api-client-rs/docs/DefaultApi.md @@ -6,6 +6,7 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**authorized_location_user_ids_get**](DefaultApi.md#authorized_location_user_ids_get) | **GET** /authorized_location_user_ids | [**disown_post**](DefaultApi.md#disown_post) | **POST** /disown | +[**game_delete**](DefaultApi.md#game_delete) | **DELETE** /game | [**game_get**](DefaultApi.md#game_get) | **GET** /game | [**game_post**](DefaultApi.md#game_post) | **POST** /game | [**games_get**](DefaultApi.md#games_get) | **GET** /games | @@ -86,6 +87,34 @@ Name | Type | Description | Required | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +## game_delete + +> game_delete(game_id) + + +### Parameters + + +Name | Type | Description | Required | Notes +------------- | ------------- | ------------- | ------------- | ------------- +**game_id** | Option<[**GameId**](GameId.md)> | | | + +### Return type + + (empty response body) + +### Authorization + +[JWT-Auth](../README.md#JWT-Auth) + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + + ## game_get > models::Game game_get(game_id) diff --git a/gamenight-api-client-rs/src/apis/default_api.rs b/gamenight-api-client-rs/src/apis/default_api.rs index 4d33346..7cf1253 100644 --- a/gamenight-api-client-rs/src/apis/default_api.rs +++ b/gamenight-api-client-rs/src/apis/default_api.rs @@ -33,6 +33,15 @@ pub enum DisownPostError { UnknownValue(serde_json::Value), } +/// struct for typed errors of method [`game_delete`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GameDeleteError { + Status401(models::Failure), + Status422(models::Failure), + UnknownValue(serde_json::Value), +} + /// struct for typed errors of method [`game_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] @@ -290,6 +299,35 @@ pub async fn disown_post(configuration: &configuration::Configuration, game_id: } } +pub async fn game_delete(configuration: &configuration::Configuration, game_id: Option) -> Result<(), Error> { + // add a prefix to parameters to efficiently prevent name collisions + let p_body_game_id = game_id; + + let uri_str = format!("{}/game", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::DELETE, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + if let Some(ref token) = configuration.bearer_access_token { + req_builder = req_builder.bearer_auth(token.to_owned()); + }; + req_builder = req_builder.json(&p_body_game_id); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + + if !status.is_client_error() && !status.is_server_error() { + Ok(()) + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { status, content, entity })) + } +} + pub async fn game_get(configuration: &configuration::Configuration, game_id: Option) -> Result> { // add a prefix to parameters to efficiently prevent name collisions let p_body_game_id = game_id; diff --git a/gamenight-cli/src/flows/add_game.rs b/gamenight-cli/src/flows/add_game.rs index 6444962..4e671a9 100644 --- a/gamenight-cli/src/flows/add_game.rs +++ b/gamenight-cli/src/flows/add_game.rs @@ -1,12 +1,10 @@ use std::fmt::Display; -use async_trait::async_trait; -use gamenight_api_client_rs::{apis::default_api::game_post, models::AddGameRequestBody, models::OwnGameRequestBody}; -use gamenight_api_client_rs::apis::default_api::{locations_get, own_post}; -use inquire::{Confirm, Select, Text}; -use crate::flows::flow_helpers::LocationSelectData; -use crate::flows::own::Own; use super::*; +use crate::flows::own::Own; +use async_trait::async_trait; +use gamenight_api_client_rs::{apis::default_api::game_post, models::AddGameRequestBody}; +use inquire::Text; #[derive(Clone)] diff --git a/gamenight-cli/src/flows/mod.rs b/gamenight-cli/src/flows/mod.rs index 5e251fd..a78965e 100644 --- a/gamenight-cli/src/flows/mod.rs +++ b/gamenight-cli/src/flows/mod.rs @@ -36,6 +36,7 @@ mod view_location; mod add_location; mod location_authorize; mod flow_helpers; +mod remove_game; pub struct GamenightState { api_configuration: Configuration, @@ -140,7 +141,7 @@ impl From for FlowError { pub enum FlowOutcome { Successful, Cancelled, - Abort + Abort, } type FlowResult<'a> = Result<(FlowOutcome, &'a mut GamenightState), FlowError>; diff --git a/gamenight-cli/src/flows/remove_game.rs b/gamenight-cli/src/flows/remove_game.rs new file mode 100644 index 0000000..84f30a2 --- /dev/null +++ b/gamenight-cli/src/flows/remove_game.rs @@ -0,0 +1,38 @@ +use std::fmt::Display; + +use async_trait::async_trait; +use gamenight_api_client_rs::models::GameId; + +use super::{Flow, FlowResult, GamenightState}; +use crate::domain::game::Game; +use crate::flows::list_games::ListGames; + +#[derive(Clone)] +pub struct RemoveGame { + pub game: Game +} + +impl RemoveGame { + pub fn new(game: Game) -> Self { + Self{ + game + } + } +} + +#[async_trait] +impl<'a> Flow<'a> for RemoveGame { + async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> { + let req = GameId { + game_id: self.game.id.to_string() + }; + gamenight_api_client_rs::apis::default_api::game_delete(&state.api_configuration, Some(req)).await?; + self.continue_with(state, &ListGames::new()).await + } +} + +impl Display for RemoveGame { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Remove") + } +} \ No newline at end of file diff --git a/gamenight-cli/src/flows/view_game.rs b/gamenight-cli/src/flows/view_game.rs index d869bdf..cbdff29 100644 --- a/gamenight-cli/src/flows/view_game.rs +++ b/gamenight-cli/src/flows/view_game.rs @@ -4,7 +4,7 @@ use inquire::Select; use uuid::Uuid; use crate::{domain::game::Game, flows::{disown::Disown, exit::Exit, own::Own, rename_game::RenameGame}}; - +use crate::flows::remove_game::RemoveGame; use super::*; #[derive(Clone)] @@ -45,6 +45,7 @@ impl<'a> Flow<'a> for ViewGame { let options: Vec + Send>> = vec![ own_or_disown, + Box::new(RemoveGame::new(game.clone())), Box::new(RenameGame::new(game.clone())), Box::new(Exit::new()) ]; diff --git a/gamenight-database/src/game.rs b/gamenight-database/src/game.rs index 50330f8..7c9afb2 100644 --- a/gamenight-database/src/game.rs +++ b/gamenight-database/src/game.rs @@ -35,3 +35,10 @@ pub fn rename_game( .set(game::name.eq(&name)) .execute(conn)?) } + +pub fn remove_game( + conn: &mut DbConnection, + id: Uuid, +) -> Result { + Ok(diesel::delete(game::table.filter(game::id.eq(id))).execute(conn)?) +}