Skip to content

Commit 59099f1

Browse files
authored
Merge pull request #414 from boostcampwm-2024/fix/like-jwt
♻️ refactor: 좋아요 조회 API 분리
2 parents 9128bd6 + 7a175fa commit 59099f1

File tree

14 files changed

+207
-50
lines changed

14 files changed

+207
-50
lines changed

docker-compose/init.sql

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ CREATE TABLE `feed` (
5454
`thumbnail` varchar(255) DEFAULT NULL,
5555
`blog_id` int NOT NULL,
5656
`summary` text,
57+
`like_count` int NOT NULL DEFAULT '0',
5758
PRIMARY KEY (`id`),
5859
UNIQUE KEY `IDX_cbdceca2d71f784a8bb160268e` (`path`),
5960
KEY `IDX_fda780ffdcc013b739cdc6f31d` (`created_at`),
@@ -67,20 +68,28 @@ CREATE TABLE `feed` (
6768
CREATE TABLE `user` (
6869
`id` int NOT NULL AUTO_INCREMENT,
6970
`email` varchar(255) NOT NULL,
70-
`password` varchar(60) NOT NULL,
71+
`password` varchar(60) DEFAULT NULL,
7172
`user_name` varchar(60) NOT NULL,
7273
`profile_image` varchar(255) DEFAULT NULL,
7374
`introduction` varchar(255) DEFAULT NULL,
75+
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
76+
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
77+
`totalViews` int NOT NULL DEFAULT '0',
78+
`currentStreak` int NOT NULL DEFAULT '0',
79+
`lastActiveDate` date DEFAULT NULL,
80+
`maxStreak` int NOT NULL DEFAULT '0',
7481
PRIMARY KEY (`id`)
7582
);
7683

7784
-- denamu.activity definition
85+
7886
CREATE TABLE `activity` (
7987
`id` int NOT NULL AUTO_INCREMENT,
8088
`activity_date` date NOT NULL,
8189
`view_count` int NOT NULL,
8290
`user_id` int DEFAULT NULL,
8391
PRIMARY KEY (`id`),
92+
UNIQUE KEY `IDX_78f3786d644ca9747fc82db9fb` (`user_id`,`activity_date`),
8493
KEY `FK_10bf0c2dd4736190070e8475119` (`user_id`),
8594
CONSTRAINT `FK_10bf0c2dd4736190070e8475119` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
8695
);
@@ -106,7 +115,7 @@ CREATE TABLE `tag_map` (
106115

107116
CREATE TABLE `comment` (
108117
`id` int NOT NULL AUTO_INCREMENT,
109-
`comment` text COLLATE utf8mb4_unicode_ci NOT NULL,
118+
`comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
110119
`date` datetime NOT NULL,
111120
`feed_id` int NOT NULL,
112121
`user_id` int NOT NULL,
@@ -117,6 +126,35 @@ CREATE TABLE `comment` (
117126
CONSTRAINT `FK_df1fd1eaf7cc0224ab5e829bf64` FOREIGN KEY (`feed_id`) REFERENCES `feed` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
118127
);
119128

129+
-- denamu.likes definition
130+
131+
CREATE TABLE `likes` (
132+
`id` int NOT NULL AUTO_INCREMENT,
133+
`feed_id` int NOT NULL,
134+
`user_id` int NOT NULL,
135+
`like_date` datetime NOT NULL,
136+
PRIMARY KEY (`id`),
137+
UNIQUE KEY `UQ_likes_user_feed` (`user_id`,`feed_id`),
138+
KEY `FK_like_feed` (`feed_id`),
139+
CONSTRAINT `FK_like_feed` FOREIGN KEY (`feed_id`) REFERENCES `feed` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
140+
CONSTRAINT `FK_like_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
141+
);
142+
143+
-- denamu.provider definition
144+
145+
CREATE TABLE `provider` (
146+
`id` int NOT NULL AUTO_INCREMENT,
147+
`provider_type` varchar(255) NOT NULL,
148+
`provider_user_id` varchar(255) NOT NULL,
149+
`refresh_token` varchar(255) NOT NULL,
150+
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
151+
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
152+
`user_id` int NOT NULL,
153+
PRIMARY KEY (`id`),
154+
KEY `FK_d3d18186b602240b93c9f1621ea` (`user_id`),
155+
CONSTRAINT `FK_d3d18186b602240b93c9f1621ea` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
156+
);
157+
120158
-- denamu.admin insert data
121159

122160
INSERT INTO admin (login_id, password) VALUES
@@ -236,10 +274,10 @@ INSERT INTO feed (created_at,title,view_count,`path`,thumbnail,blog_id) VALUES
236274
('2024-08-04 08:32:17','페어(짝) 프로그래밍에 대해서',0,'https://asn6878.tistory.com/7','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0I0n%2FbtsITiXYkG9%2FhpD50L7TcKlhU08D2jok4k%2Fimg.jpg',4),
237275
('2024-07-06 19:20:07','2024 네이버 부스트캠프 웹 · 모바일 2차 코딩테스트 후기',0,'https://asn6878.tistory.com/6','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzvZIm%2FbtsIpvcWnzY%2FnkR2JuxsNhKIyeeKHnMo1k%2Fimg.png',4),
238276
('2024-05-22 16:19:34','코딩테스트 준비를 위한 Java 입출력 정리',0,'https://asn6878.tistory.com/5','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYY34s%2FbtsHykim0k7%2FT7YBZJfvIEKvPmtLbXJkIk%2Fimg.png',4);
239-
INSERT INTO feed (created_at,title,view_count,`path`,thumbnail,blog_id,summary,likes) VALUES
277+
INSERT INTO feed (created_at,title,view_count,`path`,thumbnail,blog_id,summary,like_count) VALUES
240278
('2024-05-03 16:30:23','[Docker] 간단한 도커 명령어 모음집',2,'https://asn6878.tistory.com/4','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI3y45%2FbtsHcIbDPUe%2FpWNfGE2V3YX35MauB1Hb60%2Fimg.gif',4,NULL,0),
241279
('2024-03-10 08:49:55','Java record 에 대하여',0,'https://asn6878.tistory.com/3','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddtCkc%2FbtsFGEvHLSY%2FIPqWLZZfYlojZyLCB4dPg1%2Fimg.gif',4,NULL,0),
242-
('2024-01-04 11:37:46','인증(Authentication)과 인가(Authorization)의 개념에 대해',0,'https://asn6878.tistory.com/2','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Psk9%2FbtsC00h6SuP%2FZp2x8yPLdLLheMrGqJeHG0%2Fimg.png',4,NULL,1),
280+
('2024-01-04 11:37:46','인증(Authentication)과 인가(Authorization)의 개념에 대해',0,'https://asn6878.tistory.com/2','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Psk9%2FbtsC00h6SuP%2FZp2x8yPLdLLheMrGqJeHG0%2Fimg.png',4,NULL,0),
243281
('2025-01-16 19:29:50','NestJS + TypeORM + Testcontainers 를 사용한 통합 테스트 DB환경 구축하기',3,'https://asn6878.tistory.com/14','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2GhHh%2FbtsLPtpiK1d%2FtKiZjT4WEVz1sy4LIgFDn1%2Fimg.png',4,'**NestJS + TypeORM에서 Testcontainers로 MySQL 테스트 환경 구축하기 🐳**
244282
테스트는 프로덕션과 동일한 환경에서 진행되어야 신뢰할 수 있습니다! sqlite나 H2 같은 경량 DB 대신 실제 MySQL과 동일한 환경을 Docker로 구축해봅시다.
245283
구현 단계 📝
@@ -257,7 +295,7 @@ TypeORM 모듈에 환경변수 전달
257295
Jest 설정 파일 커스터마이징
258296
259297
🤔 흥미로운 점: GitHub Actions에서 실행 시간이 sqlite + 병렬 실행보다 약 2배 느려졌지만, 실제 프로덕션 환경과 동일한 테스트가 가능해졌습니다!
260-
테스트 안정성과 신뢰도를 높이고 싶은 NestJS 개발자라면 꼭 도입해볼 만한 구성입니다! 🚀'),
298+
테스트 안정성과 신뢰도를 높이고 싶은 NestJS 개발자라면 꼭 도입해볼 만한 구성입니다! 🚀',1),
261299
('2025-01-18 07:12:05','자바 vs 노드 당신의 선택은?!',4,'https://asn6878.tistory.com/15','https://img1.daumcdn.net/thumb/R800x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdofQSP%2FbtsLKJyhso1%2FREdhKR9vDlzDYREytkK0v1%2Fimg.png',4,'**Node.js와 Spring 프레임워크 비교 분석: 개발자의 선택은? 🤔**
262300
현재 TypeScript와 NestJS로 프로젝트를 진행 중인 개발자가 Java/Spring과 Node.js 생태계의 차이점을 깊이 있게 분석했습니다.
263301
채용 시장 현황 📊

server/src/feed/api-docs/readFeedDetail.api-docs.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export function ApiReadFeedDetail() {
3838
},
3939
},
4040
likes: { type: 'number' },
41-
isLike: { type: 'boolean' },
4241
},
4342
},
4443
},
@@ -57,7 +56,6 @@ export function ApiReadFeedDetail() {
5756
summary: '#example/n ### exexample',
5857
tag: ['tag1', 'tag2'],
5958
likes: 0,
60-
isLike: true,
6159
},
6260
},
6361
}),

server/src/feed/controller/feed.controller.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { InjectUserInterceptor } from './../../common/auth/jwt.interceptor';
21
import { ApiTags } from '@nestjs/swagger';
32
import { ApiResponse } from '../../common/response/common.response';
43
import {
@@ -30,7 +29,6 @@ import { FeedViewUpdateRequestDto } from '../dto/request/feed-update.dto';
3029
import { FeedDetailRequestDto } from '../dto/request/feed-detail.dto';
3130
import { ApiReadFeedDetail } from '../api-docs/readFeedDetail.api-docs';
3231
import { ReadFeedInterceptor } from '../interceptor/read-feed.interceptor';
33-
import { Payload } from '../../common/guard/jwt.guard';
3432

3533
@ApiTags('Feed')
3634
@Controller('feed')
@@ -121,17 +119,11 @@ export class FeedController {
121119
@ApiReadFeedDetail()
122120
@Get('/detail/:feedId')
123121
@HttpCode(HttpStatus.OK)
124-
@UseInterceptors(InjectUserInterceptor, ReadFeedInterceptor)
125-
async readFeedDetail(
126-
@Req() request: Request,
127-
@Param() feedDetailRequestDto: FeedDetailRequestDto,
128-
) {
122+
@UseInterceptors(ReadFeedInterceptor)
123+
async readFeedDetail(@Param() feedDetailRequestDto: FeedDetailRequestDto) {
129124
return ApiResponse.responseWithData(
130125
'요청이 성공적으로 처리되었습니다.',
131-
await this.feedService.readFeedDetail(
132-
feedDetailRequestDto,
133-
(request.user as Payload) ?? null,
134-
),
126+
await this.feedService.readFeedDetail(feedDetailRequestDto),
135127
);
136128
}
137129
}

server/src/feed/dto/response/feed-detail.dto.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ export class FeedDetailResponseDto {
1212
private viewCount: number,
1313
private summary: string,
1414
private likes: number,
15-
private isLike: boolean,
1615
private tag: string[],
1716
) {}
1817

19-
static toResponseDto(feed: FeedView, isLike: boolean) {
18+
static toResponseDto(feed: FeedView) {
2019
return new FeedDetailResponseDto(
2120
feed.feedId,
2221
feed.blogName,
@@ -28,7 +27,6 @@ export class FeedDetailResponseDto {
2827
feed.viewCount,
2928
feed.summary,
3029
feed.likeCount,
31-
isLike,
3230
feed.tag ? feed.tag : [],
3331
);
3432
}

server/src/feed/module/feed.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { ActivityModule } from '../../activity/module/activity.module';
1313
import { ReadFeedInterceptor } from '../interceptor/read-feed.interceptor';
1414
import { JwtAuthModule } from '../../common/auth/jwt.module';
1515
import { LikeModule } from '../../like/module/like.module';
16-
import { InjectUserInterceptor } from '../../common/auth/jwt.interceptor';
1716
import { CommentModule } from '../../comment/module/comment.module';
1817

1918
@Module({
@@ -33,7 +32,6 @@ import { CommentModule } from '../../comment/module/comment.module';
3332
FeedViewRepository,
3433
FeedScheduler,
3534
ReadFeedInterceptor,
36-
InjectUserInterceptor,
3735
],
3836
exports: [FeedRepository],
3937
})

server/src/feed/service/feed.service.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,13 @@ import {
3131
import { FeedViewUpdateRequestDto } from '../dto/request/feed-update.dto';
3232
import { FeedDetailRequestDto } from '../dto/request/feed-detail.dto';
3333
import { FeedDetailResponseDto } from '../dto/response/feed-detail.dto';
34-
import { LikeRepository } from '../../like/repository/like.repository';
35-
import { Payload } from '../../common/guard/jwt.guard';
36-
import { CommentRepository } from '../../comment/repository/comment.repository';
3734

3835
@Injectable()
3936
export class FeedService {
4037
constructor(
4138
private readonly feedRepository: FeedRepository,
4239
private readonly feedViewRepository: FeedViewRepository,
4340
private readonly redisService: RedisService,
44-
private readonly likeRepository: LikeRepository,
45-
private readonly commentRepository: CommentRepository,
4641
) {}
4742

4843
async readFeedPagination(feedPaginationQueryDto: FeedPaginationRequestDto) {
@@ -243,10 +238,7 @@ export class FeedService {
243238
return request.socket.remoteAddress;
244239
}
245240

246-
async readFeedDetail(
247-
feedDetailRequestDto: FeedDetailRequestDto,
248-
user: Payload | null,
249-
) {
241+
async readFeedDetail(feedDetailRequestDto: FeedDetailRequestDto) {
250242
const feed = await this.feedViewRepository.findOneBy({
251243
feedId: feedDetailRequestDto.feedId,
252244
});
@@ -256,16 +248,6 @@ export class FeedService {
256248
);
257249
}
258250

259-
let isLike = false;
260-
261-
if (user) {
262-
const like = await this.likeRepository.findOneBy({
263-
user: { id: user.id },
264-
feed: { id: feedDetailRequestDto.feedId },
265-
});
266-
isLike = !!like;
267-
}
268-
269-
return FeedDetailResponseDto.toResponseDto(feed, isLike);
251+
return FeedDetailResponseDto.toResponseDto(feed);
270252
}
271253
}

server/src/like/api-docs/deleteLike.api-docs.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
ApiNotFoundResponse,
55
ApiOkResponse,
66
ApiOperation,
7-
ApiParam,
87
ApiUnauthorizedResponse,
98
} from '@nestjs/swagger';
109

@@ -41,7 +40,7 @@ export function ApiDeleteLike() {
4140
ApiNotFoundResponse({
4241
description: '해당 ID의 게시글이 존재하지 않는 경우',
4342
example: {
44-
message: '존재하지 않는 게시글입니다.',
43+
message: '해당 피드를 찾을 수 없습니다.',
4544
},
4645
}),
4746
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { applyDecorators } from '@nestjs/common';
2+
import {
3+
ApiBadRequestResponse,
4+
ApiNotFoundResponse,
5+
ApiOkResponse,
6+
ApiOperation,
7+
} from '@nestjs/swagger';
8+
9+
export function ApiGetLike() {
10+
return applyDecorators(
11+
ApiOperation({
12+
summary: '게시글 좋아요 조회 API',
13+
}),
14+
ApiOkResponse({
15+
description: 'Ok',
16+
schema: {
17+
properties: {
18+
message: {
19+
type: 'string',
20+
},
21+
data: {
22+
type: 'object',
23+
properties: {
24+
isLike: {
25+
type: 'boolean',
26+
},
27+
},
28+
},
29+
},
30+
},
31+
example: {
32+
message: '좋아요 조회를 성공했습니다.',
33+
data: {
34+
isLike: false,
35+
},
36+
},
37+
}),
38+
ApiBadRequestResponse({
39+
description: 'Bad Request',
40+
example: {
41+
message: '오류 메세지',
42+
},
43+
}),
44+
ApiNotFoundResponse({
45+
description: '해당 ID의 게시글이 존재하지 않는 경우',
46+
example: {
47+
message: '해당 피드를 찾을 수 없습니다.',
48+
},
49+
}),
50+
);
51+
}

server/src/like/controller/like.controller.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import {
22
Body,
33
Controller,
44
Delete,
5+
Get,
56
HttpCode,
67
HttpStatus,
78
Param,
89
Post,
910
Req,
1011
UseGuards,
12+
UseInterceptors,
1113
} from '@nestjs/common';
1214
import { JwtGuard } from '../../common/guard/jwt.guard';
1315
import { ApiResponse } from '../../common/response/common.response';
@@ -16,15 +18,28 @@ import { ApiTags } from '@nestjs/swagger';
1618
import { ApiCreateLike } from '../api-docs/createLike.api-docs';
1719
import { ApiDeleteLike } from '../api-docs/deleteLike.api-docs';
1820
import { FeedLikeRequestDto } from '../dto/request/like.dto';
21+
import { ApiGetLike } from '../api-docs/getLike.api-docs';
22+
import { InjectUserInterceptor } from '../../common/auth/jwt.interceptor';
1923

2024
@ApiTags('Like')
2125
@Controller('like')
22-
@UseGuards(JwtGuard)
2326
export class LikeController {
2427
constructor(private readonly likeService: LikeService) {}
2528

29+
@ApiGetLike()
30+
@Get('/:feedId')
31+
@HttpCode(HttpStatus.OK)
32+
@UseInterceptors(InjectUserInterceptor)
33+
async getLike(@Req() req, @Param() feedLikeDto: FeedLikeRequestDto) {
34+
return ApiResponse.responseWithData(
35+
'좋아요 조회를 성공했습니다.',
36+
await this.likeService.get(req.user, feedLikeDto),
37+
);
38+
}
39+
2640
@ApiCreateLike()
2741
@Post()
42+
@UseGuards(JwtGuard)
2843
@HttpCode(HttpStatus.CREATED)
2944
async createLike(@Req() req, @Body() feedLikeDto: FeedLikeRequestDto) {
3045
await this.likeService.create(req.user, feedLikeDto);
@@ -33,6 +48,7 @@ export class LikeController {
3348

3449
@ApiDeleteLike()
3550
@Delete('/:feedId')
51+
@UseGuards(JwtGuard)
3652
@HttpCode(HttpStatus.OK)
3753
async deleteLike(@Req() req, @Param() feedLikeDto: FeedLikeRequestDto) {
3854
await this.likeService.delete(req.user, feedLikeDto);

server/src/like/dto/request/like.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { IsInt, Min } from 'class-validator';
55
export class FeedLikeRequestDto {
66
@ApiProperty({
77
example: 1,
8-
description: '좋아요 등록 및 취소할 피드 ID 입력',
8+
description: '좋아요 등록 및 취소, 조회할 피드 ID 입력',
99
})
1010
@IsInt({
1111
message: '정수를 입력해주세요.',

0 commit comments

Comments
 (0)