From edd3076b5477c7fe7e59011c743e54026c27b02e Mon Sep 17 00:00:00 2001 From: Brian Beck Date: Sun, 26 Sep 2021 00:25:26 -0700 Subject: [PATCH] Add time period support to leaderboard queries --- .../src/common/dto/top-players-query.dto.ts | 6 +++++ app/api/src/players/players.controller.ts | 5 +++- app/api/src/players/players.service.ts | 25 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/api/src/common/dto/top-players-query.dto.ts b/app/api/src/common/dto/top-players-query.dto.ts index 73317e0..1a0c7d1 100644 --- a/app/api/src/common/dto/top-players-query.dto.ts +++ b/app/api/src/common/dto/top-players-query.dto.ts @@ -40,6 +40,9 @@ export class TopAccuracyQueryDto { @IsPositive() @Max(100) limit: number; + + @IsOptional() + timePeriod: string; } export class TopWinsQueryDto { @@ -51,4 +54,7 @@ export class TopWinsQueryDto { @IsPositive() @Max(100) limit: number; + + @IsOptional() + timePeriod: string; } diff --git a/app/api/src/players/players.controller.ts b/app/api/src/players/players.controller.ts index 3118ff6..545aae0 100644 --- a/app/api/src/players/players.controller.ts +++ b/app/api/src/players/players.controller.ts @@ -32,6 +32,7 @@ export class PlayersController { minGames = 10, minShots = 100, limit = 10, + timePeriod, } = topPlayersQuery; return this.playerService.findTopAccuracy({ stat, @@ -39,6 +40,7 @@ export class PlayersController { minGames, minShots, limit, + timePeriod, }); } @@ -48,10 +50,11 @@ export class PlayersController { summary: 'Return a leaderboard of players for win percentage', }) findTopWins(@Query() topPlayersQuery: TopWinsQueryDto) { - const { minGames = 100, limit = 10 } = topPlayersQuery; + const { minGames = 100, limit = 10, timePeriod } = topPlayersQuery; return this.playerService.findTopWins({ minGames, limit, + timePeriod, }); } } diff --git a/app/api/src/players/players.service.ts b/app/api/src/players/players.service.ts index 193bb02..60f1b2f 100644 --- a/app/api/src/players/players.service.ts +++ b/app/api/src/players/players.service.ts @@ -56,6 +56,7 @@ export class PlayersService { minGames, minShots, limit, + timePeriod, } = topAccuracyQuery; const shotsStat = { @@ -102,6 +103,8 @@ export class PlayersService { // Cast to float to avoid integer division truncating the result. const aggregatedAccuracy = `(${aggregatedHits}::float / ${aggregatedShots}::float)`; + const sinceDate = '(now() - interval :timePeriod)'; + // TODO: This whole query could probably be turned into a `ViewEntity` at // some point, but I couldn't get that to work. @@ -112,6 +115,7 @@ export class PlayersService { shotsStat, minGames, minShots, + timePeriod, }) .select([ 'player.player_guid', @@ -121,6 +125,10 @@ export class PlayersService { 'stats.shots', 'stats.accuracy', ]) + .addSelect( + timePeriod ? sinceDate : 'NULL', + 'since_date' + ) .innerJoin( (subQuery) => { let statsQuery = subQuery @@ -131,6 +139,7 @@ export class PlayersService { .addSelect(aggregatedShots, 'shots') .addSelect(aggregatedAccuracy, 'accuracy') .where(`${shotsValue} > 0`) + .andWhere(timePeriod ? `game.datestamp >= ${sinceDate}` : 'TRUE') .groupBy('game.player_guid'); if (excludeDiscJumps) { @@ -190,16 +199,20 @@ export class PlayersService { minGames, minShots, limit, + timePeriod, + sinceDate: rows.length ? rows[0].since_date : null, players, }; } async findTopWins(topWinsQuery: TopWinsQueryDto) { - const { minGames, limit } = topWinsQuery; + const { minGames, limit, timePeriod } = topWinsQuery; + + const sinceDate = '(now() - interval :timePeriod)'; const query = this.playersRepository .createQueryBuilder('player') - .setParameters({ minGames }) + .setParameters({ minGames, timePeriod }) .select(['stats.player_name', 'stats.player_guid']) .addSelect('COUNT(stats.game_id)::integer', 'game_count') .addSelect( @@ -218,6 +231,10 @@ export class PlayersService { "(COUNT(stats.player_match_result = 'win' OR NULL)::float + COUNT(stats.player_match_result = 'draw' OR NULL)::float / 2.0) / COUNT(stats.game_id)::float", 'win_percent', ) + .addSelect( + timePeriod ? sinceDate : 'NULL', + 'since_date' + ) .innerJoin( (qb) => { return ( @@ -309,6 +326,8 @@ export class PlayersService { // Each team must have at least 2 players. .andWhere('join_g.team_size_storm >= 2') .andWhere('join_g.team_size_inferno >= 2') + // Must fall within the specified time period. + .andWhere(timePeriod ? `game.datestamp >= ${sinceDate}` : 'TRUE') ); }, 'stats', @@ -351,6 +370,8 @@ export class PlayersService { minGames, gameType: ['CTFGame', 'SCtFGame'], limit, + timePeriod, + sinceDate: rows.length ? rows[0].since_date : null, players, }; }