refactor join_game_request to require unique users

This commit is contained in:
Sorrel Bri 2020-01-23 21:41:25 -08:00
parent 680bb41337
commit 4a960c784b
12 changed files with 191 additions and 16 deletions

View file

@ -3,12 +3,53 @@ import { Link } from 'react-router-dom';
import './Game.scss'; import './Game.scss';
const GameButton = (props) => { const GameButton = (props) => {
const gameData = props.game; const { game, dispatch } = props;
const requestJoinGame = () => {
console.log(`request to Join Game ${game.id}!`)
const requestAction = {
type: 'GAMES',
message: 'JOIN_REQUEST',
body: {id: game.id}
}
dispatch(requestAction);
}
const renderOpenGame = () => {
return (
<>
<a onClick={() => requestJoinGame()} >Request to Join Game</a>
<div className="Game__playerData">
<span className="Game__playerData__name">{game.playerBlack}</span>
<span className="Game__playerData__rank">{game.playerBlackRank}</span>
</div>
</>
)
}
const renderInProgressGame = () => {
const gameLinkText = game.winType ? 'Study Game' : 'Watch Game'
return (
<>
<Link to={`/games/${game.id}`}>{gameLinkText}</Link>
<div className="Game__playerData">
<span className="Game__playerData__name">{game.playerBlack}</span>
<span className="Game__playerData__rank">{game.playerBlackRank}</span>
</div>
<div className="Game__playerData">
<span className="Game__playerData__name">{game.playerWhite}</span>
<span className="Game__playerData__rank">{game.playerWhiteRank}</span>
</div>
</>
)
}
return ( return (
<div className="GameButton" data-testid="GameButton"> <div className="GameButton" data-testid="GameButton">
<Link to={`/games/${gameData.id}`}>View Game</Link> {game.open ? renderOpenGame() : renderInProgressGame()}
<p>{gameData.playerBlack} - {gameData.playerBlackRank}</p>
<p>{gameData.playerWhite} - {gameData.playerWhiteRank}</p>
</div> </div>
); );
} }

View file

@ -0,0 +1,5 @@
div.Game__playerData {
width: 100%;
display: flex;
justify-content: space-between;
}

View file

@ -0,0 +1,13 @@
import React from 'react';
import './ActionError.scss';
const ActionError = (props) => {
const errorMessage = props.error;
return (
<span data-testid="ActionError" className="ActionError">
{errorMessage}
</span>
);
}
export default ActionError;

View file

@ -0,0 +1,5 @@
@import '../../../../public/stylesheets/partials/variables';
span.FormError {
color: map-get($colors, "error");;
}

View file

@ -0,0 +1,17 @@
import React from 'react';
import { render } from '@testing-library/react';
import ActionError from './ActionError';
test('renders ActionError without crashing', () => {
const { getByTestId } = render(<ActionError />);
const ActionErrorSpan = getByTestId('ActionError');
expect(ActionErrorSpan).toBeInTheDocument();
});
test('renders ActionError with error message', () => {
const errorMessage = "User already exists!";
const { getByTestId } = render(<ActionError error={errorMessage}/>);
const ActionErrorSpan = getByTestId('ActionError');
expect(ActionErrorSpan).toHaveTextContent(errorMessage);
})

View file

@ -6,6 +6,7 @@ import config from '../../config';
import roomsServices from '../../services/api/roomsServices'; import roomsServices from '../../services/api/roomsServices';
import GameButton from '../../components/Button/Game/Game'; import GameButton from '../../components/Button/Game/Game';
import Message from '../../components/Display/Message/Message'; import Message from '../../components/Display/Message/Message';
import ActionError from '../../components/Error/ActionError/ActionError';
import Development from '../../components/Display/Development/Development'; import Development from '../../components/Display/Development/Development';
@ -13,8 +14,7 @@ const Room = (props) => {
const state = props.state; const state = props.state;
const dispatch = props.dispatch; const dispatch = props.dispatch;
const roomId = parseInt(useParams().id) || 0; const roomId = parseInt(useParams().id) || 0;
const [ socketData, setSocketData ] = useState(); const [ socketData, setSocketData ] = useState(false);
const [ messages, setMessages ] = useState();
const fetchRoomAPI = async () => { const fetchRoomAPI = async () => {
const response = await roomsServices.getRoomService(roomId); const response = await roomsServices.getRoomService(roomId);
@ -38,15 +38,36 @@ const Room = (props) => {
const roomSocketConnect = () => { const roomSocketConnect = () => {
roomSocket.emit('connect'); roomSocket.emit('connect');
// ! dispatch data // ! dispatch data
roomSocket.on('connected', data => setSocketData('room socket connected')); roomSocket.on('connect', socket => {
roomSocket.on('connect_error', err => console.log(err)); setSocketData(true)
roomSocket.on('error', err => console.log(err)); });
roomSocket.on('join_game_request', data => {
console.log(data)
})
roomSocket.on('connect_error', err => {
setSocketData(false)
console.log(err);
});
roomSocket.on('error', err => {
setSocketData(false)
console.log(err);
});
} }
useEffect(() => { useEffect(() => {
roomSocketConnect(); roomSocketConnect();
}, []) }, [])
useEffect(() => {
const data = {
user: state.user,
game: state.joinGame
};
console.log('emitting request')
console.log(data)
roomSocket.emit('join_game_request', data)
}, [state.joinGame])
// ! [end] // ! [end]
const renderGames = () => { const renderGames = () => {
@ -56,6 +77,7 @@ const Room = (props) => {
<GameButton <GameButton
key={`game-${gameData.id}`} key={`game-${gameData.id}`}
game={gameData} game={gameData}
dispatch={dispatch}
/> />
)) ))
} }
@ -78,7 +100,11 @@ const Room = (props) => {
return ( return (
<div className="Room" data-testid="Room"> <div className="Room" data-testid="Room">
<h2>{state.currentRoom ? state.currentRoom.name : 'Loading'}</h2> <div className="Room__heading">
<h2>{state.currentRoom ? state.currentRoom.name : 'Loading'}</h2>
<span className="Room__connection">{socketData ? '✓' : ' ⃠'}</span>
{state.errors.joinGame ? <ActionError error={state.errors.joinGame}/> : <></>}
</div>
<div className="Room__game-container"> <div className="Room__game-container">
{renderGames()} {renderGames()}

View file

@ -8,6 +8,9 @@ export const errorReducer = (state: state, action: action):state => {
case 'JOIN_ROOM_ERROR': case 'JOIN_ROOM_ERROR':
return joinRoomErrorReducer(state, action); return joinRoomErrorReducer(state, action);
case 'JOIN_GAME_ERROR':
return joinGameErrorReducer(state, action);
default: default:
return state; return state;
@ -22,4 +25,9 @@ function authErrorReducer(state: state, action: action): state {
function joinRoomErrorReducer(state: state, action: action): state { function joinRoomErrorReducer(state: state, action: action): state {
const joinRoom = action.body.joinRoomError; const joinRoom = action.body.joinRoomError;
return { ...state, errors: {joinRoom} } return { ...state, errors: {joinRoom} }
}
function joinGameErrorReducer(state: state, action: action): state {
const joinGame = action.body.joinGameError;
return { ...state, errors: {joinGame} }
} }

View file

@ -6,11 +6,51 @@ export const gamesReducer = (state: state, action: action):state => {
switch(action.message) { switch(action.message) {
case 'SET_GAMES': case 'SET_GAMES':
const games = action.body; const games = formatGames(action);;
return {...state, games}; return {...state, games};
case 'JOIN_REQUEST':
if (!Object.entries(state.user).length) {
const errAction = {
type: 'ERR',
message: 'JOIN_GAME_ERROR',
body: {joinGameError: 'user not logged in'}
}
return stateReducer(state, errAction)
}
const id = action.body;
return {...state, joinGame: id};
default: default:
return state; return state;
} }
}
function parseRank(rank: string): string {
switch(rank[0]) {
case 'D':
return `${rank.slice(1)}${rank[0].toLowerCase()}`
case 'K':
return `${rank.slice(1)}${rank[0].toLowerCase()}`
case 'U':
return '?'
}
}
function formatGames(action: action): Array<{}> {
const games = [...action.body].map(game => {
if (game.playerBlackRank) {
game.playerBlackRank = parseRank(game.playerBlackRank)
}
if (game.playerWhiteRank) {
game.playerWhiteRank = parseRank(game.playerWhiteRank)
}
return game;
})
return games;
} }

View file

@ -5,6 +5,10 @@ import type { state } from '../stateReducer';
export const initState = (): state => { export const initState = (): state => {
return { return {
user: {}, user: {},
errors: {} errors: {},
currentRoom: {},
messages: {},
games: {},
joinGame: {}
}; };
} }

View file

@ -32,6 +32,7 @@ const getRoomService = async (roomIndex) => {
delete Object.assign(game, {playerBlackRank: game.player_black_rank }).player_black_rank; delete Object.assign(game, {playerBlackRank: game.player_black_rank }).player_black_rank;
delete Object.assign(game, {playerWhite: game.player_white }).player_white; delete Object.assign(game, {playerWhite: game.player_white }).player_white;
delete Object.assign(game, {playerWhiteRank: game.player_white_rank }).player_white_rank; delete Object.assign(game, {playerWhiteRank: game.player_white_rank }).player_white_rank;
delete Object.assign(game, {winType: game.win_type }).win_type;
return game; return game;
}) })
return obj; return obj;

View file

@ -18,6 +18,8 @@ const getAll = async (req, res, next) => {
const show = async (req, res, next) => { const show = async (req, res, next) => {
try { try {
const roomId = req.params.id; const roomId = req.params.id;
if (!roomId) throw('missing room parameter')
// TODO eventually add check for user's private rooms // TODO eventually add check for user's private rooms
enableRoomSocket(roomId); enableRoomSocket(roomId);

View file

@ -11,12 +11,18 @@ io.on('connection', ()=> {
enableRoomSocket = (roomId) => { enableRoomSocket = (roomId) => {
const roomSocket = io.of(roomId); const roomSocket = io.of(roomId);
roomSocket.on('connection', (socket) => { roomSocket.on('connection', (socket) => {
// socket.emit('connected');
console.log(`Socket connected at room ${roomId}`); //! Join Game Request queries db for game, ensures unique player joining
socket.on('join_game_request', async data => { socket.on('join_game_request', async data => {
const gameRequest = await logJoinGameRequest(data); const gameRequest = await logJoinGameRequest(data);
if (gameRequest.err) {
roomSocket.emit('join_game_request_error', gameRequest.err);
}
roomSocket.emit('join_game_request', gameRequest); roomSocket.emit('join_game_request', gameRequest);
}) });
}); });
return roomSocket; return roomSocket;
} }
@ -29,5 +35,12 @@ module.exports = {
async function logJoinGameRequest (data) { async function logJoinGameRequest (data) {
const {user, game} = data; const {user, game} = data;
const requestedGame = await gameQueries.findGameById(game.id); const requestedGame = await gameQueries.findGameById(game.id);
return { user, requestedGame }
if (requestedGame.user_black === user.id) {
return { err: 'players must be unique' }
}
const requestingUser = {...user};
delete requestingUser.email;
return { requestingUser, requestedGame }
} }