Skip to content

Commit 4f759ee

Browse files
authored
[24.12.30/ TASK-65] feature get post (#9)
* feture: 게시글 상게 조회 * hotfix: time zone 문제 해결 * modify: 상세 조회 FE와 intergration * modify: event API FE와 intergration * hotfix: 상세조회 date kst로 나오게 수정 * modify: back-office 와 log format 통일 * hotfix: 상세조회 카멜케이스로 변경
1 parent fbabcb9 commit 4f759ee

File tree

9 files changed

+98
-26
lines changed

9 files changed

+98
-26
lines changed

src/configs/logger.config.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,26 @@ if (!fs.existsSync(errorLogDir)) {
1212
fs.mkdirSync(errorLogDir);
1313
}
1414

15+
const jsonFormat = winston.format.printf((info) => {
16+
return JSON.stringify({
17+
timestamp: info.timestamp,
18+
level: info.level.toUpperCase(),
19+
logger: 'default',
20+
message: info.message,
21+
});
22+
});
23+
1524
const logger = winston.createLogger({
1625
format: winston.format.combine(
1726
winston.format.timestamp({
18-
format: 'YYYY-MM-DD HH:mm:ss',
19-
}),
20-
winston.format.printf((info) => {
21-
return `${info.timestamp} ${info.level}: ${info.message}`;
27+
format: 'YYYY-MM-DD HH:mm:ss.SSSZ',
2228
}),
29+
jsonFormat,
2330
),
2431
transports: [
2532
new winston.transports.Console({
2633
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
2734
}),
28-
2935
new winstonDaily({
3036
level: 'debug',
3137
datePattern: 'YYYY-MM-DD',
@@ -38,10 +44,11 @@ const logger = winston.createLogger({
3844
level: 'error',
3945
datePattern: 'YYYY-MM-DD',
4046
zippedArchive: true,
41-
filename: `%DATE%error.log`,
47+
filename: `%DATE%-error.log`,
4248
dirname: errorLogDir,
4349
maxFiles: '7d',
4450
}),
4551
],
4652
});
53+
4754
export default logger;

src/controllers/post.controller.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextFunction, Request, RequestHandler, Response } from 'express';
22
import logger from '../configs/logger.config';
33
import { PostService } from '../services/post.service';
44
import { GetAllPostsQuery, PostResponse } from '../types';
5+
import { GetPostQuery } from 'src/types/requests/getPostQuery.type';
56

67
export class PostController {
78
constructor(private postService: PostService) {}
@@ -17,7 +18,15 @@ export class PostController {
1718
isAsc: query.asc === 'true',
1819
};
1920
}
20-
21+
private validateQueryParams2(query: Partial<GetPostQuery>): {
22+
start: string;
23+
end: string;
24+
} {
25+
return {
26+
start: query.start || '',
27+
end: query.end || '',
28+
};
29+
}
2130
getAllPost: RequestHandler = async (
2231
req: Request<object, object, object, GetAllPostsQuery>,
2332
res: Response<PostResponse>,
@@ -43,6 +52,7 @@ export class PostController {
4352
next(error);
4453
}
4554
};
55+
4656
getAllPostStatistics: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
4757
try {
4858
const { id } = req.user;
@@ -61,4 +71,21 @@ export class PostController {
6171
next(error);
6272
}
6373
};
74+
75+
getPost: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
76+
try {
77+
const postId = parseInt(req.params.postId);
78+
const { start, end } = this.validateQueryParams2(req.query);
79+
const post = await this.postService.getPost(postId, start, end);
80+
res.status(200).json({
81+
success: true,
82+
message: 'post 단건 조회에 성공하였습니다',
83+
data: { post },
84+
error: null,
85+
});
86+
} catch (error) {
87+
logger.error('단건 조회 실패 : ', error);
88+
next(error);
89+
}
90+
};
6491
}

src/controllers/tracking.controller.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export class TrackingController {
88

99
event = (async (req: Request, res: Response<TrackingResponse>, next: NextFunction) => {
1010
try {
11-
const { type } = req.body;
11+
const { eventType } = req.body;
1212
const { id } = req.user;
1313

14-
await this.trackingService.tracking(type, id);
14+
await this.trackingService.tracking(eventType, id);
1515
return res.status(200).json({ success: true, message: '이벤트 데이터 저장완료', data: {}, error: null });
1616
} catch (error) {
1717
logger.error('user tracking 실패 : ', error);

src/repositories/post.repository.ts

+32-12
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,7 @@ import { DBError } from '../exception';
55
export class PostRepository {
66
constructor(private pool: Pool) {}
77

8-
async findPostsByUserId(
9-
userId: number,
10-
cursor?: string,
11-
sort?: string,
12-
isAsc?: boolean,
13-
limit: number = 15
14-
) {
8+
async findPostsByUserId(userId: number, cursor?: string, sort?: string, isAsc?: boolean, limit: number = 15) {
159
try {
1610
// 1) 정렬 컬럼 매핑
1711
let sortCol = 'p.released_at';
@@ -72,12 +66,12 @@ export class PostRepository {
7266
daily_like_count,
7367
date
7468
FROM posts_postdailystatistics
75-
WHERE date::date = NOW()::date
69+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC')::date
7670
) pds ON p.id = pds.post_id
7771
LEFT JOIN (
7872
SELECT post_id, daily_view_count, daily_like_count, date
7973
FROM posts_postdailystatistics
80-
WHERE date::date = (NOW() - INTERVAL '1 day')::date
74+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
8175
) yesterday_stats ON p.id = yesterday_stats.post_id
8276
WHERE p.user_id = $1
8377
AND (pds.post_id IS NOT NULL OR yesterday_stats.post_id IS NOT NULL)
@@ -133,7 +127,6 @@ export class PostRepository {
133127
}
134128

135129
async getYesterdayAndTodayViewLikeStats(userId: number) {
136-
137130
// pds.updated_at 은 FE 화면을 위해 억지로 9h 시간 더한 값임 주의
138131
try {
139132
const query = `
@@ -147,12 +140,12 @@ export class PostRepository {
147140
LEFT JOIN (
148141
SELECT post_id, daily_view_count, daily_like_count, updated_at
149142
FROM posts_postdailystatistics
150-
WHERE date::date = NOW()::date
143+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC')::date
151144
) pds ON p.id = pds.post_id
152145
LEFT JOIN (
153146
SELECT post_id, daily_view_count, daily_like_count
154147
FROM posts_postdailystatistics
155-
WHERE date::date = (NOW() - INTERVAL '1 day')::date
148+
WHERE (date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date = (NOW() AT TIME ZONE 'UTC' - INTERVAL '1 day')::date
156149
) yesterday_stats ON p.id = yesterday_stats.post_id
157150
WHERE p.user_id = $1
158151
`;
@@ -165,4 +158,31 @@ export class PostRepository {
165158
throw new DBError('전체 post 통계 조회 중 문제가 발생했습니다.');
166159
}
167160
}
161+
162+
async findPostByPostId(postId: number, start?: string, end?: string) {
163+
try {
164+
let query = `
165+
SELECT
166+
(pds.date AT TIME ZONE 'Asia/Seoul') AT TIME ZONE 'UTC' AS date,
167+
pds.daily_view_count,
168+
pds.daily_like_count
169+
FROM posts_postdailystatistics pds
170+
WHERE pds.post_id = $1
171+
`;
172+
173+
const values: (number | string)[] = [postId];
174+
175+
if (start && end) {
176+
query += ` AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date >= ($2 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date
177+
AND (pds.date AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date <= ($3 AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC')::date`;
178+
values.push(start, end);
179+
}
180+
181+
const result = await this.pool.query(query, values);
182+
return result.rows;
183+
} catch (error) {
184+
logger.error('Post Repo findPostByPostId error : ', error);
185+
throw new DBError('단건 post 조회 중 문제가 발생했습니다.');
186+
}
187+
}
168188
}

src/routes/post.router.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ const postController = new PostController(postService);
1515

1616
router.get('/posts', authMiddleware.verify, postController.getAllPost);
1717
router.get('/posts-stats', authMiddleware.verify, postController.getAllPostStatistics);
18+
router.get('/post/:postId', authMiddleware.verify, postController.getPost);
1819

1920
export default router;

src/services/post.service.ts

+13
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,20 @@ export class PostService {
4747
throw error;
4848
}
4949
}
50+
5051
async getTotalPostCounts(id: number) {
5152
return await this.postRepo.getTotalPostCounts(id);
5253
}
54+
55+
async getPost(postId: number, start?: string, end?: string) {
56+
const posts = await this.postRepo.findPostByPostId(postId, start, end);
57+
58+
const transformedPosts = posts.map((post) => ({
59+
date: post.date,
60+
dailyViewCount: parseInt(post.daily_view_count),
61+
dailyLikeCount: parseInt(post.daily_like_count),
62+
}));
63+
64+
return transformedPosts;
65+
}
5366
}

src/services/tracking.service.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { BadRequestError } from '../exception';
66
export class TrackingService {
77
constructor(private trackingRepo: TrackingRepository) {}
88

9-
async tracking(type: EventRequestDto, id: number) {
10-
return await this.trackingRepo.createEvent(type, id);
9+
async tracking(eventType: EventRequestDto, id: number) {
10+
return await this.trackingRepo.createEvent(eventType, id);
1111
}
1212
async stay(data: StayTimeRequestDto, userId: number) {
1313
try {

src/types/dto/eventRequest.dto.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { UserEventType } from '../userEvent.type';
44
export class EventRequestDto {
55
@IsEnum(UserEventType)
66
@IsNotEmpty()
7-
type: UserEventType;
7+
eventType: UserEventType;
88

9-
constructor(type: UserEventType) {
10-
this.type = type;
9+
constructor(eventType: UserEventType) {
10+
this.eventType = eventType;
1111
}
1212
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface GetPostQuery {
2+
start: string;
3+
end: string;
4+
}

0 commit comments

Comments
 (0)