From b5c0b4addc92f6d7f912036b7f1c225582ada977 Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Tue, 20 May 2025 19:55:08 +0300 Subject: [PATCH 1/7] add release swagger tag to /leaderboard and /online-players endpoints --- src/leaderboard/leaderboard.controller.ts | 7 +++++-- src/onlinePlayers/onlinePlayers.controller.ts | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/leaderboard/leaderboard.controller.ts b/src/leaderboard/leaderboard.controller.ts index 5ea9831a0..efeb9293d 100644 --- a/src/leaderboard/leaderboard.controller.ts +++ b/src/leaderboard/leaderboard.controller.ts @@ -13,6 +13,7 @@ import { PlayerService } from '../player/player.service'; import { LeaderboardPlayerDto } from './dto/leaderboardPlayer.dto'; import ApiResponseDescription from '../common/swagger/response/ApiResponseDescription'; import ClanPositionDto from './dto/clanPosition.dto'; +import SwaggerTags from '../common/swagger/tags/SwaggerTags.decorator'; @Controller('leaderboard') export class LeaderboardController { @@ -21,12 +22,13 @@ export class LeaderboardController { private readonly playerService: PlayerService, ) {} + @SwaggerTags('Release on 01.06.2025', 'Leaderboard') /** * Get top players * * @remarks Leaderboard of players. Top Players are defined by the amount of points that he/she has. * - * Notice that the leaderboards data is updated once every 12 hours. + * Notice that the leaderboards data is updated once every 3h hours. */ @ApiResponseDescription({ success: { @@ -45,12 +47,13 @@ export class LeaderboardController { return this.leaderBoardService.getPlayerLeaderboard(query); } + @SwaggerTags('Release on 01.06.2025', 'Leaderboard') /** * Get top clans * * @remarks Leaderboard of clans. Top Clans are defined by the amount of points that each Clan has. * - * Notice that the leaderboards data is updated once every 12 hours. + * Notice that the leaderboards data is updated once every 3h hours. */ @ApiResponseDescription({ success: { diff --git a/src/onlinePlayers/onlinePlayers.controller.ts b/src/onlinePlayers/onlinePlayers.controller.ts index 9d46c0316..2d72697f1 100644 --- a/src/onlinePlayers/onlinePlayers.controller.ts +++ b/src/onlinePlayers/onlinePlayers.controller.ts @@ -7,11 +7,13 @@ import ApiResponseDescription from '../common/swagger/response/ApiResponseDescri import OnlinePlayerDto from './dto/onlinePlayer.dto'; import InformPlayerIsOnlineDto from './dto/InformPlayerIsOnline.dto'; import OnlinePlayerSearchQueryDto from './dto/OnlinePlayerSearchQuery.dto'; +import SwaggerTags from '../common/swagger/tags/SwaggerTags.decorator'; @Controller('online-players') export class OnlinePlayersController { constructor(private readonly onlinePlayersService: OnlinePlayersService) {} + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** * Inform the API if player is still online * @@ -34,6 +36,7 @@ export class OnlinePlayersController { }); } + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** * Inform the API if player is still online * From 55143771070dd61990eb9339f972eaf4aefe876f Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 14:00:52 +0300 Subject: [PATCH 2/7] make RedisServiceInMemory singleton for data persistence --- .../common/service/redis/mocks/RedisServiceInMemory.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/__tests__/common/service/redis/mocks/RedisServiceInMemory.ts b/src/__tests__/common/service/redis/mocks/RedisServiceInMemory.ts index 7bbf58b78..b7c0f4574 100644 --- a/src/__tests__/common/service/redis/mocks/RedisServiceInMemory.ts +++ b/src/__tests__/common/service/redis/mocks/RedisServiceInMemory.ts @@ -7,11 +7,17 @@ import IRedisService from '../../../../../common/service/redis/IRedisService'; */ @Injectable() export class RedisServiceInMemory implements IRedisService, OnModuleDestroy { + private static instance: RedisServiceInMemory; private readonly store = new Map< string, { value: string; expiresAt?: number } >(); + constructor() { + if (RedisServiceInMemory.instance) return RedisServiceInMemory.instance; + RedisServiceInMemory.instance = this; + } + private isDestroyed = false; async set(key: string, value: string, ttlS?: number): Promise { From 864ee2c99aba08722467065f530b89cdc05d22fe Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 14:09:28 +0300 Subject: [PATCH 3/7] add `queueNumber` field to online player if he/she has status BattleWait --- .../addPlayerOnline.test.ts | 51 ++++++++-- .../getOnlinePlayerById.test.ts | 59 +++++++++++ .../getPlayerQueueNumber.test.ts | 99 +++++++++++++++++++ .../onlinePlayers/AddOnlinePlayerBuilder.ts | 23 +++++ .../data/onlinePlayers/OnlinePlayerBuilder.ts | 35 +++++++ .../data/onlinePlayersBuilderFactory.ts | 22 +++++ .../modules/battleQueue.module.ts | 11 +++ .../modules/onlinePlayersCommon.module.ts | 3 +- .../battleQueue/battleQueue.controller.ts | 32 ++++++ .../battleQueue/battleQueue.service.ts | 46 +++++++++ src/onlinePlayers/dto/onlinePlayer.dto.ts | 10 +- src/onlinePlayers/onlinePlayers.module.ts | 7 +- src/onlinePlayers/onlinePlayers.service.ts | 40 ++++++++ src/onlinePlayers/payload/OnlinePlayer.ts | 7 +- .../additionalTypes/BattleWaitStatus.ts | 9 ++ 15 files changed, 443 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayerById.test.ts create mode 100644 src/__tests__/onlinePlayers/battleQueue/BattleQueueService/getPlayerQueueNumber.test.ts create mode 100644 src/__tests__/onlinePlayers/data/onlinePlayers/AddOnlinePlayerBuilder.ts create mode 100644 src/__tests__/onlinePlayers/data/onlinePlayers/OnlinePlayerBuilder.ts create mode 100644 src/__tests__/onlinePlayers/data/onlinePlayersBuilderFactory.ts create mode 100644 src/__tests__/onlinePlayers/modules/battleQueue.module.ts create mode 100644 src/onlinePlayers/battleQueue/battleQueue.controller.ts create mode 100644 src/onlinePlayers/battleQueue/battleQueue.service.ts create mode 100644 src/onlinePlayers/payload/additionalTypes/BattleWaitStatus.ts diff --git a/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts b/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts index 6b832f347..30e22ad62 100644 --- a/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts +++ b/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts @@ -6,6 +6,9 @@ import { CacheKeys } from '../../../common/service/redis/cacheKeys.enum'; import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStatus'; import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module'; import { RedisService } from '../../../common/service/redis/redis.service'; +import OnlinePlayersBuilderFactory from '../data/onlinePlayersBuilderFactory'; +import { OnlinePlayerBuilder } from '../data/onlinePlayers/OnlinePlayerBuilder'; +import { BattleWaitStatus } from '../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus'; describe('OnlinePlayersService.addPlayerOnline() test suite', () => { let service: OnlinePlayersService; @@ -18,6 +21,12 @@ describe('OnlinePlayersService.addPlayerOnline() test suite', () => { .setName('player1') .build(); + const addPlayerBuilder = + OnlinePlayersBuilderFactory.getBuilder('AddOnlinePlayer'); + const onlinePlayerBuilder = OnlinePlayersBuilderFactory.getBuilder( + 'OnlinePlayer', + ) as OnlinePlayerBuilder; + const playerModel = PlayerModule.getPlayerModel(); beforeEach(async () => { @@ -33,18 +42,48 @@ describe('OnlinePlayersService.addPlayerOnline() test suite', () => { }); it('Should be able to add one player to cache', async () => { + const playerToAdd = addPlayerBuilder + .setPlayerId(player1._id) + .setStatus(OnlinePlayerStatus.BATTLE) + .build(); + const expectedKey = `${CacheKeys.ONLINE_PLAYERS}:${player1._id}`; - const expectedPayload = JSON.stringify({ - _id: player1._id, - name: player1.name, - status: OnlinePlayerStatus.BATTLE, - }); + const expectedPayload = JSON.stringify( + onlinePlayerBuilder + .setId(playerToAdd.player_id) + .setName(player1.name) + .setStatus(playerToAdd.status) + .build(), + ); + + const redisSet = jest.spyOn(redisService, 'set'); + + await service.addPlayerOnline(playerToAdd); + + expect(redisSet).toHaveBeenCalledTimes(1); + expect(redisSet).toHaveBeenCalledWith(expectedKey, expectedPayload, 90); + }); + + it(`Should set queue number if player has status ${OnlinePlayerStatus.BATTLE_WAIT}`, async () => { + const playerToAdd = addPlayerBuilder + .setPlayerId(player1._id) + .setStatus(OnlinePlayerStatus.BATTLE_WAIT) + .build(); + const expectedKey = `${CacheKeys.ONLINE_PLAYERS}:${player1._id}`; + const expectedPayload = JSON.stringify( + onlinePlayerBuilder + .setId(playerToAdd.player_id) + .setName(player1.name) + .setStatus(playerToAdd.status) + .setAdditional({ queueNumber: 0 }) + .build(), + ); const redisSet = jest.spyOn(redisService, 'set'); await service.addPlayerOnline({ player_id: player1._id, - status: OnlinePlayerStatus.BATTLE, + status: OnlinePlayerStatus.BATTLE_WAIT, }); expect(redisSet).toHaveBeenCalledTimes(1); diff --git a/src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayerById.test.ts b/src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayerById.test.ts new file mode 100644 index 000000000..21c8e6876 --- /dev/null +++ b/src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayerById.test.ts @@ -0,0 +1,59 @@ +import { OnlinePlayersService } from '../../../onlinePlayers/onlinePlayers.service'; +import { RedisService } from '../../../common/service/redis/redis.service'; +import PlayerBuilderFactory from '../../player/data/playerBuilderFactory'; +import PlayerModule from '../../player/modules/player.module'; +import OnlinePlayersModule from '../modules/onlinePlayers.module'; +import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module'; +import OnlinePlayer from '../../../onlinePlayers/payload/OnlinePlayer'; +import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStatus'; + +describe('OnlinePlayersService.getOnlinePlayerById() test suite', () => { + let service: OnlinePlayersService; + + let redisService: RedisService; + + const playerBuilder = PlayerBuilderFactory.getBuilder('Player'); + const player1 = playerBuilder + .setUniqueIdentifier('player1') + .setName('player1') + .build(); + + const playerModel = PlayerModule.getPlayerModel(); + + beforeEach(async () => { + jest.clearAllMocks(); + service = await OnlinePlayersModule.getOnlinePlayersService(); + + const player1Resp = await playerModel.create(player1); + player1._id = player1Resp._id.toString(); + + redisService = (await OnlinePlayersCommonModule.getModule()).get( + RedisService, + ); + }); + + it('Should return player if it exists', async () => { + const existingPlayer: OnlinePlayer = { + _id: player1._id, + name: player1.name, + status: OnlinePlayerStatus.UI, + }; + jest + .spyOn(redisService, 'get') + .mockResolvedValue(JSON.stringify(existingPlayer)); + + const [player, errors] = await service.getOnlinePlayerById(player1._id); + + expect(errors).toBeNull(); + expect(player).toEqual(existingPlayer); + }); + + it('Should return ServiceError NOT_FOUND if player does not exists', async () => { + jest.spyOn(redisService, 'get').mockResolvedValue(null); + + const [player, errors] = await service.getOnlinePlayerById(player1._id); + + expect(player).toBeNull(); + expect(errors).toContainSE_NOT_FOUND(); + }); +}); diff --git a/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/getPlayerQueueNumber.test.ts b/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/getPlayerQueueNumber.test.ts new file mode 100644 index 000000000..d823e09b6 --- /dev/null +++ b/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/getPlayerQueueNumber.test.ts @@ -0,0 +1,99 @@ +import BattleQueueModule from '../../modules/battleQueue.module'; +import PlayerModule from '../../../player/modules/player.module'; +import PlayerBuilderFactory from '../../../player/data/playerBuilderFactory'; +import { BattleQueueService } from '../../../../onlinePlayers/battleQueue/battleQueue.service'; +import { OnlinePlayerStatus } from '../../../../onlinePlayers/enum/OnlinePlayerStatus'; +import OnlinePlayersBuilderFactory from '../../data/onlinePlayersBuilderFactory'; +import { OnlinePlayerBuilder } from '../../data/onlinePlayers/OnlinePlayerBuilder'; +import { BattleWaitStatus } from '../../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus'; + +describe('BattleQueueService.getPlayerQueueNumber() test suite', () => { + let service: BattleQueueService; + + const playerBuilder = PlayerBuilderFactory.getBuilder('Player'); + const player1 = playerBuilder + .setName('player1') + .setUniqueIdentifier('player1') + .build(); + const player2 = playerBuilder + .setName('player2') + .setUniqueIdentifier('player2') + .build(); + const player3 = playerBuilder + .setName('player3') + .setUniqueIdentifier('player3') + .build(); + const playerModel = PlayerModule.getPlayerModel(); + + const onlinePlayerBuilder = OnlinePlayersBuilderFactory.getBuilder( + 'OnlinePlayer', + ) as OnlinePlayerBuilder; + + beforeEach(async () => { + service = await BattleQueueModule.getBattleQueueService(); + + const createdPlayer1 = await playerModel.create(player1); + player1._id = createdPlayer1._id.toString(); + const createdPlayer2 = await playerModel.create(player2); + player2._id = createdPlayer2._id.toString(); + const createdPlayer3 = await playerModel.create(player3); + player3._id = createdPlayer3._id.toString(); + }); + + it('Should return increased by 1 order number for each player', async () => { + const onlinePlayer1 = onlinePlayerBuilder + .setId(player1._id) + .setName(player1.name) + .build(); + const onlinePlayer2 = onlinePlayerBuilder + .setId(player2._id) + .setName(player2.name) + .build(); + const onlinePlayer3 = onlinePlayerBuilder + .setId(player3._id) + .setName(player3.name) + .build(); + + const [number1, errors1] = + await service.getPlayerQueueNumber(onlinePlayer1); + const [number2, errors2] = + await service.getPlayerQueueNumber(onlinePlayer2); + const [number3, errors3] = + await service.getPlayerQueueNumber(onlinePlayer3); + + expect(errors1).toBeNull(); + expect(number1).toBe(0); + + expect(errors2).toBeNull(); + expect(number2).toBe(1); + + expect(errors3).toBeNull(); + expect(number3).toBe(2); + }); + + it('Should return the same number the player has if the player is already in queue', async () => { + const queueNumber = 0; + const onlinePlayer1 = onlinePlayerBuilder + .setId(player1._id) + .setName(player1.name) + .setStatus(OnlinePlayerStatus.BATTLE_WAIT) + .setAdditional({ queueNumber }) + .build(); + + const [number1, errors1] = + await service.getPlayerQueueNumber(onlinePlayer1); + const [number2, errors2] = + await service.getPlayerQueueNumber(onlinePlayer1); + const [number3, errors3] = + await service.getPlayerQueueNumber(onlinePlayer1); + + expect(errors1).toBeNull(); + expect(number1).toBe(queueNumber); + + expect(errors2).toBeNull(); + expect(number2).toBe(queueNumber); + + expect(errors3).toBeNull(); + expect(number3).toBe(queueNumber); + }); +}); diff --git a/src/__tests__/onlinePlayers/data/onlinePlayers/AddOnlinePlayerBuilder.ts b/src/__tests__/onlinePlayers/data/onlinePlayers/AddOnlinePlayerBuilder.ts new file mode 100644 index 000000000..d131f86fd --- /dev/null +++ b/src/__tests__/onlinePlayers/data/onlinePlayers/AddOnlinePlayerBuilder.ts @@ -0,0 +1,23 @@ +import AddOnlinePlayer from '../../../../onlinePlayers/payload/AddOnlinePlayer'; +import { OnlinePlayerStatus } from '../../../../onlinePlayers/enum/OnlinePlayerStatus'; + +export class AddOnlinePlayerBuilder { + private readonly base: Partial = { + player_id: undefined, + status: OnlinePlayerStatus.UI, + }; + + build(): AddOnlinePlayer { + return { ...this.base } as AddOnlinePlayer; + } + + setPlayerId(player_id: string): this { + this.base.player_id = player_id; + return this; + } + + setStatus(status: OnlinePlayerStatus): this { + this.base.status = status; + return this; + } +} diff --git a/src/__tests__/onlinePlayers/data/onlinePlayers/OnlinePlayerBuilder.ts b/src/__tests__/onlinePlayers/data/onlinePlayers/OnlinePlayerBuilder.ts new file mode 100644 index 000000000..470049f84 --- /dev/null +++ b/src/__tests__/onlinePlayers/data/onlinePlayers/OnlinePlayerBuilder.ts @@ -0,0 +1,35 @@ +import OnlinePlayer from '../../../../onlinePlayers/payload/OnlinePlayer'; +import { OnlinePlayerStatus } from '../../../../onlinePlayers/enum/OnlinePlayerStatus'; + +export class OnlinePlayerBuilder { + private readonly base: Partial> = { + _id: undefined, + name: 'player1', + status: OnlinePlayerStatus.UI, + additional: undefined, + }; + + build(): OnlinePlayer { + return { ...this.base } as OnlinePlayer; + } + + setId(id: string): this { + this.base._id = id; + return this; + } + + setName(name: string): this { + this.base.name = name; + return this; + } + + setStatus(status: OnlinePlayerStatus): this { + this.base.status = status; + return this; + } + + setAdditional(additional: Additional): this { + this.base.additional = additional; + return this; + } +} diff --git a/src/__tests__/onlinePlayers/data/onlinePlayersBuilderFactory.ts b/src/__tests__/onlinePlayers/data/onlinePlayersBuilderFactory.ts new file mode 100644 index 000000000..efd2d36ac --- /dev/null +++ b/src/__tests__/onlinePlayers/data/onlinePlayersBuilderFactory.ts @@ -0,0 +1,22 @@ +import { OnlinePlayerBuilder } from './onlinePlayers/OnlinePlayerBuilder'; +import { AddOnlinePlayerBuilder } from './onlinePlayers/AddOnlinePlayerBuilder'; + +type BuilderName = 'OnlinePlayer' | 'AddOnlinePlayer'; + +type BuilderMap = { + OnlinePlayer: OnlinePlayerBuilder; + AddOnlinePlayer: AddOnlinePlayerBuilder; +}; + +export default class OnlinePlayersBuilderFactory { + static getBuilder(builderName: T): BuilderMap[T] { + switch (builderName) { + case 'OnlinePlayer': + return new OnlinePlayerBuilder() as BuilderMap[T]; + case 'AddOnlinePlayer': + return new AddOnlinePlayerBuilder() as BuilderMap[T]; + default: + throw new Error(`Unknown builder name: ${builderName}`); + } + } +} diff --git a/src/__tests__/onlinePlayers/modules/battleQueue.module.ts b/src/__tests__/onlinePlayers/modules/battleQueue.module.ts new file mode 100644 index 000000000..1ba52f73d --- /dev/null +++ b/src/__tests__/onlinePlayers/modules/battleQueue.module.ts @@ -0,0 +1,11 @@ +import { BattleQueueService } from '../../../onlinePlayers/battleQueue/battleQueue.service'; +import OnlinePlayersCommonModule from './onlinePlayersCommon.module'; + +export default class BattleQueueModule { + private constructor() {} + + static async getBattleQueueService() { + const module = await OnlinePlayersCommonModule.getModule(); + return module.resolve(BattleQueueService); + } +} diff --git a/src/__tests__/onlinePlayers/modules/onlinePlayersCommon.module.ts b/src/__tests__/onlinePlayers/modules/onlinePlayersCommon.module.ts index 8eb40e1e2..d13cd3645 100644 --- a/src/__tests__/onlinePlayers/modules/onlinePlayersCommon.module.ts +++ b/src/__tests__/onlinePlayers/modules/onlinePlayersCommon.module.ts @@ -9,6 +9,7 @@ import { PlayerSchema } from '../../../player/schemas/player.schema'; import { RedisModule } from '../../../common/service/redis/redis.module'; import { RedisServiceInMemory } from '../../common/service/redis/mocks/RedisServiceInMemory'; import { RedisService } from '../../../common/service/redis/redis.service'; +import { BattleQueueService } from '../../../onlinePlayers/battleQueue/battleQueue.service'; export default class OnlinePlayersCommonModule { private static module: TestingModule; @@ -25,7 +26,7 @@ export default class OnlinePlayersCommonModule { RequestHelperModule, RedisModule, ], - providers: [OnlinePlayersService], + providers: [OnlinePlayersService, BattleQueueService], }) .overrideProvider(RedisService) .useClass(RedisServiceInMemory) diff --git a/src/onlinePlayers/battleQueue/battleQueue.controller.ts b/src/onlinePlayers/battleQueue/battleQueue.controller.ts new file mode 100644 index 000000000..a7ba2f819 --- /dev/null +++ b/src/onlinePlayers/battleQueue/battleQueue.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get } from '@nestjs/common'; +import { BattleQueueService } from './battleQueue.service'; +import ApiResponseDescription from 'src/common/swagger/response/ApiResponseDescription'; +import OnlinePlayerDto from '../dto/onlinePlayer.dto'; +import { UniformResponse } from '../../common/decorator/response/UniformResponse'; +import SwaggerTags from '../../common/swagger/tags/SwaggerTags.decorator'; + +@Controller('/online-players/battleQueue') +export class BattleQueueController { + constructor(private readonly service: BattleQueueService) {} + + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') + /** + * Get battle queue + * + * @remarks Returns a list of online players waiting to join the battle in a queue order, + * where the first player is the next to play + */ + @ApiResponseDescription({ + success: { + dto: OnlinePlayerDto, + returnsArray: true, + hasPagination: false, + }, + errors: [401, 404], + }) + @Get() + @UniformResponse(null, OnlinePlayerDto) + async getBattleQueue() { + return []; + } +} diff --git a/src/onlinePlayers/battleQueue/battleQueue.service.ts b/src/onlinePlayers/battleQueue/battleQueue.service.ts new file mode 100644 index 000000000..16d719aca --- /dev/null +++ b/src/onlinePlayers/battleQueue/battleQueue.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { IServiceReturn } from '../../common/service/basicService/IService'; +import OnlinePlayer from '../payload/OnlinePlayer'; +import { BattleWaitStatus } from '../payload/additionalTypes/BattleWaitStatus'; + +@Injectable() +export class BattleQueueService { + public constructor() {} + + /** + * Max number the queue number can become + * @private + */ + private readonly queueNumberMax = 9999; + + /** + * Auto-incremented positive integer number, which is used to determine queue position of a player + * @private + */ + private nextQueueNumber = 0; + + /** + * Gets a queue number for a player. + * + * Each time a player gets a new queue number, the queue number is increased by one until it becomes its max value. + * Then the number will be set to its max value, it will be reset and will be equal to zero again. + * Notice that if the player already in the queue, the number will not be updated + * + * @param player player for which the number is requested + */ + async getPlayerQueueNumber( + player: OnlinePlayer | null, + ): Promise> { + if (player && player.additional?.queueNumber != null) { + const existingQueueNumber = (player as OnlinePlayer) + .additional.queueNumber; + return [existingQueueNumber, null]; + } + + const playerQueueNumber = this.nextQueueNumber; + this.nextQueueNumber++; + if (this.nextQueueNumber > this.queueNumberMax) this.nextQueueNumber = 0; + + return [playerQueueNumber, null]; + } +} diff --git a/src/onlinePlayers/dto/onlinePlayer.dto.ts b/src/onlinePlayers/dto/onlinePlayer.dto.ts index 983bab67e..f56313651 100644 --- a/src/onlinePlayers/dto/onlinePlayer.dto.ts +++ b/src/onlinePlayers/dto/onlinePlayer.dto.ts @@ -1,7 +1,7 @@ import { Expose } from 'class-transformer'; import { OnlinePlayerStatus } from '../enum/OnlinePlayerStatus'; -export default class OnlinePlayerDto { +export default class OnlinePlayerDto { /** * _id of the player * @@ -25,4 +25,12 @@ export default class OnlinePlayerDto { */ @Expose() status: OnlinePlayerStatus; + + /** + * Any additional information online player has + * + * @example { queueNumber: 239 } + */ + @Expose() + additional?: Additional; } diff --git a/src/onlinePlayers/onlinePlayers.module.ts b/src/onlinePlayers/onlinePlayers.module.ts index 0342ca223..10781868c 100644 --- a/src/onlinePlayers/onlinePlayers.module.ts +++ b/src/onlinePlayers/onlinePlayers.module.ts @@ -3,10 +3,13 @@ import { OnlinePlayersService } from './onlinePlayers.service'; import { OnlinePlayersController } from './onlinePlayers.controller'; import { PlayerModule } from '../player/player.module'; import { RedisModule } from '../common/service/redis/redis.module'; +import { BattleQueueService } from './battleQueue/battleQueue.service'; +import { BattleQueueController } from './battleQueue/battleQueue.controller'; @Module({ imports: [PlayerModule, RedisModule], - providers: [OnlinePlayersService], - controllers: [OnlinePlayersController], + providers: [OnlinePlayersService, BattleQueueService], + controllers: [OnlinePlayersController, BattleQueueController], + exports: [OnlinePlayersService], }) export class OnlinePlayersModule {} diff --git a/src/onlinePlayers/onlinePlayers.service.ts b/src/onlinePlayers/onlinePlayers.service.ts index bd3063b0f..33aed4441 100644 --- a/src/onlinePlayers/onlinePlayers.service.ts +++ b/src/onlinePlayers/onlinePlayers.service.ts @@ -6,6 +6,10 @@ import { RedisService } from '../common/service/redis/redis.service'; import { OnlinePlayerStatus } from './enum/OnlinePlayerStatus'; import AddOnlinePlayer from './payload/AddOnlinePlayer'; import OnlinePlayer from './payload/OnlinePlayer'; +import ServiceError from '../common/service/basicService/ServiceError'; +import { SEReason } from '../common/service/basicService/SEReason'; +import { BattleWaitStatus } from './payload/additionalTypes/BattleWaitStatus'; +import { BattleQueueService } from './battleQueue/battleQueue.service'; @Injectable() export class OnlinePlayersService { @@ -19,6 +23,7 @@ export class OnlinePlayersService { constructor( private readonly redisService: RedisService, private readonly playerService: PlayerService, + private readonly battleQueueService: BattleQueueService, ) {} /** @@ -41,6 +46,13 @@ export class OnlinePlayersService { status: status ?? OnlinePlayerStatus.UI, }; + if (status === OnlinePlayerStatus.BATTLE_WAIT) { + const [onlinePlayer] = await this.getOnlinePlayerById(player_id); + const [queueNumber] = + await this.battleQueueService.getPlayerQueueNumber(onlinePlayer); + (payload as OnlinePlayer).additional = { queueNumber }; + } + await this.redisService.set( `${this.ONLINE_PLAYERS_KEY}:${player_id}`, JSON.stringify(payload), @@ -77,4 +89,32 @@ export class OnlinePlayersService { return onlinePlayers; } + + /** + * Gets online player by its _id. + * + * @param player_id player _id to be found + * + * @returns found online player or ServiceError NOT_FOUND if player is not found + */ + async getOnlinePlayerById( + player_id: string, + ): Promise> { + const player = await this.redisService.get( + `${this.ONLINE_PLAYERS_KEY}:${player_id}`, + ); + if (!player) + return [ + null, + [ + new ServiceError({ + reason: SEReason.NOT_FOUND, + field: 'player_id', + value: player_id, + message: 'Player with this _id is not found in online players', + }), + ], + ]; + return [JSON.parse(player), null]; + } } diff --git a/src/onlinePlayers/payload/OnlinePlayer.ts b/src/onlinePlayers/payload/OnlinePlayer.ts index 9ec0029e7..5591a7816 100644 --- a/src/onlinePlayers/payload/OnlinePlayer.ts +++ b/src/onlinePlayers/payload/OnlinePlayer.ts @@ -1,6 +1,6 @@ import { OnlinePlayerStatus } from '../enum/OnlinePlayerStatus'; -export default class OnlinePlayer { +export default class OnlinePlayer { /** * Player _id */ @@ -15,4 +15,9 @@ export default class OnlinePlayer { * Player status */ status: OnlinePlayerStatus; + + /** + * Any additional information online player has + */ + additional?: Additional; } diff --git a/src/onlinePlayers/payload/additionalTypes/BattleWaitStatus.ts b/src/onlinePlayers/payload/additionalTypes/BattleWaitStatus.ts new file mode 100644 index 000000000..4decfcefb --- /dev/null +++ b/src/onlinePlayers/payload/additionalTypes/BattleWaitStatus.ts @@ -0,0 +1,9 @@ +/** + * Additional information for a player with status BattleWait + */ +export type BattleWaitStatus = { + /** + * Player number in the queue + */ + queueNumber: number; +}; From ee3c49109635e77449448488dc72601b27350b7a Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 16:51:53 +0300 Subject: [PATCH 4/7] rename `getAllOnlinePlayers()` to `getOnlinePlayers()` to be more descriptive --- ...tAllOnlinePlayers.test.ts => getOnlinePlayers.test.ts} | 8 ++++---- src/onlinePlayers/onlinePlayers.controller.ts | 2 +- src/onlinePlayers/onlinePlayers.service.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/__tests__/onlinePlayers/OnlinePlayersService/{getAllOnlinePlayers.test.ts => getOnlinePlayers.test.ts} (91%) diff --git a/src/__tests__/onlinePlayers/OnlinePlayersService/getAllOnlinePlayers.test.ts b/src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayers.test.ts similarity index 91% rename from src/__tests__/onlinePlayers/OnlinePlayersService/getAllOnlinePlayers.test.ts rename to src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayers.test.ts index 8e5ecc38e..e029ffa64 100644 --- a/src/__tests__/onlinePlayers/OnlinePlayersService/getAllOnlinePlayers.test.ts +++ b/src/__tests__/onlinePlayers/OnlinePlayersService/getOnlinePlayers.test.ts @@ -7,7 +7,7 @@ import { OnlinePlayerStatus } from '../../../onlinePlayers/enum/OnlinePlayerStat import { RedisService } from '../../../common/service/redis/redis.service'; import OnlinePlayersCommonModule from '../modules/onlinePlayersCommon.module'; -describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => { +describe('OnlinePlayersService.getOnlinePlayers() test suite', () => { let service: OnlinePlayersService; let redisService: RedisService; @@ -57,7 +57,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => { [_id2]: JSON.stringify(payload2), }); - const player_ids = await service.getAllOnlinePlayers(); + const player_ids = await service.getOnlinePlayers(); expect(player_ids).toContainEqual(payload1); expect(player_ids).toContainEqual(payload2); @@ -82,7 +82,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => { [_id2]: JSON.stringify(payload2), }); - const player_ids = await service.getAllOnlinePlayers({ + const player_ids = await service.getOnlinePlayers({ filter: { status: [OnlinePlayerStatus.UI] }, }); @@ -93,7 +93,7 @@ describe('OnlinePlayersService.getAllOnlinePlayers() test suite', () => { it('Should not return players if there are no players', async () => { jest.spyOn(redisService, 'getValuesByKeyPattern').mockResolvedValue({}); - const player_ids = await service.getAllOnlinePlayers(); + const player_ids = await service.getOnlinePlayers(); expect(player_ids).not.toContain(_id1); }); diff --git a/src/onlinePlayers/onlinePlayers.controller.ts b/src/onlinePlayers/onlinePlayers.controller.ts index 2d72697f1..ab3363007 100644 --- a/src/onlinePlayers/onlinePlayers.controller.ts +++ b/src/onlinePlayers/onlinePlayers.controller.ts @@ -56,7 +56,7 @@ export class OnlinePlayersController { @UniformResponse(null, OnlinePlayerDto) async getAllOnlinePlayers(@Query() query: OnlinePlayerSearchQueryDto) { const filter = { status: query.search }; - return this.onlinePlayersService.getAllOnlinePlayers({ + return this.onlinePlayersService.getOnlinePlayers({ filter, }); } diff --git a/src/onlinePlayers/onlinePlayers.service.ts b/src/onlinePlayers/onlinePlayers.service.ts index 33aed4441..431483d8d 100644 --- a/src/onlinePlayers/onlinePlayers.service.ts +++ b/src/onlinePlayers/onlinePlayers.service.ts @@ -68,7 +68,7 @@ export class OnlinePlayersService { * * @returns Array of OnlinePlayers or empty array if nothing found */ - async getAllOnlinePlayers(options?: { + async getOnlinePlayers(options?: { filter?: { status?: OnlinePlayerStatus[] }; }): Promise { const players = await this.redisService.getValuesByKeyPattern( From 4536f8632541c1b824577fd35eafa93d1e0855e6 Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 16:57:08 +0300 Subject: [PATCH 5/7] Add sorting online players battle queue method Also the battle queue number is saved to Redis to fix possible errors, when the API is restarted, but Redis not, which can lead to synchronization errors with this number --- .../addPlayerOnline.test.ts | 1 - .../sortPlayersByQueueNumber.test.ts | 74 +++++++++ src/common/service/redis/cacheKeys.enum.ts | 17 ++- .../battleQueue/battleQueue.service.ts | 140 ++++++++++++++++-- 4 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 src/__tests__/onlinePlayers/battleQueue/BattleQueueService/sortPlayersByQueueNumber.test.ts diff --git a/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts b/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts index 30e22ad62..b932d9e8f 100644 --- a/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts +++ b/src/__tests__/onlinePlayers/OnlinePlayersService/addPlayerOnline.test.ts @@ -86,7 +86,6 @@ describe('OnlinePlayersService.addPlayerOnline() test suite', () => { status: OnlinePlayerStatus.BATTLE_WAIT, }); - expect(redisSet).toHaveBeenCalledTimes(1); expect(redisSet).toHaveBeenCalledWith(expectedKey, expectedPayload, 90); }); }); diff --git a/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/sortPlayersByQueueNumber.test.ts b/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/sortPlayersByQueueNumber.test.ts new file mode 100644 index 000000000..85bd05e5b --- /dev/null +++ b/src/__tests__/onlinePlayers/battleQueue/BattleQueueService/sortPlayersByQueueNumber.test.ts @@ -0,0 +1,74 @@ +import { BattleQueueService } from 'src/onlinePlayers/battleQueue/battleQueue.service'; +import BattleQueueModule from '../../modules/battleQueue.module'; +import OnlinePlayersBuilderFactory from '../../data/onlinePlayersBuilderFactory'; +import { OnlinePlayerBuilder } from '../../data/onlinePlayers/OnlinePlayerBuilder'; +import { BattleWaitStatus } from '../../../../onlinePlayers/payload/additionalTypes/BattleWaitStatus'; + +describe('BattleQueueService.sortPlayersByQueueNumber', () => { + let service: BattleQueueService; + let playerBuilder: OnlinePlayerBuilder; + + beforeEach(async () => { + service = await BattleQueueModule.getBattleQueueService(); + playerBuilder = OnlinePlayersBuilderFactory.getBuilder('OnlinePlayer'); + }); + + it('should sort players in ascending queueNumber order', () => { + const players = [ + playerBuilder.setAdditional({ queueNumber: 10 }).build(), + playerBuilder.setAdditional({ queueNumber: 5 }).build(), + playerBuilder.setAdditional({ queueNumber: 7 }).build(), + ]; + + const [sorted, errors] = service.sortPlayersByQueueNumber(players); + const sortedNumbers = sorted.map((p) => p.additional.queueNumber); + + expect(errors).toBeNull(); + expect(sortedNumbers).toEqual([5, 7, 10]); + }); + + it('should sort players considering wrap-around queue numbers', () => { + const players = [ + playerBuilder.setAdditional({ queueNumber: 1 }).build(), + playerBuilder.setAdditional({ queueNumber: 9999 }).build(), + playerBuilder.setAdditional({ queueNumber: 5 }).build(), + playerBuilder.setAdditional({ queueNumber: 9998 }).build(), + playerBuilder.setAdditional({ queueNumber: 0 }).build(), + ]; + + const [sorted, errors] = service.sortPlayersByQueueNumber(players); + const sortedNumbers = sorted.map((p) => p.additional.queueNumber); + + expect(errors).toBeNull(); + expect(sortedNumbers).toEqual([9998, 9999, 0, 1, 5]); + }); + + it('should still sort correctly with gaps and wrap', () => { + const players = [ + playerBuilder.setAdditional({ queueNumber: 9998 }).build(), + playerBuilder.setAdditional({ queueNumber: 3 }).build(), + playerBuilder.setAdditional({ queueNumber: 9995 }).build(), + playerBuilder.setAdditional({ queueNumber: 9 }).build(), + playerBuilder.setAdditional({ queueNumber: 1000 }).build(), + ]; + + const [sorted, errors] = service.sortPlayersByQueueNumber(players); + const sortedNumbers = sorted.map((p) => p.additional.queueNumber); + + expect(errors).toBeNull(); + expect(sortedNumbers).toEqual([9995, 9998, 3, 9, 1000]); + }); + + it('Should return ServiceError NOT_FOUND if empty array is provided', () => { + const [sorted, errors] = service.sortPlayersByQueueNumber([]); + expect(sorted).toBeNull(); + expect(errors).toContainSE_NOT_FOUND(); + }); + + it('Should return ServiceError NOT_FOUND if no players have the order number', () => { + const players1 = [playerBuilder.build(), playerBuilder.build()]; + const [sorted, errors] = service.sortPlayersByQueueNumber(players1); + expect(sorted).toBeNull(); + expect(errors).toContainSE_NOT_FOUND(); + }); +}); diff --git a/src/common/service/redis/cacheKeys.enum.ts b/src/common/service/redis/cacheKeys.enum.ts index e1a985818..517faa19d 100644 --- a/src/common/service/redis/cacheKeys.enum.ts +++ b/src/common/service/redis/cacheKeys.enum.ts @@ -5,12 +5,23 @@ * and avoid hardcoding string literals throughout the codebase. * Each key corresponds to a specific cache entry in Redis. * - * - `CLAN_LEADERBOARD`: Key for caching clan leaderboard data. - * - `PLAYER_LEADERBOARD`: Key for caching player leaderboard data. - * - `ONLINE_PLAYERS`: Key for caching the list of online players. */ export enum CacheKeys { + /** + * Key for caching clan leaderboard data. + */ CLAN_LEADERBOARD = 'clanLeaderboard', + /** + * Key for caching player leaderboard data. + */ PLAYER_LEADERBOARD = 'playerLeaderboard', + + /** + * Key for caching the list of online players. + */ ONLINE_PLAYERS = 'online_players', + /** + * Next queue number for a battle queue + */ + NEXT_QUEUE_NUMBER = 'next_queue_number', } diff --git a/src/onlinePlayers/battleQueue/battleQueue.service.ts b/src/onlinePlayers/battleQueue/battleQueue.service.ts index 16d719aca..6bccc0c25 100644 --- a/src/onlinePlayers/battleQueue/battleQueue.service.ts +++ b/src/onlinePlayers/battleQueue/battleQueue.service.ts @@ -2,10 +2,14 @@ import { Injectable } from '@nestjs/common'; import { IServiceReturn } from '../../common/service/basicService/IService'; import OnlinePlayer from '../payload/OnlinePlayer'; import { BattleWaitStatus } from '../payload/additionalTypes/BattleWaitStatus'; +import ServiceError from '../../common/service/basicService/ServiceError'; +import { SEReason } from '../../common/service/basicService/SEReason'; +import { RedisService } from '../../common/service/redis/redis.service'; +import { CacheKeys } from '../../common/service/redis/cacheKeys.enum'; @Injectable() export class BattleQueueService { - public constructor() {} + public constructor(private readonly redisService: RedisService) {} /** * Max number the queue number can become @@ -13,12 +17,6 @@ export class BattleQueueService { */ private readonly queueNumberMax = 9999; - /** - * Auto-incremented positive integer number, which is used to determine queue position of a player - * @private - */ - private nextQueueNumber = 0; - /** * Gets a queue number for a player. * @@ -27,20 +25,140 @@ export class BattleQueueService { * Notice that if the player already in the queue, the number will not be updated * * @param player player for which the number is requested + * + * @returns queue number for an online player */ async getPlayerQueueNumber( player: OnlinePlayer | null, ): Promise> { - if (player && player.additional?.queueNumber != null) { + if (player?.additional?.queueNumber != null) { const existingQueueNumber = (player as OnlinePlayer) .additional.queueNumber; return [existingQueueNumber, null]; } - const playerQueueNumber = this.nextQueueNumber; - this.nextQueueNumber++; - if (this.nextQueueNumber > this.queueNumberMax) this.nextQueueNumber = 0; + const playerQueueNumber = await this.getNextQueueNumber(); + await this.increaseQueueNumber(); return [playerQueueNumber, null]; } + + /** + * Sorts online players based on their queueNumber considering wrap-around after queue number is reset + * + * Notice that all players that does not have a queue number, will be excluded from queue and not retuned. + * + * @param players players with queueNumber + * + * @returns players in order number or ServiceError NOT_FOUND if: + * - Empty array is provided + * - No players have a queue number + */ + sortPlayersByQueueNumber( + players: OnlinePlayer[], + ): IServiceReturn[]> { + const [validPlayers, filterErrors] = + this.filterInvalidPlayersInQueue(players); + + if (filterErrors) return [null, filterErrors]; + + const queueNumbers = validPlayers.map((p) => p.additional.queueNumber); + + const queueMax = this.queueNumberMax + 1; + let oldestQN = queueNumbers[0]; + + for (const currentNumber of queueNumbers) { + const diffRefToCurr = (currentNumber - oldestQN + queueMax) % queueMax; + const diffCurrToRef = (oldestQN - currentNumber + queueMax) % queueMax; + + if (diffCurrToRef < diffRefToCurr) oldestQN = currentNumber; + } + + const sortedPlayers = validPlayers + .slice() + .sort((playerLeft, playerRight) => { + const aQN = playerLeft.additional.queueNumber; + const bQN = playerRight.additional.queueNumber; + + const normA = (aQN - oldestQN + queueMax) % queueMax; + const normB = (bQN - oldestQN + queueMax) % queueMax; + + return normA - normB; + }); + + return [sortedPlayers, null]; + } + + /** + * Gets next queue number from Redis. + * + * Notice that if number does not exist = not set, it will be set to zero. + * + * Notice that the method does not change the value in Redis, it only returns the current value + * + * @private + * @returns next queue number from Redis + */ + private async getNextQueueNumber() { + const queueNumberStr = await this.redisService.get( + CacheKeys.NEXT_QUEUE_NUMBER, + ); + if (queueNumberStr) return Number.parseInt(queueNumberStr); + + await this.redisService.set(CacheKeys.NEXT_QUEUE_NUMBER, '0'); + return 0; + } + + /** + * Increases next queue number in Redis. + * + * Notice that if the number is more than max queue number it will be reset to zero + * + * @private + */ + private async increaseQueueNumber() { + let queueNumber = await this.getNextQueueNumber(); + + queueNumber++; + if (queueNumber > this.queueNumberMax) queueNumber = 0; + + await this.redisService.set(CacheKeys.NEXT_QUEUE_NUMBER, `${queueNumber}`); + } + + /** + * Filters players that does not have an order number + * + * @param players players to filter + * @private + * @returns only players that have the order number or ServiceError NOT_FOUND if there are no valid players + */ + private filterInvalidPlayersInQueue( + players: OnlinePlayer[], + ): IServiceReturn[]> { + if (players.length === 0) + return [ + null, + [ + new ServiceError({ + reason: SEReason.NOT_FOUND, + message: 'No online players in battle queue', + }), + ], + ]; + + const validPlayers = players.filter( + (player) => player.additional?.queueNumber != null, + ); + if (validPlayers.length === 0) + return [ + null, + [ + new ServiceError({ + reason: SEReason.NOT_FOUND, + message: 'No online players with queue number found', + }), + ], + ]; + return [validPlayers, null]; + } } From c56fa13cefc3cd499ccb89eaf5b69c5b86f7bc9e Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 16:57:38 +0300 Subject: [PATCH 6/7] Add /online-players/battleQueue GET endpoint --- .../battleQueue/battleQueue.controller.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/onlinePlayers/battleQueue/battleQueue.controller.ts b/src/onlinePlayers/battleQueue/battleQueue.controller.ts index a7ba2f819..0ed843bb7 100644 --- a/src/onlinePlayers/battleQueue/battleQueue.controller.ts +++ b/src/onlinePlayers/battleQueue/battleQueue.controller.ts @@ -4,10 +4,15 @@ import ApiResponseDescription from 'src/common/swagger/response/ApiResponseDescr import OnlinePlayerDto from '../dto/onlinePlayer.dto'; import { UniformResponse } from '../../common/decorator/response/UniformResponse'; import SwaggerTags from '../../common/swagger/tags/SwaggerTags.decorator'; +import { OnlinePlayersService } from '../onlinePlayers.service'; +import { OnlinePlayerStatus } from '../enum/OnlinePlayerStatus'; @Controller('/online-players/battleQueue') export class BattleQueueController { - constructor(private readonly service: BattleQueueService) {} + constructor( + private readonly service: BattleQueueService, + private readonly onlinePlayersService: OnlinePlayersService, + ) {} @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** @@ -27,6 +32,9 @@ export class BattleQueueController { @Get() @UniformResponse(null, OnlinePlayerDto) async getBattleQueue() { - return []; + const queuePlayers = await this.onlinePlayersService.getOnlinePlayers({ + filter: { status: [OnlinePlayerStatus.BATTLE_WAIT] }, + }); + return this.service.sortPlayersByQueueNumber(queuePlayers); } } From 97de359b110bb75af9df94f11ae48d01291d4dd2 Mon Sep 17 00:00:00 2001 From: Mikhail Deriabin Date: Wed, 21 May 2025 17:16:26 +0300 Subject: [PATCH 7/7] fix bug with `@SwaggerTags()` decorator breaks the generated swagger --- src/leaderboard/leaderboard.controller.ts | 4 ++-- src/onlinePlayers/battleQueue/battleQueue.controller.ts | 2 +- src/onlinePlayers/onlinePlayers.controller.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/leaderboard/leaderboard.controller.ts b/src/leaderboard/leaderboard.controller.ts index efeb9293d..b4938fd48 100644 --- a/src/leaderboard/leaderboard.controller.ts +++ b/src/leaderboard/leaderboard.controller.ts @@ -22,7 +22,6 @@ export class LeaderboardController { private readonly playerService: PlayerService, ) {} - @SwaggerTags('Release on 01.06.2025', 'Leaderboard') /** * Get top players * @@ -39,6 +38,7 @@ export class LeaderboardController { errors: [400, 404], hasAuth: false, }) + @SwaggerTags('Release on 01.06.2025', 'Leaderboard') @Get('player') @NoAuth() @UniformResponse(ModelName.PLAYER, LeaderboardPlayerDto) @@ -47,7 +47,6 @@ export class LeaderboardController { return this.leaderBoardService.getPlayerLeaderboard(query); } - @SwaggerTags('Release on 01.06.2025', 'Leaderboard') /** * Get top clans * @@ -64,6 +63,7 @@ export class LeaderboardController { errors: [400, 404], hasAuth: false, }) + @SwaggerTags('Release on 01.06.2025', 'Leaderboard') @Get('clan') @NoAuth() @UniformResponse(ModelName.CLAN, ClanDto) diff --git a/src/onlinePlayers/battleQueue/battleQueue.controller.ts b/src/onlinePlayers/battleQueue/battleQueue.controller.ts index 0ed843bb7..19d339ffc 100644 --- a/src/onlinePlayers/battleQueue/battleQueue.controller.ts +++ b/src/onlinePlayers/battleQueue/battleQueue.controller.ts @@ -14,7 +14,6 @@ export class BattleQueueController { private readonly onlinePlayersService: OnlinePlayersService, ) {} - @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** * Get battle queue * @@ -29,6 +28,7 @@ export class BattleQueueController { }, errors: [401, 404], }) + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') @Get() @UniformResponse(null, OnlinePlayerDto) async getBattleQueue() { diff --git a/src/onlinePlayers/onlinePlayers.controller.ts b/src/onlinePlayers/onlinePlayers.controller.ts index ab3363007..dca2a0499 100644 --- a/src/onlinePlayers/onlinePlayers.controller.ts +++ b/src/onlinePlayers/onlinePlayers.controller.ts @@ -13,7 +13,6 @@ import SwaggerTags from '../common/swagger/tags/SwaggerTags.decorator'; export class OnlinePlayersController { constructor(private readonly onlinePlayersService: OnlinePlayersService) {} - @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** * Inform the API if player is still online * @@ -27,6 +26,7 @@ export class OnlinePlayersController { }, errors: [400, 401], }) + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') @Post('ping') @UniformResponse() async ping(@Body() body: InformPlayerIsOnlineDto, @LoggedUser() user: User) { @@ -36,7 +36,6 @@ export class OnlinePlayersController { }); } - @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') /** * Inform the API if player is still online * @@ -52,6 +51,7 @@ export class OnlinePlayersController { }, errors: [401], }) + @SwaggerTags('Release on 01.06.2025', 'OnlinePlayers') @Get() @UniformResponse(null, OnlinePlayerDto) async getAllOnlinePlayers(@Query() query: OnlinePlayerSearchQueryDto) {