Skip to content

[25.06.27 / TASK-210] Feature - 리더보드 username 대응 개발 #37

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

Merged
merged 3 commits into from
Jun 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { PostLeaderboardSortType, UserLeaderboardSortType } from '@/types';

dotenv.config();

jest.setTimeout(20000); // 각 케이스당 20초 타임아웃 설정
jest.setTimeout(60000); // 각 케이스당 60초 타임아웃 설정

/**
* LeaderboardRepository 통합 테스트
Expand Down Expand Up @@ -44,7 +44,7 @@ describe('LeaderboardRepository 통합 테스트', () => {
idleTimeoutMillis: 30000, // 연결 유휴 시간 (30초)
connectionTimeoutMillis: 5000, // 연결 시간 초과 (5초)
allowExitOnIdle: false, // 유휴 상태에서 종료 허용
statement_timeout: 30000,
statement_timeout: 60000, // 쿼리 타임아웃 증가 (60초)
};

// localhost 가 아니면 ssl 필수
Expand Down Expand Up @@ -105,6 +105,7 @@ describe('LeaderboardRepository 통합 테스트', () => {
result.forEach((leaderboardUser) => {
expect(leaderboardUser).toHaveProperty('id');
expect(leaderboardUser).toHaveProperty('email');
expect(leaderboardUser).toHaveProperty('username');
expect(leaderboardUser).toHaveProperty('total_views');
expect(leaderboardUser).toHaveProperty('total_likes');
expect(leaderboardUser).toHaveProperty('total_posts');
Expand Down Expand Up @@ -214,13 +215,13 @@ describe('LeaderboardRepository 통합 테스트', () => {
}
});

it('email이 null인 사용자는 제외되어야 한다', async () => {
it('username이 null인 사용자는 제외되어야 한다', async () => {
const result = await repo.getUserLeaderboard(DEFAULT_PARAMS.USER_SORT, DEFAULT_PARAMS.DATE_RANGE, 30);

if (!isEnoughData(result, 1, '사용자 리더보드 email null 제외')) return;
if (!isEnoughData(result, 1, '사용자 리더보드 username null 제외')) return;

result.forEach((user) => {
expect(user.email).not.toBeNull();
expect(user.username).not.toBeNull();
});
});
});
Expand All @@ -241,6 +242,7 @@ describe('LeaderboardRepository 통합 테스트', () => {
expect(leaderboardPost).toHaveProperty('id');
expect(leaderboardPost).toHaveProperty('title');
expect(leaderboardPost).toHaveProperty('slug');
expect(leaderboardPost).toHaveProperty('username');
expect(leaderboardPost).toHaveProperty('total_views');
expect(leaderboardPost).toHaveProperty('total_likes');
expect(leaderboardPost).toHaveProperty('view_diff');
Expand Down
9 changes: 6 additions & 3 deletions src/repositories/__test__/leaderboard.repo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { mockPool, createMockQueryResult } from '@/utils/fixtures';

jest.mock('pg');


describe('LeaderboardRepository', () => {
let repo: LeaderboardRepository;

Expand All @@ -20,6 +19,7 @@ describe('LeaderboardRepository', () => {
{
id: '1',
email: '[email protected]',
username: 'test',
total_views: 100,
total_likes: 50,
total_posts: 1,
Expand All @@ -30,6 +30,7 @@ describe('LeaderboardRepository', () => {
{
id: '2',
email: '[email protected]',
username: 'test2',
total_views: 200,
total_likes: 100,
total_posts: 2,
Expand Down Expand Up @@ -79,7 +80,7 @@ describe('LeaderboardRepository', () => {

expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('WHERE date >='), // pastDateKST를 사용하는 부분 확인
[expect.any(Number)] // limit
[expect.any(Number)], // limit
);
});

Expand All @@ -96,6 +97,7 @@ describe('LeaderboardRepository', () => {
id: '2',
title: 'test2',
slug: 'test2',
username: 'test2',
total_views: 200,
total_likes: 100,
view_diff: 20,
Expand All @@ -106,6 +108,7 @@ describe('LeaderboardRepository', () => {
id: '1',
title: 'test',
slug: 'test',
username: 'test',
total_views: 100,
total_likes: 50,
view_diff: 10,
Expand Down Expand Up @@ -154,7 +157,7 @@ describe('LeaderboardRepository', () => {

expect(mockPool.query).toHaveBeenCalledWith(
expect.stringContaining('WHERE date >='), // pastDateKST를 사용하는 부분 확인
[expect.any(Number)] // limit
[expect.any(Number)], // limit
Copy link
Member

@six-standard six-standard Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분과 위의 동일한 코드는 단순 린팅 적용이나 변경일까요?
콤마 하나만 수정되어있는데, 왜 추가된건지 궁금합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 단순 린팅 적용이 맞습니다! 😅

);
});

Expand Down
7 changes: 5 additions & 2 deletions src/repositories/leaderboard.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class LeaderboardRepository {
SELECT
u.id AS id,
u.email AS email,
u.username AS username,
COALESCE(SUM(ts.today_view), 0) AS total_views,
COALESCE(SUM(ts.today_like), 0) AS total_likes,
COUNT(DISTINCT CASE WHEN p.is_active = true THEN p.id END) AS total_posts,
Expand All @@ -27,8 +28,8 @@ export class LeaderboardRepository {
LEFT JOIN posts_post p ON p.user_id = u.id
LEFT JOIN today_stats ts ON ts.post_id = p.id
LEFT JOIN start_stats ss ON ss.post_id = p.id
WHERE u.email IS NOT NULL
GROUP BY u.id, u.email
WHERE u.username IS NOT NULL
GROUP BY u.id, u.email, u.username
ORDER BY ${this.SORT_COL_MAPPING[sort]} DESC, u.id
LIMIT $1;
`;
Expand All @@ -52,11 +53,13 @@ export class LeaderboardRepository {
p.title,
p.slug,
p.released_at,
u.username AS username,
COALESCE(ts.today_view, 0) AS total_views,
COALESCE(ts.today_like, 0) AS total_likes,
COALESCE(ts.today_view, 0) - COALESCE(ss.start_view, COALESCE(ts.today_view, 0)) AS view_diff,
COALESCE(ts.today_like, 0) - COALESCE(ss.start_like, COALESCE(ts.today_like, 0)) AS like_diff
FROM posts_post p
LEFT JOIN users_user u ON u.id = p.user_id
LEFT JOIN today_stats ts ON ts.post_id = p.id
LEFT JOIN start_stats ss ON ss.post_id = p.id
WHERE p.is_active = true
Expand Down
8 changes: 8 additions & 0 deletions src/services/__test__/leaderboard.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('LeaderboardService', () => {
{
id: '1',
email: '[email protected]',
username: 'test',
total_views: '100',
total_likes: '50',
total_posts: '1',
Expand All @@ -40,6 +41,7 @@ describe('LeaderboardService', () => {
{
id: '2',
email: '[email protected]',
username: 'test2',
total_views: '200',
total_likes: '100',
total_posts: '2',
Expand All @@ -54,6 +56,7 @@ describe('LeaderboardService', () => {
{
id: '1',
email: '[email protected]',
username: 'test',
totalViews: 100,
totalLikes: 50,
totalPosts: 1,
Expand All @@ -64,6 +67,7 @@ describe('LeaderboardService', () => {
{
id: '2',
email: '[email protected]',
username: 'test2',
totalViews: 200,
totalLikes: 100,
totalPosts: 2,
Expand Down Expand Up @@ -121,6 +125,7 @@ describe('LeaderboardService', () => {
id: '1',
title: 'test',
slug: 'test-slug',
username: 'test',
total_views: '100',
total_likes: '50',
view_diff: '20',
Expand All @@ -131,6 +136,7 @@ describe('LeaderboardService', () => {
id: '2',
title: 'test2',
slug: 'test2-slug',
username: 'test2',
total_views: '200',
total_likes: '100',
view_diff: '10',
Expand All @@ -145,6 +151,7 @@ describe('LeaderboardService', () => {
id: '1',
title: 'test',
slug: 'test-slug',
username: 'test',
totalViews: 100,
totalLikes: 50,
viewDiff: 20,
Expand All @@ -155,6 +162,7 @@ describe('LeaderboardService', () => {
id: '2',
title: 'test2',
slug: 'test2-slug',
username: 'test2',
totalViews: 200,
totalLikes: 100,
viewDiff: 10,
Expand Down
22 changes: 13 additions & 9 deletions src/services/leaderboard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export class LeaderboardService {
private mapRawUserResult(rawResult: RawUserResult[]): UserLeaderboardData {
const users = rawResult.map((user) => ({
id: user.id,
email: user.email,
email: user.email || null,
username: user.username,
totalViews: Number(user.total_views),
totalLikes: Number(user.total_likes),
totalPosts: Number(user.total_posts),
Expand All @@ -58,6 +59,7 @@ export class LeaderboardService {
id: post.id,
title: post.title,
slug: post.slug,
username: post.username || null,
totalViews: Number(post.total_views),
totalLikes: Number(post.total_likes),
viewDiff: Number(post.view_diff),
Expand All @@ -69,24 +71,26 @@ export class LeaderboardService {
}
}

interface RawPostResult {
interface RawUserResult {
id: string;
title: string;
slug: string;
email: string | null;
username: string;
total_views: string;
total_likes: string;
total_posts: string;
view_diff: string;
like_diff: string;
released_at: string;
post_diff: string;
}

interface RawUserResult {
interface RawPostResult {
id: string;
email: string;
title: string;
slug: string;
username: string | null;
total_views: string;
total_likes: string;
total_posts: string;
view_diff: string;
like_diff: string;
post_diff: string;
released_at: string;
}
12 changes: 11 additions & 1 deletion src/types/dto/responses/leaderboardResponse.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import { BaseResponseDto } from '@/types/dto/responses/baseResponse.type';
* email:
* type: string
* description: 사용자 이메일
* nullable: true
* username:
* type: string
* description: 사용자 이름
* totalViews:
* type: integer
* description: 누적 조회수
Expand All @@ -34,7 +38,8 @@ import { BaseResponseDto } from '@/types/dto/responses/baseResponse.type';
*/
interface LeaderboardUser {
id: string;
email: string;
email: string | null;
username: string;
totalViews: number;
totalLikes: number;
totalPosts: number;
Expand Down Expand Up @@ -89,6 +94,10 @@ export class UserLeaderboardResponseDto extends BaseResponseDto<UserLeaderboardD
* slug:
* type: string
* description: 게시물 url slug 값
* username:
* type: string
* nullable: true
* description: 게시물 작성자 이름
* totalViews:
* type: integer
* description: 누적 조회수
Expand All @@ -110,6 +119,7 @@ interface LeaderboardPost {
id: string;
title: string;
slug: string;
username: string | null;
totalViews: number;
totalLikes: number;
viewDiff: number;
Expand Down
Loading