diff --git a/src/stat/dto/compareUsers.dto.ts b/src/stat/dto/compareUsers.dto.ts new file mode 100644 index 0000000..f3a1ee6 --- /dev/null +++ b/src/stat/dto/compareUsers.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class CompareUsersDto { + @ApiProperty({ required: true }) + @IsString() + user1: string; + + @ApiProperty({ required: true }) + user2: string; +} + +export class CompareUsersResponseDto { + @ApiProperty() + algorithmScoreDifference: number; + + @ApiProperty() + algorithmRankDifference: number; + + @ApiProperty() + githubScoreDifference: number; + + @ApiProperty() + githubRankDifference: number; + + @ApiProperty() + gradeScoreDifference: number; + + @ApiProperty() + gradeRankDifference: number; + + @ApiProperty() + totalScoreDifference: number; + + @ApiProperty() + totalRankDifference: number; + + @ApiProperty() + mostSignificantScoreDifferenceStat: string; +} diff --git a/src/stat/service/total.service.ts b/src/stat/service/total.service.ts index 4233aad..6a4e793 100644 --- a/src/stat/service/total.service.ts +++ b/src/stat/service/total.service.ts @@ -10,6 +10,7 @@ import { Algorithm } from '../../Entity/algorithm'; import { RankListDto, RankListOptionDto } from '../dto/rank-list-option.dto'; import { PointFindDto } from '../dto/rank-find.dto'; import { StatFindDto } from '../dto/stat-find.dto'; +import { CompareUsersResponseDto } from '../dto/compareUsers.dto'; @Injectable() export class TotalService { @@ -83,4 +84,176 @@ export class TotalService { public async getIndividualTotalRank(userId: string, options: PointFindDto) { return await this.totalRepository.findIndividualRank(userId, options); } + + public async compareUsers(user1: string, user2: string) { + const comparison = new CompareUsersResponseDto(); + [ + comparison.algorithmScoreDifference, + comparison.algorithmRankDifference, + ] = await this.compareAlgorithm(user1, user2); + + [comparison.githubScoreDifference, comparison.githubRankDifference] = + await this.compareGithub(user1, user2); + + [comparison.gradeScoreDifference, comparison.gradeRankDifference] = + await this.compareGrade(user1, user2); + + [comparison.totalScoreDifference, comparison.totalRankDifference] = + await this.compareTotal(user1, user2); + + comparison.mostSignificantScoreDifferenceStat = + this.findMaxScoreDifference(comparison); + return comparison; + } + + findMaxScoreDifference(response: CompareUsersResponseDto): string | null { + const scoreDifferences = { + algorithm: response.algorithmScoreDifference, + github: response.githubScoreDifference, + grade: response.gradeScoreDifference, + }; + + let maxDifference = 0; + let maxDifferenceKey: string | null = null; + + for (const [key, value] of Object.entries(scoreDifferences)) { + if (value !== null && value < 0 && value < maxDifference) { + maxDifference = value; + maxDifferenceKey = key; + } + } + + return maxDifferenceKey; + } + + private async compareGithub(user1: string, user2: string) { + const githubScoreDifference = await this.compareGithubScore( + user1, + user2, + ); + if (githubScoreDifference === null) { + return [null, null]; + } + const githubRankDifference = await this.compareGithubRank(user1, user2); + return [githubScoreDifference, githubRankDifference]; + } + + private async compareGithubScore(user1: string, user2: string) { + const user1Github = await this.githubService.findGithub(user1); + const user2Github = await this.githubService.findGithub(user2); + if (user1Github == null || user2Github == null) { + return null; + } + return user1Github.score - user2Github.score; + } + + private async compareGithubRank(user1: string, user2: string) { + const user1Rank = await this.githubService.getIndividualGithubRank( + user1, + new PointFindDto(), + ); + const user2Rank = await this.githubService.getIndividualGithubRank( + user2, + new PointFindDto(), + ); + return user1Rank.rank - user2Rank.rank; + } + + private async compareAlgorithm(user1: string, user2: string) { + const algorithmScoreDifference = await this.compareAlgorithmScore( + user1, + user2, + ); + if (algorithmScoreDifference === null) { + return [null, null]; + } + const algorithmRankDifference = await this.compareAlgorithmRank( + user1, + user2, + ); + return [algorithmScoreDifference, algorithmRankDifference]; + } + + private async compareAlgorithmScore(user1: string, user2: string) { + const user1Algorithm = await this.algorithmService.findAlgorithm(user1); + const user2Algorithm = await this.algorithmService.findAlgorithm(user2); + if (user1Algorithm == null || user2Algorithm == null) { + return null; + } + return user1Algorithm.score - user2Algorithm.score; + } + + private async compareAlgorithmRank(user1: string, user2: string) { + const user1Rank = + await this.algorithmService.getIndividualAlgorithmRank( + user1, + new PointFindDto(), + ); + const user2Rank = + await this.algorithmService.getIndividualAlgorithmRank( + user2, + new PointFindDto(), + ); + return user1Rank.rank - user2Rank.rank; + } + + private async compareGrade(user1: string, user2: string) { + const gradeScoreDifference = await this.compareGradeScore(user1, user2); + if (gradeScoreDifference === null) { + return [null, null]; + } + const gradeRankDifference = await this.compareGradeRank(user1, user2); + return [gradeScoreDifference, gradeRankDifference]; + } + + private async compareGradeScore(user1: string, user2: string) { + const user1Grade = await this.gradeService.findGrade(user1); + const user2Grade = await this.gradeService.findGrade(user2); + if (user1Grade == null || user2Grade == null) { + return null; + } + return user1Grade.score - user2Grade.score; + } + + private async compareGradeRank(user1: string, user2: string) { + const user1Rank = await this.gradeService.getIndividualGradeRank( + user1, + new PointFindDto(), + ); + const user2Rank = await this.gradeService.getIndividualGradeRank( + user2, + new PointFindDto(), + ); + return user1Rank.rank - user2Rank.rank; + } + + private async compareTotal(user1: string, user2: string) { + const totalScoreDifference = await this.compareTotalScore(user1, user2); + if (totalScoreDifference === null) { + return [null, null]; + } + const totalRankDifference = await this.compareTotalRank(user1, user2); + return [totalScoreDifference, totalRankDifference]; + } + + private async compareTotalScore(user1: string, user2: string) { + const user1Total = await this.totalRepository.findOneById(user1); + const user2Total = await this.totalRepository.findOneById(user2); + if (user1Total == null || user2Total == null) { + return null; + } + return user1Total.score - user2Total.score; + } + + private async compareTotalRank(user1: string, user2: string) { + const user1Rank = await this.getIndividualTotalRank( + user1, + new PointFindDto(), + ); + const user2Rank = await this.getIndividualTotalRank( + user2, + new PointFindDto(), + ); + return user1Rank.rank - user2Rank.rank; + } } diff --git a/src/stat/stat.controller.ts b/src/stat/stat.controller.ts index 8e29640..b8493f2 100644 --- a/src/stat/stat.controller.ts +++ b/src/stat/stat.controller.ts @@ -6,6 +6,7 @@ import { Param, Patch, Post, + Query, UseGuards, } from '@nestjs/common'; import { AlgorithmService } from './service/algorithm.service'; @@ -32,6 +33,10 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { Transactional } from 'typeorm-transactional'; +import { + CompareUsersDto, + CompareUsersResponseDto, +} from './dto/compareUsers.dto'; @UseGuards(JwtAuthGuard) @Controller('api/stat') @@ -43,6 +48,22 @@ export class StatController { private readonly totalService: TotalService, ) {} + @ApiTags('stat') + @ApiOperation({ + summary: '개발 역량 비교 API', + description: + '두 사용자의 역량을 비교한다. user1 - user2 한 결과를 반환한다. 둘중 하나라도 역량이 등록되지 않은 경우 null 반환. 가장차이나는 역량은 user1이 user2 보다 작은 역량중에서 찾아 반환한다. user1이 user2 보다 모든 역량이 크거나 같다면 null 반환', + }) + @ApiBearerAuth('accessToken') + @ApiOkResponse({ + description: '유저 정보 비교 성공', + type: CompareUsersResponseDto, + }) + @Get('compare') + public async compareUsers(@Query() users: CompareUsersDto) { + return await this.totalService.compareUsers(users.user1, users.user2); + } + @ApiTags('stat') @ApiOperation({ summary: '유저의 개발 역량 반환 API', diff --git a/src/utils/stat.repository.ts b/src/utils/stat.repository.ts index cc208c2..b69de9d 100644 --- a/src/utils/stat.repository.ts +++ b/src/utils/stat.repository.ts @@ -48,15 +48,19 @@ export class StatRepository extends Repository { .distinct(true) .from((sub) => { return sub - .select('RANK() OVER (ORDER BY a.score DESC)', 'rank') + .select( + 'RANK() OVER (ORDER BY a.score DESC, user_id ASC)', + 'rank', + ) .addSelect('a.user_id', 'user_id') .from(this.entity, 'a') .innerJoin(User, 'u', 'a.user_id = u.user_id') .addSelect('u.major', 'major') - .where(this.createClassificationOption(options)); + .where(this.createClassificationOption(options)) + .orderBy('score', 'DESC') + .addOrderBy('user_id'); }, 'b') .where(`b.user_id = '${userId}'`); - return await queryBuilder.getRawOne(); } createCursorOption(options: RankListOptionDto) {