Skip to content

Commit a9d6845

Browse files
committed
feature: 레포 계층에만 viewGrowth 기반 조회 메서드 추가
1 parent 6ba4531 commit a9d6845

File tree

3 files changed

+110
-8
lines changed

3 files changed

+110
-8
lines changed

src/repositories/post.repository.ts

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { DBError } from '@/exception';
55
export class PostRepository {
66
constructor(private pool: Pool) { }
77

8-
async findPostsByUserId(userId: number, cursor?: string, sort?: string, isAsc?: boolean, limit: number = 15) {
8+
async findPostsByUserId(
9+
userId: number,
10+
cursor?: string,
11+
sort?: string,
12+
isAsc: boolean = false,
13+
limit: number = 15
14+
) {
915
try {
1016
// 1) 정렬 컬럼 매핑
1117
let sortCol = 'p.released_at';
@@ -62,10 +68,7 @@ export class PostRepository {
6268
pds.date
6369
FROM posts_post p
6470
LEFT JOIN (
65-
SELECT post_id,
66-
daily_view_count,
67-
daily_like_count,
68-
date
71+
SELECT post_id, daily_view_count, daily_like_count, date
6972
FROM posts_postdailystatistics
7073
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC')::date
7174
) pds ON p.id = pds.post_id
@@ -116,6 +119,98 @@ export class PostRepository {
116119
}
117120
}
118121

122+
// findPostsByUserId 와 동일
123+
// view_growth, like_growth 컬럼 추가 연산
124+
async findPostsByUserIdWithGrowthMetrics(
125+
userId: number,
126+
cursor?: string,
127+
isAsc: boolean = false,
128+
limit: number = 15
129+
) {
130+
try {
131+
const selectFields = `
132+
p.id,
133+
p.title,
134+
p.slug,
135+
p.created_at AS post_created_at,
136+
p.released_at AS post_released_at,
137+
COALESCE(pds.daily_view_count, 0) AS daily_view_count,
138+
COALESCE(pds.daily_like_count, 0) AS daily_like_count,
139+
COALESCE(yesterday_stats.daily_view_count, 0) AS yesterday_daily_view_count,
140+
COALESCE(yesterday_stats.daily_like_count, 0) AS yesterday_daily_like_count,
141+
pds.date,
142+
(COALESCE(pds.daily_view_count, 0) - COALESCE(yesterday_stats.daily_view_count, 0)) AS view_growth,
143+
(COALESCE(pds.daily_like_count, 0) - COALESCE(yesterday_stats.daily_like_count, 0)) AS like_growth
144+
`;
145+
146+
const direction = isAsc ? 'ASC' : 'DESC';
147+
const orderByExpression = `view_growth ${direction}, p.id ${direction}`;
148+
149+
// 커서 처리
150+
let cursorCondition = '';
151+
let params: unknown[] = [];
152+
153+
if (cursor) {
154+
const [cursorSortValue, cursorId] = cursor.split(',');
155+
156+
cursorCondition = `
157+
AND (
158+
(COALESCE(pds.daily_view_count, 0) - COALESCE(yesterday_stats.daily_view_count, 0)) ${isAsc ? '>' : '<'} $2
159+
OR (
160+
(COALESCE(pds.daily_view_count, 0) - COALESCE(yesterday_stats.daily_view_count, 0)) = $2
161+
AND p.id ${isAsc ? '>' : '<'} $3
162+
)
163+
)
164+
`;
165+
166+
params = [userId, cursorSortValue, cursorId, limit];
167+
} else {
168+
params = [userId, limit];
169+
}
170+
171+
const query = `
172+
SELECT ${selectFields}
173+
FROM posts_post p
174+
LEFT JOIN (
175+
SELECT post_id, daily_view_count, daily_like_count, date
176+
FROM posts_postdailystatistics
177+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC')::date
178+
) pds ON p.id = pds.post_id
179+
LEFT JOIN (
180+
SELECT post_id, daily_view_count, daily_like_count, date
181+
FROM posts_postdailystatistics
182+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
183+
) yesterday_stats ON p.id = yesterday_stats.post_id
184+
WHERE p.user_id = $1
185+
AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL)
186+
${cursorCondition}
187+
ORDER BY ${orderByExpression}
188+
LIMIT ${cursor ? '$4' : '$2'}
189+
`;
190+
191+
const posts = await this.pool.query(query, params);
192+
193+
if (posts.rows.length === 0) {
194+
return {
195+
posts: [],
196+
nextCursor: null,
197+
};
198+
}
199+
200+
// 다음 커서 생성
201+
const lastPost = posts.rows[posts.rows.length - 1];
202+
const nextCursor = `${lastPost.view_growth},${lastPost.id}`;
203+
204+
return {
205+
posts: posts.rows,
206+
nextCursor,
207+
};
208+
} catch (error) {
209+
logger.error('Post Repo findPostsByUserIdWithGrowthMetrics error: ', error);
210+
throw new DBError('트래픽 성장률 기준 post 조회 중 문제가 발생했습니다.');
211+
}
212+
}
213+
119214
async getTotalPostCounts(id: number) {
120215
try {
121216
const query = 'SELECT COUNT(*) FROM "posts_post" WHERE user_id = $1';

src/services/post.service.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ export class PostService {
77

88
async getAllposts(userId: number, cursor?: string, sort: string = '', isAsc?: boolean, limit: number = 15) {
99
try {
10-
const result = await this.postRepo.findPostsByUserId(userId, cursor, sort, isAsc, limit);
10+
let result = null;
11+
if (sort === "viewGrowth") {
12+
result = await this.postRepo.findPostsByUserIdWithGrowthMetrics(userId, cursor, isAsc, limit);
13+
}
14+
else {
15+
result = await this.postRepo.findPostsByUserId(userId, cursor, sort, isAsc, limit);
16+
}
1117

1218
const transformedPosts = result.posts.map((post) => ({
1319
id: post.id,

src/types/dto/requests/getAllPostsQuery.type.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator';
77
* schemas:
88
* PostSortType:
99
* type: string
10-
* enum: ['', 'dailyViewCount', 'dailyLikeCount']
10+
* enum: ['', 'dailyViewCount', 'dailyLikeCount', 'viewGrowth']
1111
* description: |
1212
* 포스트 정렬 기준
1313
* * '' - 작성일
1414
* * 'dailyViewCount' - 조회수
1515
* * 'dailyLikeCount' - 좋아요수
16+
* * 'viewGrowth' - 조회수 증가량
1617
* default: ''
1718
*/
18-
export type PostSortType = '' | 'dailyViewCount' | 'dailyLikeCount';
19+
export type PostSortType = '' | 'dailyViewCount' | 'dailyLikeCount' | 'viewGrowth';
1920

2021
export interface GetAllPostsQuery {
2122
cursor?: string;

0 commit comments

Comments
 (0)