Skip to content

Commit

Permalink
πŸ”§ fix : query λ³„λ‘œ 응닡이 μ•„λ‹Œ ν•œλ²ˆμ˜ μš”μ²­μ— λ‘κ°œμ˜ λž­ν‚Ή λͺ¨λ‘ μ‘λ‹΅ν•˜κ²Œ λ³€κ²½(#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
jinddings committed Nov 21, 2024
1 parent 69efe27 commit d3cfc84
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 67 deletions.
23 changes: 23 additions & 0 deletions BE/src/ranking/dto/ranking-data.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';

export class RankingDataDto {
@ApiProperty({
description: 'μ‚¬μš©μž λ‹‰λ„€μž„',
example: 'trader123',
})
nickname: string;

@ApiProperty({
description: '수읡λ₯  (%)',
example: 15.7,
required: false,
})
profitRate?: number;

@ApiProperty({
description: '총 μžμ‚°',
example: 1000000,
required: false,
})
totalAsset?: number;
}
9 changes: 5 additions & 4 deletions BE/src/ranking/dto/ranking-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { RankingResultDto } from './ranking-result.dto';

export class RankingResponseDto {
@ApiProperty({
description: 'top 10 μœ μ € λž­ν‚Ή',
description: '수읡λ₯  λž­ν‚Ή',
})
topRank: string[];
profitRateRanking: RankingResultDto;

@ApiProperty({
description: '둜그인 ν•œ μœ μ €μ˜ λž­ν‚Ή',
description: 'μžμ‚° λž­ν‚Ή',
})
userRank?: number | null;
assetRanking: RankingResultDto;
}
17 changes: 17 additions & 0 deletions BE/src/ranking/dto/ranking-result.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { RankingDataDto } from './ranking-data.dto';

export class RankingResultDto {
@ApiProperty({
description: 'μƒμœ„ 10λͺ…μ˜ λž­ν‚Ή 데이터',
type: [RankingDataDto],
})
topRank: RankingDataDto[];

@ApiProperty({
description: 'ν˜„μž¬ μ‚¬μš©μžμ˜ μˆœμœ„ (없을 경우 null)',
example: 42,
nullable: true,
})
userRank: number | null;
}
5 changes: 5 additions & 0 deletions BE/src/ranking/interface/ranking-data.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface RankingData {
nickname: string;
profitRate?: number;
totalAsset?: number;
}
14 changes: 4 additions & 10 deletions BE/src/ranking/ranking.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { OptionalAuthGuard } from 'src/auth/optional-auth-guard';
import { RankingService } from './ranking.service';
import { RankingResponseDto } from './dto/ranking-response.dto';
import { SortType } from './enum/sort-type.enum';

@Controller('/api/ranking')
@ApiTags('λž­ν‚Ή API')
Expand All @@ -22,18 +21,13 @@ export class RankingController {
@ApiQuery({
name: 'sortBy',
required: false,
description: 'profitRate: 수읡λ₯ μˆœ, totalAsset: μžμ‚°μˆœ',
enum: ['profitRate', 'totalAsset'],
})
async getRanking(
@Req() req: Request,
@Query('sortBy') sortBy: SortType = SortType.PROFIT_RATE,
): Promise<RankingResponseDto> {
async getRanking(@Req() req: Request): Promise<RankingResponseDto> {
if (!req.user) {
return this.rankingService.getRanking(sortBy);
return this.rankingService.getRanking();
}

const { nickname } = req.user;
return this.rankingService.getRankingAuthUser(nickname, sortBy);
return this.rankingService.getRankingAuthUser(nickname);
}
}
120 changes: 67 additions & 53 deletions BE/src/ranking/ranking.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { AssetRepository } from 'src/asset/asset.repository';
import { Cron } from '@nestjs/schedule';
import { SortType } from './enum/sort-type.enum';
import { Ranking } from './interface/ranking.interface';
import { RankingResponseDto } from './dto/ranking-response.dto';
import { RankingResultDto } from './dto/ranking-result.dto';
import { RankingDataDto } from './dto/ranking-data.dto';

@Injectable()
export class RankingService {
Expand All @@ -12,69 +15,74 @@ export class RankingService {
private readonly redisDomainService: RedisDomainService,
) {}

async getRanking(sortBy: SortType = SortType.PROFIT_RATE) {
const date = new Date().toISOString().slice(0, 10);
const key = `ranking:${date}:${sortBy}`;

if (await this.redisDomainService.exists(key)) {
return {
topRank: await this.redisDomainService.zrevrange(key, 0, 9),
userRank: null,
};
}

const ranking = await this.calculateRanking(sortBy);
async getRanking(): Promise<RankingResponseDto> {
const profitRateRanking = await this.getRankingData(SortType.PROFIT_RATE);
const assetRanking = await this.getRankingData(SortType.ASSET);

await Promise.all(
ranking.map((rank: Ranking) =>
this.redisDomainService.zadd(
key,
this.getSortScore(rank, sortBy),
rank.nickname,
),
),
);
return { profitRateRanking, assetRanking };
}

return {
topRank: await this.redisDomainService.zrevrange(key, 0, 9),
userRank: null,
};
async getRankingAuthUser(nickname: string): Promise<RankingResponseDto> {
const profitRateRanking = await this.getRankingData(SortType.PROFIT_RATE, {
nickname,
});
const assetRanking = await this.getRankingData(SortType.ASSET, {
nickname,
});
return { profitRateRanking, assetRanking };
}

async getRankingAuthUser(
nickname: string,
sortBy: SortType = SortType.PROFIT_RATE,
) {
private async getRankingData(
sortBy: SortType,
options: { nickname?: string } = { nickname: null },
): Promise<RankingResultDto> {
const date = new Date().toISOString().slice(0, 10);
const key = `ranking:${date}:${sortBy}`;

let userRank = null;

if (await this.redisDomainService.exists(key)) {
userRank = await this.redisDomainService.zrevrank(key, nickname);

return {
topRank: await this.redisDomainService.zrevrange(key, 0, 9),
userRank: userRank !== null ? userRank + 1 : null,
};
if (!(await this.redisDomainService.exists(key))) {
const ranking = await this.calculateRanking(sortBy);
await Promise.all(
ranking.map((rank: Ranking) =>
this.redisDomainService.zadd(
key,
this.getSortScore(rank, sortBy),
sortBy === SortType.PROFIT_RATE
? JSON.stringify({
nickname: rank.nickname,
profitRate: rank.profitRate,
})
: JSON.stringify({
nickname: rank.nickname,
totalAsset: rank.totalAsset,
}),
),
),
);
}

const ranking = await this.calculateRanking(sortBy);
await Promise.all(
ranking.map((rank: Ranking) =>
this.redisDomainService.zadd(
key,
this.getSortScore(rank, sortBy),
rank.nickname,
),
),
);
const [topRank, userRank] = await Promise.all([
this.redisDomainService.zrevrange(key, 0, 9),
options.nickname !== null
? this.redisDomainService.zrevrank(
key,
JSON.stringify({
nickname: options.nickname,
...(sortBy === SortType.PROFIT_RATE
? { profitRate: 0 }
: { totalAsset: 0 }),
}),
)
: null,
]);

userRank = await this.redisDomainService.zrevrank(key, nickname);
const parsedTopRank: RankingDataDto[] = topRank.map((rank) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
JSON.parse(rank),
);

return {
topRank: await this.redisDomainService.zrevrange(key, 0, 9),
userRank: userRank ? userRank + 1 : null,
topRank: parsedTopRank,
userRank: userRank !== null ? userRank + 1 : null,
};
}

Expand All @@ -100,7 +108,10 @@ export class RankingService {
this.redisDomainService.zadd(
profitRateKey,
rank.profitRate,
rank.nickname,
JSON.stringify({
nickname: rank.nickname,
profitRate: rank.profitRate,
}),
),
),
),
Expand All @@ -109,7 +120,10 @@ export class RankingService {
this.redisDomainService.zadd(
assetKey,
rank.totalAsset,
rank.nickname,
JSON.stringify({
nickname: rank.nickname,
totalAsset: rank.totalAsset,
}),
),
),
),
Expand Down

0 comments on commit d3cfc84

Please sign in to comment.