416 lines
20 KiB
TypeScript
416 lines
20 KiB
TypeScript
import { Repository, Not, In } from 'typeorm';
|
|
import { AppDataSource } from '../ormconfig';
|
|
import { GameAggregate, GameState } from '../../Domain/Game/GameAggregate';
|
|
import { IGameRepository } from '../../Domain/IRepository/IGameRepository';
|
|
import { logDatabase, logError } from '../../Application/Services/Logger';
|
|
|
|
export class GameRepository implements IGameRepository {
|
|
private repo: Repository<GameAggregate>;
|
|
constructor() {
|
|
this.repo = AppDataSource.getRepository(GameAggregate);
|
|
}
|
|
|
|
async create(game: Partial<GameAggregate>): Promise<GameAggregate> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const result = await this.repo.save(game);
|
|
const endTime = performance.now();
|
|
logDatabase('Game created', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${result.id}, gameCode: ${result.gamecode}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game creation failed', `executionTime: ${Math.round(endTime - startTime)}ms`);
|
|
logError('GameRepository.create error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to create game in database');
|
|
}
|
|
}
|
|
|
|
async findByPage(from: number, to: number): Promise<{ games: GameAggregate[], totalCount: number }> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const limit = to - from + 1;
|
|
const offset = from;
|
|
|
|
// Get total count for pagination
|
|
const totalCount = await this.repo.count({
|
|
where: { state: Not(GameState.CANCELLED) }
|
|
});
|
|
|
|
// Get paginated results
|
|
const games = await this.repo.find({
|
|
where: { state: Not(GameState.CANCELLED) },
|
|
order: { updateDate: 'DESC' },
|
|
take: limit,
|
|
skip: offset
|
|
});
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Game page query completed', `executionTime: ${Math.round(endTime - startTime)}ms, found: ${games.length}, total: ${totalCount}, from: ${from}, to: ${to}`);
|
|
|
|
return { games, totalCount };
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game page query failed', `executionTime: ${Math.round(endTime - startTime)}ms, from: ${from}, to: ${to}`);
|
|
logError('GameRepository.findByPage error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to get games page from database');
|
|
}
|
|
}
|
|
|
|
async findByPageIncludingDeleted(from: number, to: number): Promise<{ games: GameAggregate[], totalCount: number }> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const limit = to - from + 1;
|
|
const offset = from;
|
|
|
|
// Get total count for pagination (including deleted)
|
|
const totalCount = await this.repo.count();
|
|
|
|
// Get paginated results (including deleted)
|
|
const games = await this.repo.find({
|
|
order: { updateDate: 'DESC' },
|
|
take: limit,
|
|
skip: offset
|
|
});
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Game page query (including deleted) completed', `executionTime: ${Math.round(endTime - startTime)}ms, found: ${games.length}, total: ${totalCount}, from: ${from}, to: ${to}`);
|
|
|
|
return { games, totalCount };
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game page query (including deleted) failed', `executionTime: ${Math.round(endTime - startTime)}ms, from: ${from}, to: ${to}`);
|
|
logError('GameRepository.findByPageIncludingDeleted error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to get games page (including deleted) from database');
|
|
}
|
|
}
|
|
|
|
async findById(id: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const result = await this.repo.findOne({
|
|
where: { id, state: Not(GameState.CANCELLED) }
|
|
});
|
|
const endTime = performance.now();
|
|
logDatabase('Game findById completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}, found: ${!!result}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game findById failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}`);
|
|
logError('GameRepository.findById error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find game by id in database');
|
|
}
|
|
}
|
|
|
|
async findByIdIncludingDeleted(id: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const result = await this.repo.findOne({
|
|
where: { id }
|
|
});
|
|
const endTime = performance.now();
|
|
logDatabase('Game findByIdIncludingDeleted completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}, found: ${!!result}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game findByIdIncludingDeleted failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}`);
|
|
logError('GameRepository.findByIdIncludingDeleted error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find game by id (including deleted) in database');
|
|
}
|
|
}
|
|
|
|
async findByGameCode(gamecode: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const result = await this.repo.findOne({
|
|
where: { gamecode, state: Not(GameState.CANCELLED) }
|
|
});
|
|
const endTime = performance.now();
|
|
logDatabase('Game findByGameCode completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameCode: ${gamecode}, found: ${!!result}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game findByGameCode failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameCode: ${gamecode}`);
|
|
logError('GameRepository.findByGameCode error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find game by game code in database');
|
|
}
|
|
}
|
|
|
|
async search(query: string, limit?: number, offset?: number): Promise<{ games: GameAggregate[], totalCount: number }> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const queryBuilder = this.repo.createQueryBuilder('game')
|
|
.where('game.state != :cancelledState', { cancelledState: GameState.CANCELLED })
|
|
.andWhere('(game.gamecode ILIKE :query)', { query: `%${query}%` });
|
|
|
|
// Get total count
|
|
const totalCount = await queryBuilder.getCount();
|
|
|
|
// Apply pagination if provided
|
|
if (limit !== undefined) {
|
|
queryBuilder.take(limit);
|
|
}
|
|
if (offset !== undefined) {
|
|
queryBuilder.skip(offset);
|
|
}
|
|
|
|
const games = await queryBuilder.orderBy('game.updateDate', 'DESC').getMany();
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Game search completed', `executionTime: ${Math.round(endTime - startTime)}ms, query: ${query}, found: ${games.length}, total: ${totalCount}`);
|
|
|
|
return { games, totalCount };
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game search failed', `executionTime: ${Math.round(endTime - startTime)}ms, query: ${query}`);
|
|
logError('GameRepository.search error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to search games in database');
|
|
}
|
|
}
|
|
|
|
async searchIncludingDeleted(query: string, limit?: number, offset?: number): Promise<{ games: GameAggregate[], totalCount: number }> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const queryBuilder = this.repo.createQueryBuilder('game')
|
|
.where('(game.gamecode ILIKE :query)', { query: `%${query}%` });
|
|
|
|
// Get total count
|
|
const totalCount = await queryBuilder.getCount();
|
|
|
|
// Apply pagination if provided
|
|
if (limit !== undefined) {
|
|
queryBuilder.take(limit);
|
|
}
|
|
if (offset !== undefined) {
|
|
queryBuilder.skip(offset);
|
|
}
|
|
|
|
const games = await queryBuilder.orderBy('game.updateDate', 'DESC').getMany();
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Game search (including deleted) completed', `executionTime: ${Math.round(endTime - startTime)}ms, query: ${query}, found: ${games.length}, total: ${totalCount}`);
|
|
|
|
return { games, totalCount };
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game search (including deleted) failed', `executionTime: ${Math.round(endTime - startTime)}ms, query: ${query}`);
|
|
logError('GameRepository.searchIncludingDeleted error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to search games (including deleted) in database');
|
|
}
|
|
}
|
|
|
|
async update(id: string, update: Partial<GameAggregate>): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
await this.repo.update(id, update);
|
|
const result = await this.findById(id);
|
|
const endTime = performance.now();
|
|
logDatabase('Game update completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}, updated: ${!!result}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game update failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}`);
|
|
logError('GameRepository.update error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to update game in database');
|
|
}
|
|
}
|
|
|
|
async delete(id: string): Promise<any> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const result = await this.repo.delete(id);
|
|
const endTime = performance.now();
|
|
logDatabase('Game delete completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}, affected: ${result.affected}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game delete failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}`);
|
|
logError('GameRepository.delete error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to delete game from database');
|
|
}
|
|
}
|
|
|
|
async softDelete(id: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
await this.repo.update(id, { state: GameState.CANCELLED });
|
|
const result = await this.findByIdIncludingDeleted(id);
|
|
const endTime = performance.now();
|
|
logDatabase('Game soft delete completed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}, updated: ${!!result}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game soft delete failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${id}`);
|
|
logError('GameRepository.softDelete error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to soft delete game in database');
|
|
}
|
|
}
|
|
|
|
// Game-specific methods
|
|
async findActiveGames(): Promise<GameAggregate[]> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const games = await this.repo.find({
|
|
where: { state: GameState.ACTIVE },
|
|
order: { updateDate: 'DESC' }
|
|
});
|
|
const endTime = performance.now();
|
|
logDatabase('Active games query completed', `executionTime: ${Math.round(endTime - startTime)}ms, found: ${games.length}`);
|
|
return games;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Active games query failed', `executionTime: ${Math.round(endTime - startTime)}ms`);
|
|
logError('GameRepository.findActiveGames error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find active games in database');
|
|
}
|
|
}
|
|
|
|
async findGamesByPlayer(playerId: string): Promise<GameAggregate[]> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const queryBuilder = this.repo.createQueryBuilder('game')
|
|
.where('game.state != :cancelledState', { cancelledState: GameState.CANCELLED })
|
|
.andWhere('JSON_CONTAINS(game.players, :playerId)', { playerId: `"${playerId}"` })
|
|
.orderBy('game.updateDate', 'DESC');
|
|
|
|
const games = await queryBuilder.getMany();
|
|
const endTime = performance.now();
|
|
logDatabase('Games by player query completed', `executionTime: ${Math.round(endTime - startTime)}ms, playerId: ${playerId}, found: ${games.length}`);
|
|
return games;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Games by player query failed', `executionTime: ${Math.round(endTime - startTime)}ms, playerId: ${playerId}`);
|
|
logError('GameRepository.findGamesByPlayer error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find games by player in database');
|
|
}
|
|
}
|
|
|
|
async findWaitingGames(): Promise<GameAggregate[]> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const games = await this.repo.find({
|
|
where: { state: GameState.WAITING },
|
|
order: { createdate: 'ASC' }
|
|
});
|
|
const endTime = performance.now();
|
|
logDatabase('Waiting games query completed', `executionTime: ${Math.round(endTime - startTime)}ms, found: ${games.length}`);
|
|
return games;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Waiting games query failed', `executionTime: ${Math.round(endTime - startTime)}ms`);
|
|
logError('GameRepository.findWaitingGames error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find waiting games in database');
|
|
}
|
|
}
|
|
|
|
async findFinishedGames(from?: number, to?: number): Promise<{ games: GameAggregate[], totalCount: number }> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const queryBuilder = this.repo.createQueryBuilder('game')
|
|
.where('game.state = :finishedState', { finishedState: GameState.FINISHED })
|
|
.orderBy('game.enddate', 'DESC');
|
|
|
|
// Get total count
|
|
const totalCount = await queryBuilder.getCount();
|
|
|
|
// Apply pagination if provided
|
|
if (from !== undefined && to !== undefined) {
|
|
const limit = to - from + 1;
|
|
const offset = from;
|
|
queryBuilder.take(limit).skip(offset);
|
|
}
|
|
|
|
const games = await queryBuilder.getMany();
|
|
const endTime = performance.now();
|
|
logDatabase('Finished games query completed', `executionTime: ${Math.round(endTime - startTime)}ms, found: ${games.length}, total: ${totalCount}`);
|
|
return { games, totalCount };
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Finished games query failed', `executionTime: ${Math.round(endTime - startTime)}ms`);
|
|
logError('GameRepository.findFinishedGames error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to find finished games in database');
|
|
}
|
|
}
|
|
|
|
async addPlayerToGame(gameId: string, playerId: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const game = await this.findById(gameId);
|
|
if (!game) {
|
|
return null;
|
|
}
|
|
|
|
// Check if player is already in the game
|
|
if (game.players.includes(playerId)) {
|
|
return game;
|
|
}
|
|
|
|
// Check if game is full
|
|
if (game.players.length >= game.maxplayers) {
|
|
throw new Error('Game is full');
|
|
}
|
|
|
|
const updatedPlayers = [...game.players, playerId];
|
|
const result = await this.update(gameId, { players: updatedPlayers });
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Player added to game', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, playerId: ${playerId}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Add player to game failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, playerId: ${playerId}`);
|
|
logError('GameRepository.addPlayerToGame error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to add player to game in database');
|
|
}
|
|
}
|
|
|
|
async removePlayerFromGame(gameId: string, playerId: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const game = await this.findById(gameId);
|
|
if (!game) {
|
|
return null;
|
|
}
|
|
|
|
const updatedPlayers = game.players.filter(id => id !== playerId);
|
|
const result = await this.update(gameId, { players: updatedPlayers });
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Player removed from game', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, playerId: ${playerId}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Remove player from game failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, playerId: ${playerId}`);
|
|
logError('GameRepository.removePlayerFromGame error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to remove player from game in database');
|
|
}
|
|
}
|
|
|
|
async updateGameState(gameId: string, state: GameState, winner?: string): Promise<GameAggregate | null> {
|
|
const startTime = performance.now();
|
|
try {
|
|
const updateData: Partial<GameAggregate> = { state };
|
|
|
|
if (state === GameState.ACTIVE) {
|
|
updateData.startdate = new Date();
|
|
}
|
|
|
|
if (state === GameState.FINISHED) {
|
|
updateData.enddate = new Date();
|
|
if (winner) {
|
|
updateData.winnerId = winner;
|
|
}
|
|
}
|
|
|
|
const result = await this.update(gameId, updateData);
|
|
|
|
const endTime = performance.now();
|
|
logDatabase('Game state updated', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, state: ${updateData.state}, winner: ${winner}`);
|
|
return result;
|
|
} catch (error) {
|
|
const endTime = performance.now();
|
|
logDatabase('Game state update failed', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}`);
|
|
logError('GameRepository.updateGameState error', error instanceof Error ? error : new Error(String(error)));
|
|
throw new Error('Failed to update game state in database');
|
|
}
|
|
}
|
|
} |