fel kesz game backend
This commit is contained in:
@@ -107,38 +107,6 @@ router.get('/users/page/:from/:to', adminRequired, async (req: Request, res: Res
|
||||
}
|
||||
});
|
||||
|
||||
// Get users by page (admin only) - RECOMMENDED
|
||||
router.get('/users/page/:from/:to', adminRequired, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const from = parseInt(req.params.from);
|
||||
const to = parseInt(req.params.to);
|
||||
const includeDeleted = req.query.includeDeleted === 'true';
|
||||
|
||||
if (isNaN(from) || isNaN(to) || from < 0 || to < from) {
|
||||
return res.status(400).json({ error: 'Invalid page parameters. "from" and "to" must be valid numbers with to >= from >= 0' });
|
||||
}
|
||||
|
||||
logRequest('Admin get users by page endpoint accessed', req, res, { from, to, includeDeleted });
|
||||
|
||||
const result = includeDeleted
|
||||
? await container.userRepository.findByPageIncludingDeleted(from, to)
|
||||
: await container.userRepository.findByPage(from, to);
|
||||
|
||||
logRequest('Admin users page retrieved successfully', req, res, {
|
||||
from,
|
||||
to,
|
||||
count: result.users.length,
|
||||
total: result.totalCount,
|
||||
includeDeleted
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logError('Admin get users by page endpoint error', error as Error, req, res);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get user by ID including soft-deleted ones
|
||||
router.get('/users/:userId',
|
||||
adminRequired,
|
||||
@@ -173,32 +141,32 @@ router.get('/users/:userId',
|
||||
});
|
||||
|
||||
// Search users including soft-deleted ones
|
||||
router.get('/users/search/:searchTerm',
|
||||
adminRequired,
|
||||
ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }),
|
||||
async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { searchTerm } = req.params;
|
||||
const includeDeleted = req.query.includeDeleted === 'true';
|
||||
// router.get('/users/search/:searchTerm',
|
||||
// adminRequired,
|
||||
// ValidationMiddleware.validateStringLength({ searchTerm: { min: 2, max: 100 } }),
|
||||
// async (req: Request, res: Response) => {
|
||||
// try {
|
||||
// const { searchTerm } = req.params;
|
||||
// const includeDeleted = req.query.includeDeleted === 'true';
|
||||
|
||||
logRequest('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted });
|
||||
// logRequest('Admin search users endpoint accessed', req, res, { searchTerm, includeDeleted });
|
||||
|
||||
const users = includeDeleted
|
||||
? await container.userRepository.searchIncludingDeleted(searchTerm)
|
||||
: await container.userRepository.search(searchTerm);
|
||||
// const users = includeDeleted
|
||||
// ? await container.userRepository.searchIncludingDeleted(searchTerm)
|
||||
// : await container.userRepository.search(searchTerm);
|
||||
|
||||
logRequest('Admin user search completed', req, res, {
|
||||
searchTerm,
|
||||
resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0),
|
||||
includeDeleted
|
||||
});
|
||||
// logRequest('Admin user search completed', req, res, {
|
||||
// searchTerm,
|
||||
// resultCount: Array.isArray(users) ? users.length : (users.totalCount || 0),
|
||||
// includeDeleted
|
||||
// });
|
||||
|
||||
res.json(users);
|
||||
} catch (error) {
|
||||
logError('Admin search users endpoint error', error as Error, req, res);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
// res.json(users);
|
||||
// } catch (error) {
|
||||
// logError('Admin search users endpoint error', error as Error, req, res);
|
||||
// res.status(500).json({ error: 'Internal server error' });
|
||||
// }
|
||||
// });
|
||||
|
||||
// Update any user (admin only)
|
||||
router.patch('/users/:userId',
|
||||
@@ -390,6 +358,30 @@ router.get('/decks/search/:searchTerm', adminRequired, async (req: Request, res:
|
||||
}
|
||||
});
|
||||
|
||||
//modify deck (admin only)
|
||||
router.patch('/decks/:id', adminRequired, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const deckId = req.params.id;
|
||||
const adminUserId = (req as any).user.userId;
|
||||
logRequest('Admin update deck endpoint accessed', req, res, { deckId, adminUserId, updateFields: Object.keys(req.body) });
|
||||
const result = await container.updateDeckCommandHandler.execute({ id: deckId, userstate: 1 , ...req.body});
|
||||
logRequest('Deck updated successfully by admin', req, res, { deckId, adminUserId });
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
logError('Admin update deck endpoint error', error as Error, req, res);
|
||||
if (error instanceof Error && error.message.includes('not found')) {
|
||||
return res.status(404).json({ error: `Deck not found` });
|
||||
}
|
||||
if (error instanceof Error && (error.message.includes('duplicate') || error.message.includes('unique constraint'))) {
|
||||
return res.status(409).json({ error: 'Deck with this name already exists' });
|
||||
}
|
||||
if (error instanceof Error && error.message.includes('validation')) {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: error.message });
|
||||
}
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Hard delete deck (admin only)
|
||||
router.delete('/decks/:id/hard', adminRequired, async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
@@ -60,7 +60,7 @@ deckRouter.post('/', authRequired, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).user.userId;
|
||||
logRequest('Create deck endpoint accessed', req, res, { name: req.body.name, userId });
|
||||
|
||||
req.body.userid = userId; // Set userId in request body
|
||||
const result = await container.createDeckCommandHandler.execute(req.body);
|
||||
|
||||
logRequest('Deck created successfully', req, res, { deckId: result.id, name: req.body.name, userId });
|
||||
@@ -140,7 +140,7 @@ deckRouter.get('/:id', authRequired, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
deckRouter.put('/:id', authRequired, async (req, res) => {
|
||||
deckRouter.patch('/:id', authRequired, async (req, res) => {
|
||||
try {
|
||||
const deckId = req.params.id;
|
||||
const userId = (req as any).user.userId;
|
||||
@@ -164,6 +164,10 @@ deckRouter.put('/:id', authRequired, async (req, res) => {
|
||||
if (error instanceof Error && error.message.includes('validation')) {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: error.message });
|
||||
}
|
||||
|
||||
if (error instanceof Error && error.message.includes('admin')) {
|
||||
return res.status(403).json({ error: 'Forbidden: ' + error.message });
|
||||
}
|
||||
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
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
|
||||
});
|
||||
|
||||
res.json(game);
|
||||
} 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;
|
||||
@@ -32,7 +32,7 @@ userRouter.post('/login',
|
||||
logAuth('User login successful', result.user.id, { username: result.user.username }, req, res);
|
||||
res.json(result);
|
||||
} else {
|
||||
return ErrorResponseService.sendUnauthorized(res, 'Invalid username or password');
|
||||
throw new Error(`Login failed: ${result}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -48,6 +48,9 @@ userRouter.post('/login',
|
||||
if (error.message.includes('not verified')) {
|
||||
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
|
||||
}
|
||||
if (error.message.includes('restriction')) {
|
||||
return ErrorResponseService.sendUnauthorized(res, 'Please verify your email address');
|
||||
}
|
||||
if (error.message.includes('deactivated')) {
|
||||
return ErrorResponseService.sendUnauthorized(res, 'Account has been deactivated');
|
||||
}
|
||||
@@ -84,7 +87,8 @@ userRouter.post('/create',
|
||||
res.status(201).json(result);
|
||||
|
||||
} catch (error) {
|
||||
logError('Create user endpoint error', error as Error, req, res);
|
||||
// Don't log here since CreateUserCommandHandler already logs system errors
|
||||
// Only log validation/user input errors at router level
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('already exists')) {
|
||||
@@ -93,6 +97,10 @@ userRouter.post('/create',
|
||||
if (error.message.includes('validation')) {
|
||||
return ErrorResponseService.sendBadRequest(res, error.message);
|
||||
}
|
||||
// Log unexpected errors that weren't handled by the command handler
|
||||
if (!error.message.includes('Failed to create user')) {
|
||||
logError('Unexpected create user endpoint error', error as Error, req, res);
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorResponseService.sendInternalServerError(res);
|
||||
@@ -165,4 +173,29 @@ userRouter.patch('/profile', authRequired, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
//Soft delete user (current user)
|
||||
userRouter.delete('/profile', authRequired, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).user.userId;
|
||||
const result = await container.deleteUserCommandHandler.execute({ id: userId, soft: true });
|
||||
logRequest('User soft deleted successfully', req, res, { userId });
|
||||
res.json({ success: result });
|
||||
} catch (error) {
|
||||
logError('Soft delete user endpoint error', error as Error, req, res);
|
||||
return ErrorResponseService.sendInternalServerError(res);
|
||||
}
|
||||
});
|
||||
|
||||
//logout user (current user)
|
||||
userRouter.post('/logout', authRequired, async (req, res) => {
|
||||
try {
|
||||
const userId = (req as any).user.userId;
|
||||
await container.logoutCommandHandler.execute(userId, res, req);
|
||||
logRequest('User logged out successfully', req, res, { userId });
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError('Logout user endpoint error', error as Error, req, res);
|
||||
return ErrorResponseService.sendInternalServerError(res);
|
||||
}
|
||||
});
|
||||
export default userRouter;
|
||||
|
||||
Reference in New Issue
Block a user