327 lines
13 KiB
TypeScript
327 lines
13 KiB
TypeScript
import { Router } from 'express';
|
|
import { authRequired } from '../../Application/Services/AuthMiddleware';
|
|
import { optionalAuth } from '../middleware/optionalAuth';
|
|
import { container } from '../../Application/Services/DIContainer';
|
|
import { ErrorResponseService } from '../../Application/Services/ErrorResponseService';
|
|
import { ValidationMiddleware } from '../../Application/Services/ValidationMiddleware';
|
|
import { logRequest, logError, logWarning } from '../../Application/Services/Logger';
|
|
import { LoginType } from '../../Domain/Game/GameAggregate';
|
|
|
|
const gameRouter = Router();
|
|
|
|
gameRouter.post('/start', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
const orgId = (req as any).user.orgId;
|
|
const { deckids, maxplayers, logintype } = req.body;
|
|
|
|
logRequest('Start game endpoint accessed', req, res, {
|
|
userId,
|
|
orgId,
|
|
deckCount: deckids?.length,
|
|
maxplayers,
|
|
logintype
|
|
});
|
|
|
|
// Validate required fields
|
|
if (!deckids || !Array.isArray(deckids) || deckids.length === 0) {
|
|
return res.status(400).json({ error: 'deckids is required and must be a non-empty array' });
|
|
}
|
|
|
|
if (!maxplayers || typeof maxplayers !== 'number') {
|
|
return res.status(400).json({ error: 'maxplayers is required and must be a number' });
|
|
}
|
|
|
|
if (logintype === undefined || typeof logintype !== 'number') {
|
|
return res.status(400).json({ error: 'logintype is required and must be a number (0=PUBLIC, 1=PRIVATE, 2=ORGANIZATION)' });
|
|
}
|
|
|
|
// Start the game using the GameService
|
|
const game = await container.gameService.startGame(
|
|
deckids,
|
|
maxplayers,
|
|
logintype as LoginType,
|
|
userId,
|
|
orgId
|
|
);
|
|
|
|
logRequest('Game started successfully', req, res, {
|
|
userId,
|
|
gameId: game.id,
|
|
gameCode: game.gamecode,
|
|
deckCount: game.gamedecks.length,
|
|
totalCards: game.gamedecks.reduce((sum, deck) => sum + deck.cards.length, 0)
|
|
});
|
|
|
|
res.json(game);
|
|
} catch (error) {
|
|
logError('Start game endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('not found')) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('validation') ||
|
|
error.message.includes('must be') ||
|
|
error.message.includes('required') ||
|
|
error.message.includes('Invalid')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
}
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
gameRouter.post('/join', optionalAuth, async (req, res) => {
|
|
try {
|
|
const user = (req as any).user;
|
|
const { gameCode, playerName } = req.body;
|
|
|
|
logRequest('Join game endpoint accessed', req, res, {
|
|
gameCode,
|
|
playerName,
|
|
hasAuth: !!user,
|
|
userId: user?.userId,
|
|
orgId: user?.orgId
|
|
});
|
|
|
|
// Validate required fields
|
|
if (!gameCode || typeof gameCode !== 'string') {
|
|
return res.status(400).json({ error: 'gameCode is required and must be a string' });
|
|
}
|
|
|
|
if (gameCode.length !== 6) {
|
|
return res.status(400).json({ error: 'gameCode must be exactly 6 characters long' });
|
|
}
|
|
|
|
// First, we need to find the game to determine its type
|
|
const gameRepository = container.gameRepository;
|
|
const gameToJoin = await gameRepository.findByGameCode(gameCode);
|
|
|
|
if (!gameToJoin) {
|
|
return res.status(404).json({ error: 'Game not found' });
|
|
}
|
|
|
|
// Determine join requirements based on game login type
|
|
let actualPlayerId: string | undefined;
|
|
let actualPlayerName: string | undefined;
|
|
let actualOrgId: string | null = null;
|
|
|
|
switch (gameToJoin.logintype) {
|
|
case LoginType.PUBLIC:
|
|
// Public games: playerName required, authentication optional
|
|
// If user is logged in and no playerName provided, use their username
|
|
if (!playerName || typeof playerName !== 'string' || !playerName.trim()) {
|
|
if (user && user.userId) {
|
|
// User is logged in, fetch their username to use as playerName
|
|
try {
|
|
const userDetails = await container.getUserByIdQueryHandler.execute({ id: user.userId });
|
|
if (userDetails && userDetails.username) {
|
|
actualPlayerName = userDetails.username;
|
|
logRequest('Using logged-in user\'s username as playerName', req, res, {
|
|
userId: user.userId,
|
|
username: userDetails.username
|
|
});
|
|
} else {
|
|
return res.status(400).json({
|
|
error: 'playerName is required for public games'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logError('Failed to fetch user details for playerName', error as Error, req, res);
|
|
return res.status(400).json({
|
|
error: 'playerName is required for public games'
|
|
});
|
|
}
|
|
} else {
|
|
// User is not logged in, playerName is required
|
|
return res.status(400).json({
|
|
error: 'playerName is required for public games'
|
|
});
|
|
}
|
|
} else {
|
|
// playerName was provided, use it
|
|
actualPlayerName = playerName.trim();
|
|
}
|
|
actualPlayerId = user?.userId; // Use authenticated user ID if available, otherwise undefined
|
|
break;
|
|
|
|
case LoginType.PRIVATE:
|
|
// Private games: authentication required
|
|
if (!user || !user.userId) {
|
|
return res.status(401).json({
|
|
error: 'Authentication required to join private games'
|
|
});
|
|
}
|
|
actualPlayerId = user.userId;
|
|
actualPlayerName = playerName;
|
|
break;
|
|
|
|
case LoginType.ORGANIZATION:
|
|
// Organization games: authentication + organization membership required
|
|
if (!user || !user.userId) {
|
|
return res.status(401).json({
|
|
error: 'Authentication required to join organization games'
|
|
});
|
|
}
|
|
|
|
if (!user.orgId) {
|
|
return res.status(403).json({
|
|
error: 'Organization membership required to join organization games'
|
|
});
|
|
}
|
|
|
|
if (gameToJoin.orgid && user.orgId !== gameToJoin.orgid) {
|
|
return res.status(403).json({
|
|
error: 'You must be a member of the same organization to join this game'
|
|
});
|
|
}
|
|
|
|
actualPlayerId = user.userId;
|
|
actualPlayerName = playerName;
|
|
actualOrgId = user.orgId;
|
|
break;
|
|
|
|
default:
|
|
return res.status(400).json({ error: 'Invalid game type' });
|
|
}
|
|
|
|
// Join the game using the GameService with determined parameters
|
|
const game = await container.gameService.joinGame(
|
|
gameCode,
|
|
actualPlayerId,
|
|
actualPlayerName,
|
|
actualOrgId,
|
|
gameToJoin.logintype
|
|
);
|
|
|
|
logRequest('Player joined game successfully', req, res, {
|
|
userId: actualPlayerId || 'anonymous',
|
|
gameId: game.id,
|
|
gameCode: game.gamecode,
|
|
gameType: LoginType[gameToJoin.logintype],
|
|
playerCount: game.players.length,
|
|
maxPlayers: game.maxplayers,
|
|
playerName: actualPlayerName
|
|
});
|
|
|
|
// Create game token for WebSocket authentication
|
|
const gameTokenService = container.gameTokenService;
|
|
const gameToken = gameTokenService.createGameToken(
|
|
game.id,
|
|
game.gamecode,
|
|
actualPlayerName || 'Anonymous',
|
|
actualPlayerId
|
|
);
|
|
|
|
// Return clean response with essential data + game token
|
|
res.json({
|
|
id: game.id,
|
|
gamecode: game.gamecode,
|
|
playerName: actualPlayerName,
|
|
playerCount: game.players.length,
|
|
maxPlayers: game.maxplayers,
|
|
gameType: LoginType[gameToJoin.logintype],
|
|
isAuthenticated: !!actualPlayerId,
|
|
gameToken: gameToken
|
|
});
|
|
} catch (error) {
|
|
logError('Join game endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('not found')) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('Authentication required')) {
|
|
return res.status(401).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('Organization') || error.message.includes('organization')) {
|
|
return res.status(403).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('full') ||
|
|
error.message.includes('already in') ||
|
|
error.message.includes('not accepting')) {
|
|
return res.status(409).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('validation') ||
|
|
error.message.includes('must be') ||
|
|
error.message.includes('required') ||
|
|
error.message.includes('Invalid')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
}
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
gameRouter.post('/:gameId/start', authRequired, async (req, res) => {
|
|
try {
|
|
const userId = (req as any).user.userId;
|
|
const { gameId } = req.params;
|
|
|
|
logRequest('Start gameplay endpoint accessed', req, res, {
|
|
userId,
|
|
gameId
|
|
});
|
|
|
|
// Validate required fields
|
|
if (!gameId || typeof gameId !== 'string') {
|
|
return res.status(400).json({ error: 'gameId is required and must be a string' });
|
|
}
|
|
|
|
// Start the gameplay using the GameService
|
|
const result = await container.gameService.startGamePlay(gameId, userId);
|
|
|
|
logRequest('Game gameplay started successfully', req, res, {
|
|
userId,
|
|
gameId,
|
|
playerCount: result.game.players.length
|
|
});
|
|
|
|
res.json({
|
|
message: 'Game started successfully',
|
|
gameId: gameId,
|
|
playerCount: result.game.players.length,
|
|
game: result.game,
|
|
boardData: result.boardData
|
|
});
|
|
} catch (error) {
|
|
logError('Start gameplay endpoint error', error as Error, req, res);
|
|
|
|
if (error instanceof Error) {
|
|
if (error.message.includes('not found')) {
|
|
return res.status(404).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('Only') || error.message.includes('master')) {
|
|
return res.status(403).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('already started') ||
|
|
error.message.includes('not ready') ||
|
|
error.message.includes('minimum players') ||
|
|
error.message.includes('not in waiting state') ||
|
|
error.message.includes('cannot be started')) {
|
|
return res.status(409).json({ error: error.message });
|
|
}
|
|
if (error.message.includes('validation') ||
|
|
error.message.includes('must be') ||
|
|
error.message.includes('required') ||
|
|
error.message.includes('Invalid')) {
|
|
return res.status(400).json({ error: error.message });
|
|
}
|
|
// Board generation specific errors
|
|
if (error.message.includes('Board generation') ||
|
|
error.message.includes('board not found') ||
|
|
error.message.includes('BoardGenerationService') ||
|
|
error.message.includes('Failed to wait for board generation') ||
|
|
error.message.includes('board generation timeout')) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
}
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
export default gameRouter; |