fel kesz game backend
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Test1755691733404 implements MigrationInterface {
|
||||
name = 'Test1755691733404'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "Users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "orgid" uuid, "username" character varying(100) NOT NULL, "password" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "fname" character varying(100) NOT NULL, "lname" character varying(100) NOT NULL, "code" character varying(50), "type" character varying(50) NOT NULL, "phone" character varying(20), "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "Orglogindate" TIMESTAMP, CONSTRAINT "UQ_ffc81a3b97dcbf8e320d5106c0d" UNIQUE ("username"), CONSTRAINT "UQ_3c3ab3f49a87e6ddb607f3c4945" UNIQUE ("email"), CONSTRAINT "PK_16d4f7d636df336db11d87413e3" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Organizations" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "contactfname" character varying(100) NOT NULL, "contactlname" character varying(100) NOT NULL, "contactphone" character varying(20) NOT NULL, "contactemail" character varying(255) NOT NULL, "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "url" character varying(500), "userinorg" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_e0690a31419f6666194423526f2" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Decks" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "type" integer NOT NULL, "user_id" uuid NOT NULL, "creation_date" TIMESTAMP NOT NULL DEFAULT now(), "cards" json NOT NULL, "played_number" integer NOT NULL DEFAULT '0', "ctype" integer NOT NULL DEFAULT '0', "update_date" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', "organization_id" uuid, CONSTRAINT "PK_001f26cb3ec39c1f25269943473" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Chats" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "users" uuid array NOT NULL, "messages" json NOT NULL, "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', CONSTRAINT "PK_64c36c2b8d86a0d5de4cf64de8d" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "Decks" ADD CONSTRAINT "FK_06ee28f90d68543a03b14aebe13" FOREIGN KEY ("organization_id") REFERENCES "Organizations"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "Decks" DROP CONSTRAINT "FK_06ee28f90d68543a03b14aebe13"`);
|
||||
await queryRunner.query(`DROP TABLE "Chats"`);
|
||||
await queryRunner.query(`DROP TABLE "Decks"`);
|
||||
await queryRunner.query(`DROP TABLE "Organizations"`);
|
||||
await queryRunner.query(`DROP TABLE "Users"`);
|
||||
}
|
||||
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddEmailVerificationFields1755706019351 implements MigrationInterface {
|
||||
name = 'AddEmailVerificationFields1755706019351'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "code"`);
|
||||
await queryRunner.query(`ALTER TABLE "Users" ADD "token" character varying(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "Users" ADD "TokenExpires" TIMESTAMP`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "TokenExpires"`);
|
||||
await queryRunner.query(`ALTER TABLE "Users" DROP COLUMN "token"`);
|
||||
await queryRunner.query(`ALTER TABLE "Users" ADD "code" character varying(50)`);
|
||||
}
|
||||
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddChatMessagingSystem1755817306222 implements MigrationInterface {
|
||||
name = 'AddChatMessagingSystem1755817306222'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "ChatArchives" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "chatId" uuid NOT NULL, "archivedMessages" json NOT NULL, "archivedAt" TIMESTAMP NOT NULL, "createDate" TIMESTAMP NOT NULL DEFAULT now(), "chatType" character varying(50) NOT NULL, "chatName" character varying(255), "gameId" uuid, "participants" uuid array NOT NULL, CONSTRAINT "PK_fe62979fc2061d7afe278d3f14e" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "type" character varying(50) NOT NULL DEFAULT 'direct'`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "name" character varying(255)`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "gameId" uuid`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "createdBy" uuid`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "lastActivity" TIMESTAMP`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "createDate" TIMESTAMP NOT NULL DEFAULT now()`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ADD "archiveDate" TIMESTAMP`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ALTER COLUMN "messages" SET DEFAULT '[]'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "Chats" ALTER COLUMN "messages" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "archiveDate"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "createDate"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "lastActivity"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "createdBy"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "gameId"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "name"`);
|
||||
await queryRunner.query(`ALTER TABLE "Chats" DROP COLUMN "type"`);
|
||||
await queryRunner.query(`DROP TABLE "ChatArchives"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreateContactTable1755855028839 implements MigrationInterface {
|
||||
name = 'CreateContactTable1755855028839'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "Contacts" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "userid" uuid, "type" integer NOT NULL, "txt" text NOT NULL, "state" integer NOT NULL DEFAULT '0', "createDate" TIMESTAMP NOT NULL DEFAULT now(), "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "adminResponse" text, "responseDate" TIMESTAMP, "respondedBy" uuid, CONSTRAINT "PK_68782cec65c8eef577c62958273" PRIMARY KEY ("id"))`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "Contacts"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Full1757939815984 implements MigrationInterface {
|
||||
name = 'Full1757939815984'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "Chats" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "type" character varying(50) NOT NULL DEFAULT 'direct', "name" character varying(255), "gameId" uuid, "createdBy" uuid, "users" uuid array NOT NULL, "messages" json NOT NULL DEFAULT '[]', "lastActivity" TIMESTAMP, "createDate" TIMESTAMP NOT NULL DEFAULT now(), "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', "archiveDate" TIMESTAMP, CONSTRAINT "PK_64c36c2b8d86a0d5de4cf64de8d" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "orgid" uuid, "username" character varying(100) NOT NULL, "password" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "fname" character varying(100) NOT NULL, "lname" character varying(100) NOT NULL, "token" character varying(255), "TokenExpires" TIMESTAMP, "phone" character varying(20), "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "Orglogindate" TIMESTAMP, CONSTRAINT "UQ_ffc81a3b97dcbf8e320d5106c0d" UNIQUE ("username"), CONSTRAINT "UQ_3c3ab3f49a87e6ddb607f3c4945" UNIQUE ("email"), CONSTRAINT "PK_16d4f7d636df336db11d87413e3" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Contacts" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "email" character varying(255) NOT NULL, "userid" uuid, "type" integer NOT NULL, "txt" text NOT NULL, "state" integer NOT NULL DEFAULT '0', "createDate" TIMESTAMP NOT NULL DEFAULT now(), "updateDate" TIMESTAMP NOT NULL DEFAULT now(), "adminResponse" text, "responseDate" TIMESTAMP, "respondedBy" uuid, CONSTRAINT "PK_68782cec65c8eef577c62958273" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "ChatArchives" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "chatId" uuid NOT NULL, "archivedMessages" json NOT NULL, "archivedAt" TIMESTAMP NOT NULL, "createDate" TIMESTAMP NOT NULL DEFAULT now(), "chatType" character varying(50) NOT NULL, "chatName" character varying(255), "gameId" uuid, "participants" uuid array NOT NULL, CONSTRAINT "PK_fe62979fc2061d7afe278d3f14e" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Games" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "gamecode" character varying(10) NOT NULL, "maxplayers" integer NOT NULL, "logintype" integer NOT NULL DEFAULT '0', "createdby" character varying(255), "orgid" character varying(255), "gamedecks" json NOT NULL, "players" json NOT NULL DEFAULT '[]', "started" boolean NOT NULL DEFAULT false, "finished" boolean NOT NULL DEFAULT false, "winner" character varying(255), "state" integer NOT NULL DEFAULT '0', "create_date" TIMESTAMP NOT NULL DEFAULT now(), "start_date" TIMESTAMP, "end_date" TIMESTAMP, "update_date" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_9d52c646079cbe6f242a85c5c41" UNIQUE ("gamecode"), CONSTRAINT "PK_1950492f583d31609c5e9fbbe12" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Organizations" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "contactfname" character varying(100) NOT NULL, "contactlname" character varying(100) NOT NULL, "contactphone" character varying(20) NOT NULL, "contactemail" character varying(255) NOT NULL, "state" integer NOT NULL DEFAULT '0', "regdate" TIMESTAMP NOT NULL DEFAULT now(), "updatedate" TIMESTAMP NOT NULL DEFAULT now(), "url" character varying(500), "userinorg" integer NOT NULL DEFAULT '0', "maxOrganizationalDecks" integer, CONSTRAINT "PK_e0690a31419f6666194423526f2" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE TABLE "Decks" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "type" integer NOT NULL, "user_id" uuid NOT NULL, "creation_date" TIMESTAMP NOT NULL DEFAULT now(), "cards" json NOT NULL, "played_number" integer NOT NULL DEFAULT '0', "ctype" integer NOT NULL DEFAULT '0', "update_date" TIMESTAMP NOT NULL DEFAULT now(), "state" integer NOT NULL DEFAULT '0', "organization_id" uuid, CONSTRAINT "PK_001f26cb3ec39c1f25269943473" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "Decks" ADD CONSTRAINT "FK_06ee28f90d68543a03b14aebe13" FOREIGN KEY ("organization_id") REFERENCES "Organizations"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "Decks" DROP CONSTRAINT "FK_06ee28f90d68543a03b14aebe13"`);
|
||||
await queryRunner.query(`DROP TABLE "Decks"`);
|
||||
await queryRunner.query(`DROP TABLE "Organizations"`);
|
||||
await queryRunner.query(`DROP TABLE "Games"`);
|
||||
await queryRunner.query(`DROP TABLE "ChatArchives"`);
|
||||
await queryRunner.query(`DROP TABLE "Contacts"`);
|
||||
await queryRunner.query(`DROP TABLE "Users"`);
|
||||
await queryRunner.query(`DROP TABLE "Chats"`);
|
||||
}
|
||||
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
|
||||
|
||||
export class AddMaxOrganizationalDecksToOrganization1692712800000 implements MigrationInterface {
|
||||
name = 'AddMaxOrganizationalDecksToOrganization1692712800000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Add maxOrganizationalDecks column to Organizations table
|
||||
await queryRunner.addColumn('Organizations', new TableColumn({
|
||||
name: 'maxOrganizationalDecks',
|
||||
type: 'int',
|
||||
isNullable: true, // No default - set by admin
|
||||
comment: 'Maximum number of organizational decks a premium user can create in this organization'
|
||||
}));
|
||||
|
||||
// Add performance indexes for deck filtering queries
|
||||
await queryRunner.query(`CREATE INDEX "IDX_DECK_USER_STATE_CTYPE" ON "Decks" ("user_id", "state", "ctype")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_DECK_ORG_CTYPE_STATE" ON "Decks" ("organization_id", "ctype", "state")`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop indexes
|
||||
await queryRunner.query(`DROP INDEX "IDX_DECK_ORG_CTYPE_STATE"`);
|
||||
await queryRunner.query(`DROP INDEX "IDX_DECK_USER_STATE_CTYPE"`);
|
||||
|
||||
// Remove maxOrganizationalDecks column
|
||||
await queryRunner.dropColumn('Organizations', 'maxOrganizationalDecks');
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddEmailVerificationFields1755706017175 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class FixEmailVerificationFields1755706055220 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class Test1755691732089 implements MigrationInterface {
|
||||
export class Full1757939815062 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
}
|
||||
@@ -89,7 +89,7 @@ export class DeckRepository implements IDeckRepository {
|
||||
|
||||
async update(id: string, update: Partial<DeckAggregate>) {
|
||||
await this.repo.update(id, update);
|
||||
return this.findById(id);
|
||||
return this.findByIdIncludingDeleted(id);
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
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, started: boolean, finished?: boolean, winner?: string): Promise<GameAggregate | null> {
|
||||
const startTime = performance.now();
|
||||
try {
|
||||
const updateData: Partial<GameAggregate> = { started };
|
||||
|
||||
if (started && !finished) {
|
||||
updateData.state = GameState.ACTIVE;
|
||||
updateData.startdate = new Date();
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
updateData.finished = true;
|
||||
updateData.state = GameState.FINISHED;
|
||||
updateData.enddate = new Date();
|
||||
if (winner) {
|
||||
updateData.winner = winner;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.update(gameId, updateData);
|
||||
|
||||
const endTime = performance.now();
|
||||
logDatabase('Game state updated', `executionTime: ${Math.round(endTime - startTime)}ms, gameId: ${gameId}, started: ${started}, finished: ${finished}, 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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export const AppDataSource = new DataSource({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
username: process.env.DB_USERNAME || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'password',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
database: process.env.DB_NAME || 'serpentrace',
|
||||
synchronize: false, // Set to false when using migrations
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
|
||||
Reference in New Issue
Block a user