From 3063146a76227133aca70d0942b1976cb706a1d6 Mon Sep 17 00:00:00 2001 From: djmil Date: Wed, 15 Nov 2023 13:27:52 +0100 Subject: [PATCH] GameProposal: Cancel - doPushing * JSON content type detection * unexpeted responce status: show warning instead of throwing exeption - useGamesApi: if already pushing - do not push another one wobling for Cancel button - middleware: GameProposal controller: handle Cancel requests --- .../api/GameProposalController.java | 24 +++++++++++++++--- .../cordaclient/CordaClient.java | 2 +- webapp/src/api/games.js | 25 +++++++++++-------- webapp/src/container/Games.jsx | 8 ++++-- webapp/src/container/games/action/Cancel.jsx | 7 +++--- webapp/src/container/games/action/Create.jsx | 7 ++---- webapp/src/hook/api.js | 15 ++++++++--- webapp/src/reducer/games.js | 14 ++++++++--- 8 files changed, 70 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/djmil/cordacheckers/api/GameProposalController.java b/backend/src/main/java/djmil/cordacheckers/api/GameProposalController.java index 66c83b5..5c32c81 100644 --- a/backend/src/main/java/djmil/cordacheckers/api/GameProposalController.java +++ b/backend/src/main/java/djmil/cordacheckers/api/GameProposalController.java @@ -1,11 +1,14 @@ package djmil.cordacheckers.api; import java.net.URI; +import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -33,11 +36,11 @@ public class GameProposalController { HoldingIdentityResolver holdingIdentityResolver; @PostMapping() - public ResponseEntity createGameProposal( + public ResponseEntity create( @AuthenticationPrincipal User sender, @RequestBody ReqGameProposalCreate gpRequest, - UriComponentsBuilder ucb) throws JsonMappingException, JsonProcessingException { - + UriComponentsBuilder ucb) throws JsonMappingException, JsonProcessingException + { final HoldingIdentity gpSender = sender.getHoldingIdentity(); final HoldingIdentity gpReceiver = holdingIdentityResolver.getByUsername(gpRequest.opponentName()); final Stone.Color gpReceiverColor = gpRequest.opponentColor(); @@ -47,7 +50,7 @@ public class GameProposalController { gpSender, gpReceiver, gpReceiverColor, - // gpRequest.board() // GireaIssue #3: use provided board configuration + // gpRequest.board() // GiteaIssue #3: use provided board configuration gpRequest.message()); URI locationOfNewGameProposal = ucb @@ -60,4 +63,17 @@ public class GameProposalController { .body(gameStateView); } + @PutMapping("/{uuid}/cancel") + public ResponseEntity cancel( + @AuthenticationPrincipal User issuer, + @PathVariable UUID uuid + ) { + final GameView canceledGameView = cordaClient.gameProposalCancel( + issuer.getHoldingIdentity(), + uuid + ); + + return ResponseEntity + .ok(canceledGameView); + } } \ No newline at end of file diff --git a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java index 5bfed8a..e9e8d6d 100644 --- a/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java +++ b/backend/src/main/java/djmil/cordacheckers/cordaclient/CordaClient.java @@ -134,7 +134,7 @@ public class CordaClient { public GameView gameProposalCancel(HoldingIdentity holdingIdentity, UUID gameUuid) { final RequestBody requestBody = new RequestBody( - "gp.reject-" +UUID.randomUUID(), + "gp.cancel-" +UUID.randomUUID(), "djmil.cordacheckers.gameproposal.CancelFlow", gameUuid); diff --git a/webapp/src/api/games.js b/webapp/src/api/games.js index aa099c4..6cfa0b8 100644 --- a/webapp/src/api/games.js +++ b/webapp/src/api/games.js @@ -1,4 +1,5 @@ import { usePolling, doPushing } from '../hook/api'; +import { gamesInitialState } from '../reducer/games'; export default function useGamesApi(gamesReducer, config) { const [games, dispatchGames] = gamesReducer; @@ -16,19 +17,23 @@ export default function useGamesApi(gamesReducer, config) { return games; } - return { pollGamesList: usePollingGamesList, - - pushNewGame: (reqParams) => doPushing('/api/gameproposal', 'POST', reqParams, { - onPushing: (isPushingNewGame) => dispatchGames({ type: 'next', isPushingNewGame }), - onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [game, ...games.gamesList], newGame: emptyNewGame }) - }), + + pushNewGame: ({ opponentName, opponentColor, board, message }) => ifNot(games.isPushingNewGame) && + doPushing('/api/gameproposal', 'POST', { opponentName, opponentColor, board, message }, { + onPushing: (isPushingNewGame) => dispatchGames({ type: 'next', isPushingNewGame }), + onSuccess: (game) => dispatchGames({ type: 'next', gamesList: [game, ...games.gamesList], newGame: gamesInitialState.newGame }) + }), + + pushGameProposalCancel: ({ uuid }) => ifNot(games.isPushingGameProposalCancel) && + doPushing(`/api/gameproposal/${uuid}/cancel`, 'PUT', null, { + onPushing: (isPushingGameProposalCancel) => dispatchGames({ type: 'next', isPushingGameProposalCancel }), + onSuccess: (canceledGame) => dispatchGames({ type: 'next', gamesList: games.nextGameList(canceledGame), proposal: gamesInitialState.proposal }) + }), } } -const emptyNewGame = { - whitePlayer: '', - blackPlayer: '', - message2opponent: '' +function ifNot(expression) { + return !expression; } \ No newline at end of file diff --git a/webapp/src/container/Games.jsx b/webapp/src/container/Games.jsx index aada860..804297e 100644 --- a/webapp/src/container/Games.jsx +++ b/webapp/src/container/Games.jsx @@ -93,10 +93,14 @@ function ActionPanel({ players, gamesApi }) { gamesApi.pushNewGame(reqParams)} + onClick={(reqParams) => gamesApi.pushNewGame(reqParams)} /> } /> - , , ]} /> + , + , + gamesApi.pushGameProposalCancel({ uuid })} /> + ]} /> , , ]} /> , ]} /> diff --git a/webapp/src/container/games/action/Cancel.jsx b/webapp/src/container/games/action/Cancel.jsx index 7686bf0..b66d6db 100644 --- a/webapp/src/container/games/action/Cancel.jsx +++ b/webapp/src/container/games/action/Cancel.jsx @@ -1,15 +1,16 @@ import React, { useContext } from 'react'; +import Wobler from '../../../components/Wobler'; import { GamesContext } from '../../../context/games'; -export default function Cancel() { +export default function Cancel({ onClick }) { const games = useContext(GamesContext); const selectedGame = games.findGame({ uuid: games.proposal.selectedUUID }); if (selectedGame?.status === 'GAME_PROPOSAL_WAIT_FOR_OPPONENT') return ( - ) } diff --git a/webapp/src/container/games/action/Create.jsx b/webapp/src/container/games/action/Create.jsx index 0416a97..1e91983 100644 --- a/webapp/src/container/games/action/Create.jsx +++ b/webapp/src/container/games/action/Create.jsx @@ -4,7 +4,7 @@ import Wobler from '../../../components/Wobler'; import { Color } from '../../../components/Checkers'; -export default function Create({ isCurrentUser, pushNewGame }) { +export default function Create({ isCurrentUser, onClick }) { const games = useContext(GamesContext); const hasPlayers = checkPlayers(games.newGame); @@ -17,9 +17,6 @@ export default function Create({ isCurrentUser, pushNewGame }) { if (!hasCurrentUser) return alert("You must be one of the selected players"); - if (games.isPushingNewGame) - return; // current request is still being processed - /* * Prepare & send NewGame request */ @@ -32,7 +29,7 @@ export default function Create({ isCurrentUser, pushNewGame }) { message: games.newGame.message2opponent } - pushNewGame(reqParams); + onClick(reqParams); } return ( diff --git a/webapp/src/hook/api.js b/webapp/src/hook/api.js index 28e759c..30fed93 100644 --- a/webapp/src/hook/api.js +++ b/webapp/src/hook/api.js @@ -72,11 +72,18 @@ export async function doPushing(uri, method, data, { onSuccess, onPushing }) { body: JSON.stringify(data), // body data type must match "Content-Type" header }); - if (!response.ok) - throw new Error(`Error! status: ${response.status}`); + if (!response.ok) { + return console.warn(`Unexpected response status: ${response.status}`, response); + } - if (onSuccess) - onSuccess(await response.json()); + if (onSuccess) { + var content = (response.headers.get('Content-Type') === "application/json") + ? await response.json() + : null; + + console.log("rsponce", response, "content", content); + onSuccess(content); + } // } catch (err) { } finally { if (onPushing) diff --git a/webapp/src/reducer/games.js b/webapp/src/reducer/games.js index 4b083c5..4b83cc3 100644 --- a/webapp/src/reducer/games.js +++ b/webapp/src/reducer/games.js @@ -1,7 +1,7 @@ import { useReducer } from 'react'; import { nextState } from '../util/StateHelper'; -const initialState = { +export const gamesInitialState = { gamesList: null, newGame: { @@ -18,8 +18,10 @@ const initialState = { // Network isPollingGamesList: false, isPushingNewGame: false, + isPushingGameProposalCancel: false, findGame, + nextGameList, }; function reducer(state, action) { @@ -46,9 +48,15 @@ function reducer(state, action) { } export default function useGamesReducer() { - return useReducer(reducer, initialState); + return useReducer(reducer, gamesInitialState); } -function findGame({uuid}) { +function findGame({ uuid }) { return this.gamesList?.find((game) => game.uuid === uuid); +} + +function nextGameList(nextGame) { + return this.gamesList?.map( + (game) => (nextGame?.uuid === game.uuid) ? nextGame : game + ); } \ No newline at end of file