Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#087] 모든 역량 랭크를 조회하는 기능 구현 #88

Merged
merged 21 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fd6cb41
Feat: 깃허브 랭크를 쿼리하는 메서드 구현
koomin1227 Apr 19, 2024
45ca7da
Feat: getGithubRank 메서드 구현
koomin1227 Apr 19, 2024
e84b54e
Feat: 깃허브 랭크를 조회하는 컨트롤러 구현
koomin1227 Apr 19, 2024
c361375
Feat: 학점 랭크를 쿼리하는 메서드 구현
koomin1227 Apr 19, 2024
116d616
Feat: getGradeRank 메서드 구현
koomin1227 Apr 19, 2024
d90ecfe
Feat: 학점 랭크를 조회하는 컨트롤러 구현
koomin1227 Apr 19, 2024
efcad5f
Feat: 종합 랭크를 쿼리하는 메서드 구현
koomin1227 Apr 19, 2024
946a5a4
Feat: getTotalRank 메서드 구현
koomin1227 Apr 19, 2024
b8b6fc5
Feat: 종합 랭크를 조회하는 컨트롤러 구현
koomin1227 Apr 19, 2024
2a80ea1
Test: getAlgorithms 테스트
koomin1227 Apr 19, 2024
2d48138
Refactor: 불필요한 import 제거
koomin1227 Apr 19, 2024
b634555
Feat: stat을 다루는 repository들이 공유할 StatRepository 생성
koomin1227 Apr 19, 2024
72a86c3
Refactor: AlgorithmRepository의 랭크 메서드를 상속 받도록 수정
koomin1227 Apr 19, 2024
809af4d
Refactor: GithubRepository 랭크 메서드를 상속 받도록 수정
koomin1227 Apr 19, 2024
540bc5d
Refactor: GradeRepository 랭크 메서드를 상속 받도록 수정
koomin1227 Apr 19, 2024
894de01
Refactor: TotalRepository 랭크 메서드를 상속 받도록 수정
koomin1227 Apr 19, 2024
7b8b90a
Feat: StatRepository에서 개인등수를 확인하는 findIndividualRank 추가
koomin1227 Apr 19, 2024
06f4fc5
Fix: 스웨거에 path 변수가 표시되도록 수정
koomin1227 Apr 19, 2024
5d8ef50
Refactor: 각 repository에서 공통되었던 개인 랭크 찾는 메서드를 상속 받도록 수정
koomin1227 Apr 19, 2024
e4fd0ad
Refactor: rank 에서 user_id를 userId로 수정
koomin1227 Apr 19, 2024
a3bad4f
Feat: TotalPoint 추가
koomin1227 Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/Entity/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
OneToOne,
PrimaryGeneratedColumn,
Expand Down
2 changes: 0 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import * as process from 'process';
import { HttpLoggerInterceptor } from './utils/httpLoggerInterceptor';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
import { HttpExceptionFilter } from './utils/httpExceptionFilter';
import { WinstonModule } from 'nest-winston';
import { transports } from 'winston';

@Module({
imports: [
Expand Down
2 changes: 1 addition & 1 deletion src/stat/dto/rank-list-option.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class RankListDto {
rank: number;

@ApiProperty()
user_id: string;
userId: string;

@ApiProperty()
point: number;
Expand Down
3 changes: 3 additions & 0 deletions src/stat/dto/stat-find.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ export class StatFindDto {

@ApiProperty()
grade: number;

@ApiProperty()
totalPoint: number;
}
84 changes: 81 additions & 3 deletions src/stat/rank.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,81 @@ export class RankController {
return await this.algorithmService.getAlgorithms(options);
}

@Get('/users/:id')
@Get('github')
@ApiTags('rank')
@ApiOperation({
summary: '깃허브 전체 랭킹 API',
description:
'깃허브 역량의 랭킹리스트를 반환한다. 페이지네이션이 가능하고 학과 별로 필터링 가능하다. (학번 필터링은 아직 미구현) 커서 사용시 cursorPoint, cursorUserId 두개를 동시에 넣어야한다. 각각 마지막으로 받은 유저의 점수와 유저 아이디이다.',
})
@ApiBearerAuth('accessToken')
@ApiOkResponse({
description: '깃허브 랭킹 반환',
type: [RankListDto],
})
@ApiUnauthorizedResponse({
description: 'jwt 관련 문제 (인증 시간이 만료됨, jwt를 보내지 않음)',
})
@ApiForbiddenResponse({
description: '허용되지 않은 자원에 접근한 경우. 즉, 권한이 없는 경우',
})
@ApiInternalServerErrorResponse({
description: '서버 오류',
})
async findGithubRank(@Query() options: RankListOptionDto) {
return await this.githubService.getGithubRank(options);
}

@Get('grade')
@ApiTags('rank')
@ApiOperation({
summary: '학점 전체 랭킹 API',
description:
'학점 역량의 랭킹리스트를 반환한다. 페이지네이션이 가능하고 학과 별로 필터링 가능하다. (학번 필터링은 아직 미구현) 커서 사용시 cursorPoint, cursorUserId 두개를 동시에 넣어야한다. 각각 마지막으로 받은 유저의 점수와 유저 아이디이다.',
})
@ApiBearerAuth('accessToken')
@ApiOkResponse({
description: '깃허브 랭킹 반환',
type: [RankListDto],
})
@ApiUnauthorizedResponse({
description: 'jwt 관련 문제 (인증 시간이 만료됨, jwt를 보내지 않음)',
})
@ApiForbiddenResponse({
description: '허용되지 않은 자원에 접근한 경우. 즉, 권한이 없는 경우',
})
@ApiInternalServerErrorResponse({
description: '서버 오류',
})
async findGradeRank(@Query() options: RankListOptionDto) {
return await this.gradeService.getGradeRank(options);
}

@Get('total')
@ApiTags('rank')
@ApiOperation({
summary: '종합 전체 랭킹 API',
description:
'학점 역량의 랭킹리스트를 반환한다. 페이지네이션이 가능하고 학과 별로 필터링 가능하다. (학번 필터링은 아직 미구현) 커서 사용시 cursorPoint, cursorUserId 두개를 동시에 넣어야한다. 각각 마지막으로 받은 유저의 점수와 유저 아이디이다.',
})
@ApiBearerAuth('accessToken')
@ApiOkResponse({
description: '종합 랭킹 반환',
type: [RankListDto],
})
@ApiUnauthorizedResponse({
description: 'jwt 관련 문제 (인증 시간이 만료됨, jwt를 보내지 않음)',
})
@ApiForbiddenResponse({
description: '허용되지 않은 자원에 접근한 경우. 즉, 권한이 없는 경우',
})
@ApiInternalServerErrorResponse({
description: '서버 오류',
})
async findTotalRank(@Query() options: RankListOptionDto) {
return await this.totalService.getTotalRank(options);
}

@ApiTags('rank')
@ApiOperation({
summary: '유저의 각 부문 별 랭킹 API',
Expand All @@ -73,10 +147,14 @@ export class RankController {
@ApiInternalServerErrorResponse({
description: '서버 오류',
})
async findUsersRank(@Param('id') userId, @Query() options: PointFindDto) {
@Get('/users/:id')
async findUsersRank(
@Param('id') userId: string,
@Query() options: PointFindDto,
) {
const user = await this.userService.findUserByUserId(userId);

if (options && user.major !== options.major) {
if (options.major && user.major !== options.major) {
return new RankFindDto(null, null, null, null);
} else {
const algorithmRank =
Expand Down
40 changes: 6 additions & 34 deletions src/stat/repository/algorithm.repository.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { BaseRepository } from '../../utils/base.repository';
import { Inject, Injectable, Scope } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { REQUEST } from '@nestjs/core';
import { Algorithm } from '../../Entity/algorithm';
import { RankListDto, RankListOptionDto } from '../dto/rank-list-option.dto';
import { RankListOptionDto } from '../dto/rank-list-option.dto';
import { User } from '../../Entity/user';
import { RankController } from '../rank.controller';
import { PointFindDto } from '../dto/rank-find.dto';
import { StatRepository } from '../../utils/stat.repository';

@Injectable({ scope: Scope.REQUEST })
export class AlgorithmRepository extends BaseRepository {
private repository: Repository<Algorithm>;
export class AlgorithmRepository extends StatRepository {
constructor(dataSource: DataSource, @Inject(REQUEST) req: Request) {
super(dataSource, req);
this.repository = this.getRepository(Algorithm);
super(dataSource, req, Algorithm);
}

async save(algorithm: Algorithm) {
Expand All @@ -23,31 +20,6 @@ export class AlgorithmRepository extends BaseRepository {
async findOneById(userId: string) {
return await this.repository.findOneBy({ userId: userId });
}

async findAlgorithmWithRank(
options: RankListOptionDto,
): Promise<[RankListDto]> {
const queryBuilder = this.repository
.createQueryBuilder()
.select(['b.rank', 'b.user_id', 'b.point', 'b.nickname'])
.distinct(true)
.from((sub) => {
return sub
.select('RANK() OVER (ORDER BY a.point DESC)', 'rank')
.addSelect('a.user_id', 'user_id')
.addSelect('a.point', 'point')
.addSelect('u.nickname', 'nickname')
.from(Algorithm, 'a')
.innerJoin(User, 'u', 'a.user_id = u.user_id')
.where(this.createClassificationOption(options));
}, 'b')
.where(this.createCursorOption(options))
.orderBy('point', 'DESC')
.addOrderBy('user_id')
.limit(3);
return await (<Promise<[RankListDto]>>queryBuilder.getRawMany());
}

public async findIndividualAlgorithmRank(
userId: string,
options: PointFindDto,
Expand All @@ -66,7 +38,7 @@ export class AlgorithmRepository extends BaseRepository {
.addSelect('u.major', 'major')
.where(this.createClassificationOption(options));
}, 'b')
.where(`b.user_id = ${userId}`);
.where(`b.user_id = '${userId}'`);

return await queryBuilder.getRawOne();
}
Expand Down
47 changes: 5 additions & 42 deletions src/stat/repository/github.repository.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { BaseRepository } from '../../utils/base.repository';
import { Inject, Injectable, Scope } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { Github } from '../../Entity/github';
import { DataSource, Repository } from 'typeorm';
import { DataSource } from 'typeorm';
import { REQUEST } from '@nestjs/core';
import { RankListOptionDto } from '../dto/rank-list-option.dto';
import { Algorithm } from '../../Entity/algorithm';
import { User } from '../../Entity/user';
import { PointFindDto } from '../dto/rank-find.dto';
import { StatRepository } from '../../utils/stat.repository';

@Injectable()
export class GithubRepository extends BaseRepository {
private repository: Repository<Github>;

export class GithubRepository extends StatRepository {
constructor(dataSource: DataSource, @Inject(REQUEST) req: Request) {
super(dataSource, req);
this.repository = this.getRepository(Github);
super(dataSource, req, Github);
}

public async save(github: Github) {
Expand Down Expand Up @@ -43,34 +36,4 @@ export class GithubRepository extends BaseRepository {
public async findAll() {
return await this.repository.find();
}

public async findIndividualGithubRank(
userId: string,
options: PointFindDto,
) {
const queryBuilder = this.repository
.createQueryBuilder()
.select(['b.rank', 'b.user_id'])
.distinct(true)
.from((sub) => {
return sub
.select('RANK() OVER (ORDER BY g.point DESC)', 'rank')
.addSelect('g.user_id', 'user_id')
.addSelect('g.point', 'point')
.from(Github, 'g')
.innerJoin(User, 'u', 'g.user_id = u.user_id')
.where(this.createClassificationOption(options));
}, 'b')
.where(`b.user_id = ${userId}`);

return await queryBuilder.getRawOne();
}

createClassificationOption(options: PointFindDto) {
if (options.major != null) {
return `u.major like '${options.major}'`;
} else {
return `u.id > 0`;
}
}
}
45 changes: 4 additions & 41 deletions src/stat/repository/grade.repository.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import { BaseRepository } from '../../utils/base.repository';
import { Inject, Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { Github } from '../../Entity/github';
import { DataSource } from 'typeorm';
import { REQUEST } from '@nestjs/core';
import { Grade } from '../../Entity/grade';
import { RankListOptionDto } from '../dto/rank-list-option.dto';
import { User } from '../../Entity/user';
import { PointFindDto } from '../dto/rank-find.dto';
import { StatRepository } from '../../utils/stat.repository';

@Injectable()
export class GradeRepository extends BaseRepository {
private repository: Repository<Grade>;

export class GradeRepository extends StatRepository {
constructor(dataSource: DataSource, @Inject(REQUEST) req: Request) {
super(dataSource, req);
this.repository = this.getRepository(Grade);
super(dataSource, req, Grade);
}

public async save(grade: Grade) {
Expand All @@ -38,34 +31,4 @@ export class GradeRepository extends BaseRepository {
public async delete(id: string) {
await this.repository.delete({ userId: id });
}

public async findIndividualGradeRank(
userId: string,
options: PointFindDto,
) {
const queryBuilder = this.repository
.createQueryBuilder()
.select(['b.rank', 'b.user_id'])
.distinct(true)
.from((sub) => {
return sub
.select('RANK() OVER (ORDER BY g.point DESC)', 'rank')
.addSelect('g.user_id', 'user_id')
.addSelect('g.point', 'point')
.from(Grade, 'g')
.innerJoin(User, 'u', 'g.user_id = u.user_id')
.where(this.createClassificationOption(options));
}, 'b')
.where(`b.user_id = ${userId}`);

return await queryBuilder.getRawOne();
}

createClassificationOption(options: PointFindDto) {
if (options.major != null) {
return `u.major like '${options.major}'`;
} else {
return `u.id > 0`;
}
}
}
47 changes: 4 additions & 43 deletions src/stat/repository/total.repository.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import { BaseRepository } from '../../utils/base.repository';
import { DataSource, Repository } from 'typeorm';
import { Grade } from '../../Entity/grade';
import { DataSource } from 'typeorm';
import { Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { TotalPoint } from '../../Entity/totalPoint';
import { RankListOptionDto } from '../dto/rank-list-option.dto';
import { Algorithm } from '../../Entity/algorithm';
import { User } from '../../Entity/user';
import { PointFindDto } from '../dto/rank-find.dto';

export class TotalRepository extends BaseRepository {
private repository: Repository<TotalPoint>;
import { StatRepository } from '../../utils/stat.repository';

export class TotalRepository extends StatRepository {
constructor(dataSource: DataSource, @Inject(REQUEST) req: Request) {
super(dataSource, req);
this.repository = this.getRepository(TotalPoint);
super(dataSource, req, TotalPoint);
}

async findOneById(userId: string) {
Expand All @@ -28,35 +20,4 @@ export class TotalRepository extends BaseRepository {
async save(total: TotalPoint) {
return await this.repository.save(total);
}

public async findIndividualAlgorithmRank(
userId: string,
options: PointFindDto,
) {
const queryBuilder = this.repository
.createQueryBuilder()
.select(['b.rank', 'b.user_id', 'b.major'])
.distinct(true)
.from((sub) => {
return sub
.select('RANK() OVER (ORDER BY a.point DESC)', 'rank')
.addSelect('a.user_id', 'user_id')
.addSelect('a.point', 'point')
.from(Algorithm, 'a')
.innerJoin(User, 'u', 'a.user_id = u.user_id')
.addSelect('u.major', 'major')
.where(this.createClassificationOption(options));
}, 'b')
.where(`b.user_id = ${userId}`);

return await queryBuilder.getRawOne();
}

createClassificationOption(options: PointFindDto) {
if (options.major != null) {
return `u.major like '${options.major}'`;
} else {
return `u.id > 0`;
}
}
}
Loading
Loading