Skip to content

Commit

Permalink
feat: Dashboard controllers
Browse files Browse the repository at this point in the history
  • Loading branch information
Fllorent0D committed Aug 7, 2023
1 parent 32f9819 commit 9b2368e
Show file tree
Hide file tree
Showing 22 changed files with 627 additions and 14 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tabt-rest",
"version": "1.12.107",
"version": "1.12.110",
"description": "",
"author": "",
"private": true,
Expand Down
2 changes: 2 additions & 0 deletions src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MatchModule } from './match/match.module';
import { HealthModule } from './health/health.module';
import { InternalIdentifiersModule } from './internal-identifiers/internal-identifiers.module';
import { UserAgentModule } from './user-agent/user-agent.module';
import { DashboardModule } from './dashboard/dashboard.module';

@Module({
imports: [
Expand All @@ -20,6 +21,7 @@ import { UserAgentModule } from './user-agent/user-agent.module';
HealthModule,
UserAgentModule,
InternalIdentifiersModule,
DashboardModule
],
})
export class ApiModule {
Expand Down
38 changes: 38 additions & 0 deletions src/api/dashboard/controllers/club-dashboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Controller, Get, NotFoundException, Param, ParseIntPipe, Query, Version } from '@nestjs/common';
import { ApiNotFoundResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { MemberDashboardDTOV1, WeeklyNumericRankingInputV2 } from '../dto/member-dashboard.dto';
import { MemberDashboardService } from '../services/member-dashboard.service';
import { ClubDashboardDTOV1 } from '../dto/club-dashboard.dto';
import { ClubDashboardService } from '../services/club-dashboard.service';

@ApiTags('Dashboards')
@Controller({
path: 'dashboard/club',
version: '1',
})
export class ClubDashboardController {
constructor(
private readonly clubDashboardService: ClubDashboardService,
) {
}

@Get(':uniqueIndex')
@ApiOkResponse({
type: ClubDashboardDTOV1,
description: 'The information to be displayed on the club dashboard',
})
@ApiNotFoundResponse({
description: 'No info found for given club',
})
@Version('1')
async clubDashboardV1(
@Param('uniqueIndex') id: string,
): Promise<ClubDashboardDTOV1> {
const clubDasboard = await this.clubDashboardService.getDashboard(id);
if (!clubDasboard) {
throw new NotFoundException(`No member found for id ${id}`);
}
return clubDasboard;
}

}
41 changes: 41 additions & 0 deletions src/api/dashboard/controllers/division-dashboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Controller, Get, NotFoundException, Param, ParseIntPipe, Query, Version } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse, ApiTags } from "@nestjs/swagger";
import { MemberDashboardDTOV1, WeeklyNumericRankingInputV2 } from "../dto/member-dashboard.dto";
import { MemberService } from "src/services/members/member.service";
import { getSimplifiedPlayerCategory } from "../../member/helpers/player-category-helpers";
import { EloMemberService } from "src/services/members/elo-member.service";
import { MemberDashboardService } from "../services/member-dashboard.service";
import { DivisionDashboardDTOV1 } from '../dto/division-dashboard.dto';
import { DivisionDashboardService } from '../services/division-dashboard.service';

@ApiTags('Dashboards')
@Controller({
path: 'dashboard/division',
version: '1',
})
export class DivisionDashboardController {
constructor(
private readonly divisionDashboardService: DivisionDashboardService,
){
}

@Get(':divisionId')
@ApiOkResponse({
type: DivisionDashboardDTOV1,
description: 'The information to be displayed on the division dashboard',
})
@ApiNotFoundResponse({
description: 'No info found for given division',
})
@Version('1')
async memberDashboardV1(
@Param('divisionId', ParseIntPipe) id: number,
): Promise<DivisionDashboardDTOV1> {
const divisionDashboardDTOV1 = await this.divisionDashboardService.getDashboard(id);
if(!divisionDashboardDTOV1) {
throw new NotFoundException(`No division found for id ${id}`);
}
return divisionDashboardDTOV1;
}

}
37 changes: 37 additions & 0 deletions src/api/dashboard/controllers/member-dashboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Controller, Get, NotFoundException, Param, ParseIntPipe, Query, Version } from '@nestjs/common';
import { ApiNotFoundResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { MemberDashboardDTOV1, WeeklyNumericRankingInputV2 } from '../dto/member-dashboard.dto';
import { MemberDashboardService } from '../services/member-dashboard.service';

@ApiTags('Dashboards')
@Controller({
path: 'dashboard/member',
version: '1',
})
export class MemberDashboardController {
constructor(
private readonly memberDashboardService: MemberDashboardService,
) {
}

@Get(':uniqueIndex')
@ApiOkResponse({
type: MemberDashboardDTOV1,
description: 'The information to be displayed on the members dashboard',
})
@ApiNotFoundResponse({
description: 'No info found for given player',
})
@Version('1')
async memberDashboardV1(
@Param('uniqueIndex', ParseIntPipe) id: number,
@Query() params: WeeklyNumericRankingInputV2,
): Promise<MemberDashboardDTOV1> {
const memberDashboard = await this.memberDashboardService.getDashboard(id, params.category);
if (!memberDashboard) {
throw new NotFoundException(`No member found for id ${id}`);
}
return memberDashboard;
}

}
17 changes: 17 additions & 0 deletions src/api/dashboard/dashboard.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { CommonModule } from '../../common/common.module';
import { ServicesModule } from '../../services/services.module';
import { MemberDashboardController } from './controllers/member-dashboard.controller';
import { MemberDashboardService } from './services/member-dashboard.service';
import { DivisionDashboardService } from './services/division-dashboard.service';
import { DivisionDashboardController } from './controllers/division-dashboard.controller';
import { ClubDashboardService } from './services/club-dashboard.service';
import { ClubDashboardController } from './controllers/club-dashboard.controller';

@Module({
imports: [CommonModule, ServicesModule],
controllers: [MemberDashboardController, DivisionDashboardController, ClubDashboardController],
providers: [MemberDashboardService, DivisionDashboardService, ClubDashboardService],
})
export class DashboardModule {
}
16 changes: 16 additions & 0 deletions src/api/dashboard/dto/club-dashboard.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { ClubEntry, MemberEntry, TeamEntry, TeamMatchesEntry } from '../../../entity/tabt-soap/TabTAPI_Port';
import { ResponseDTO } from './common.dto';

export class ClubDashboardDTOV1 {
@ApiProperty({ type: ClubEntry})
club: ResponseDTO<ClubEntry>;
@ApiProperty({type: MemberEntry, isArray: true})
members: ResponseDTO<MemberEntry[]>;
@ApiProperty({type: TeamEntry, isArray: true})
teams: ResponseDTO<TeamEntry[]>;
@ApiProperty({type: 'object', additionalProperties: {type: 'array', additionalProperties: {type: '#/components/TeamMatchEntry'}} , description: 'key is the week name'})
matches: ResponseDTO<{
[weekName: number]: TeamMatchesEntry[]
}>
}
14 changes: 14 additions & 0 deletions src/api/dashboard/dto/common.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export enum RESPONSE_STATUS {
SUCCESS = 'SUCCESS',
ERROR = 'ERROR',
WARNING = 'WARNING',
}

export class ResponseDTO<T> {
constructor(
public readonly status: RESPONSE_STATUS,
public readonly payload: T | undefined,
public readonly error?: string,
) {
}
}
18 changes: 18 additions & 0 deletions src/api/dashboard/dto/division-dashboard.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DivisionEntry, RankingEntry } from '../../../entity/tabt-soap/TabTAPI_Port';
import { WinLossSummaryDTOV1 } from './member-dashboard.dto';
import { ResponseDTO } from './common.dto';

export class DivisionMemberDashboardDTOV1 extends WinLossSummaryDTOV1 {
member: {
lastname: string;
firstname: string;
ranking: string;
clubName: string;
};
}

export class DivisionDashboardDTOV1 {
division: ResponseDTO<DivisionEntry>;
ranking: ResponseDTO<RankingEntry[]>;
playersStats: ResponseDTO<DivisionMemberDashboardDTOV1[]>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { DivisionMemberDashboardDTOV1 } from '../division-dashboard.dto';
import { MemberResults } from '../../../../common/dto/member-ranking.dto';

export class DivisionMemberDashboardDTOV1Mapper {
static mapMemberResults(memberResult: MemberResults): DivisionMemberDashboardDTOV1{
return {
member:{
lastname: memberResult.lastName,
firstname: memberResult.firstName,
ranking: memberResult.ranking,
clubName: ''
},
count: memberResult.played,
victories: memberResult.win,
defeats: memberResult.lose,
victoriesPct: memberResult.winPourcentage,
defeatsPct: memberResult.losePourcentage,
}
}
}
41 changes: 41 additions & 0 deletions src/api/dashboard/dto/member-dashboard.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { MemberEntry, MemberEntryResultEntry, TeamMatchesEntry } from '../../../entity/tabt-soap/TabTAPI_Port';
import { PLAYER_CATEGORY, WeeklyNumericRankingV3 } from '../../member/dto/member.dto';
import { IsEnum } from 'class-validator';
import { ResponseDTO } from './common.dto';


export class MemberDashboardDTOV1 {
constructor(
public member: ResponseDTO<MemberEntry>,
public numericRankingResponse: ResponseDTO<WeeklyNumericRankingV3>,
public latestTeamMatches: ResponseDTO<TeamMatchesEntry[]>,
public stats: ResponseDTO<MemberStatsDTOV1>,
) {
}
}

export class MemberStatsDTOV1 {
tieBreaks: WinLossSummaryDTOV1;
matches: WinLossSummaryDTOV1;
perRanking: RankingWinLossDTOV1[];
}

export class WinLossSummaryDTOV1 {
count: number;
victories?: number;
defeats?: number;
victoriesPct?: number;
defeatsPct?: number;
}

export class RankingWinLossDTOV1 extends WinLossSummaryDTOV1 {
ranking: string;
players: MemberEntryResultEntry[]
}

export class WeeklyNumericRankingInputV2 {
@ApiPropertyOptional({ enum: PLAYER_CATEGORY})
@IsEnum(PLAYER_CATEGORY)
category?: PLAYER_CATEGORY;
}
3 changes: 3 additions & 0 deletions src/api/dashboard/interfaces/dashboard-service.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface DashboardServiceInterface<T> {
getDashboard(...args: any[]): Promise<T>;
}
119 changes: 119 additions & 0 deletions src/api/dashboard/services/club-dashboard.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { ClubService } from '../../../services/clubs/club.service';
import { DashboardServiceInterface } from '../interfaces/dashboard-service.interface';
import { DivisionMemberDashboardDTOV1 } from '../dto/division-dashboard.dto';
import { MatchesMembersRankerService } from '../../../services/matches/matches-members-ranker.service';
import { DivisionMemberDashboardDTOV1Mapper } from '../dto/mappers/division-member-dashboard-dto-v1.mapper';
import { ClubDashboardDTOV1 } from '../dto/club-dashboard.dto';
import { MemberService } from '../../../services/members/member.service';
import { MatchService } from '../../../services/matches/match.service';
import { ClubTeamService } from '../../../services/clubs/club-team.service';
import { ClubEntry, MemberEntry, TeamEntry, TeamMatchesEntry } from '../../../entity/tabt-soap/TabTAPI_Port';
import { RESPONSE_STATUS, ResponseDTO } from '../dto/common.dto';

@Injectable()
export class ClubDashboardService implements DashboardServiceInterface<ClubDashboardDTOV1> {

constructor(
private readonly clubService: ClubService,
private readonly matchesMembersRankerService: MatchesMembersRankerService,
private readonly memberService: MemberService,
private readonly matchService: MatchService,
private readonly clubTeamService: ClubTeamService,
) {
}

async getDashboard(clubUniqueIndex: string): Promise<ClubDashboardDTOV1> {
const club = await this.getClub(clubUniqueIndex);
if (!club.payload) {
return {
club: new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, 'No club found for given id'),
members: new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, 'No club found for given id'),
teams: new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, 'No club found for given id'),
matches: new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, 'No club found for given id'),
}
}


const [members, teams, matches] = await Promise.all([
this.getClubMembers(clubUniqueIndex),
this.getClubTeams(clubUniqueIndex),
this.getClubMatchesGrouped(clubUniqueIndex),
]);


return {
club,
members,
teams,
matches,
};
}

async getPlayersStats(divisionId: number): Promise<DivisionMemberDashboardDTOV1[]> {
const results = await this.matchesMembersRankerService.getMembersRankingFromDivision(divisionId);
// map the results to the DTO
return results.map(DivisionMemberDashboardDTOV1Mapper.mapMemberResults);
};

private async getClubsMembers(clubUniqueIndex: string): Promise<ResponseDTO<MemberEntry[]>> {
try {
const members = await this.memberService.getMembers({ Club: clubUniqueIndex });
return new ResponseDTO(RESPONSE_STATUS.SUCCESS, members);
} catch (error) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, error.message);
}
}

private async getClub(clubUniqueIndex: string): Promise<ResponseDTO<ClubEntry>> {
try {
const clubs = await this.clubService.getClubs({ Club: clubUniqueIndex });
if(!clubs?.[0]) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, 'No club found for given id');
}
return new ResponseDTO(RESPONSE_STATUS.SUCCESS, clubs[0]);
} catch (error) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, error.message);
}
}

private async getClubMembers(clubUniqueIndex: string): Promise<ResponseDTO<MemberEntry[]>> {
try {
const members = await this.memberService.getMembers({ Club: clubUniqueIndex });
return new ResponseDTO(RESPONSE_STATUS.SUCCESS, members);
} catch (error) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, error.message);
}
}

private async getClubTeams(clubUniqueIndex: string): Promise<ResponseDTO<TeamEntry[]>> {
try {
const teams = await this.clubTeamService.getClubsTeams({ Club: clubUniqueIndex });
return new ResponseDTO(RESPONSE_STATUS.SUCCESS, teams);
} catch (error) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, error.message);
}
}


private async getClubMatchesGrouped(clubUniqueIndex: string): Promise<ResponseDTO<{ [weekname: number]: TeamMatchesEntry[] }>> {
try {
const matches = await this.matchService.getMatches({ Club: clubUniqueIndex });
const reduced = matches.reduce<{ [weekName: number]: TeamMatchesEntry[] }>((acc, currentValue) => {
const weekName = Number(currentValue.WeekName);
if (acc[weekName]) {
acc[weekName].push(currentValue);
} else {
acc[weekName] = [currentValue];
}
return acc;
}, {});
return new ResponseDTO(RESPONSE_STATUS.SUCCESS, reduced);
} catch (error) {
return new ResponseDTO(RESPONSE_STATUS.ERROR, undefined, error.message

)
}

}
}
Loading

0 comments on commit 9b2368e

Please sign in to comment.