From e7ad843e464cf000aa3f8130a9074ce36509662e Mon Sep 17 00:00:00 2001 From: codeVac513 Date: Mon, 2 Dec 2024 16:43:48 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20feedContro?= =?UTF-8?q?ller,=20FeedService=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/feed/feed.controller.ts | 16 ++++++++-------- server/src/feed/feed.service.ts | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/feed/feed.controller.ts b/server/src/feed/feed.controller.ts index bb2e16ac..f61f2aac 100644 --- a/server/src/feed/feed.controller.ts +++ b/server/src/feed/feed.controller.ts @@ -44,18 +44,18 @@ export class FeedController { transform: true, }), ) - async getFeedList(@Query() queryFeedDto: QueryFeedDto) { + async readFeedList(@Query() queryFeedDto: QueryFeedDto) { return ApiResponse.responseWithData( '피드 조회 완료', - await this.feedService.getFeedData(queryFeedDto), + await this.feedService.readFeedList(queryFeedDto), ); } @ApiGetTrendSse() @Sse('trend/sse') - async sseTrendList() { + async readTrendFeedList() { return new Observable((observer) => { - this.feedService.getTrendList().then((trendData) => { + this.feedService.readTrendFeedList().then((trendData) => { observer.next({ data: { message: '현재 트렌드 피드 수신 완료', @@ -83,8 +83,8 @@ export class FeedController { }), new ValidationPipe(), ) - async searchFeed(@Query() searchFeedReq: SearchFeedReq) { - const data = await this.feedService.search(searchFeedReq); + async searchFeedList(@Query() searchFeedReq: SearchFeedReq) { + const data = await this.feedService.searchFeedList(searchFeedReq); return ApiResponse.responseWithData('검색 결과 조회 완료', data); } @@ -112,10 +112,10 @@ export class FeedController { @ApiGetRecentFeedList() @Get('/recent') @HttpCode(HttpStatus.OK) - async getRecentFeedList() { + async readRecentFeedList() { return ApiResponse.responseWithData( '최신 피드 업데이트 완료', - await this.feedService.getRecentFeedList(), + await this.feedService.readRecentFeedList(), ); } } diff --git a/server/src/feed/feed.service.ts b/server/src/feed/feed.service.ts index edb6e9c6..741beac4 100644 --- a/server/src/feed/feed.service.ts +++ b/server/src/feed/feed.service.ts @@ -27,7 +27,7 @@ export class FeedService { private readonly eventService: EventEmitter2, ) {} - async getFeedData(queryFeedDto: QueryFeedDto) { + async readFeedList(queryFeedDto: QueryFeedDto) { const feedList = await this.feedRepository.findFeed(queryFeedDto); const hasMore = this.existNextFeed(feedList, queryFeedDto.limit); if (hasMore) feedList.pop(); @@ -46,7 +46,7 @@ export class FeedService { return lastFeed.id; } - async getTrendList() { + async readTrendFeedList() { const trendFeedIdList = await this.redisService.redisClient.lrange( redisKeys.FEED_ORIGIN_TREND_KEY, 0, @@ -88,11 +88,11 @@ export class FeedService { redisPipeline.rpush(redisKeys.FEED_ORIGIN_TREND_KEY, ...nowTrend); await redisPipeline.exec(); } - const trendFeeds = await this.getTrendList(); + const trendFeeds = await this.readTrendFeedList(); this.eventService.emit('ranking-update', trendFeeds); } - async search(searchFeedReq: SearchFeedReq) { + async searchFeedList(searchFeedReq: SearchFeedReq) { const { find, page, limit, type } = searchFeedReq; const offset = (page - 1) * limit; @@ -195,7 +195,7 @@ export class FeedService { } } - async getRecentFeedList() { + async readRecentFeedList() { const redis = this.redisService.redisClient; const recentFeedList = []; if ((await redis.get(redisKeys.FEED_RECENT_KEY)) === 'true') { From 3e7a91ea1fec462c0e9c19b5e89a1105e6bed793 Mon Sep 17 00:00:00 2001 From: codeVac513 Date: Mon, 2 Dec 2024 16:57:19 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9D=20docs:=20swagger=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=B3=84=EB=A1=9C=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=82=98=EB=88=84=EA=B3=A0=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?=EB=8D=B0=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feed/api-docs/readFeedList.api-docs.ts | 88 ++++ .../api-docs/readRecentFeedList.api-docs.ts | 54 +++ .../api-docs/readTrendFeedList.api-docs.ts | 97 +++++ .../feed/api-docs/searchFeedList.api-docs.ts | 116 ++++++ .../api-docs/updateFeedViewCount.api-docs.ts | 41 ++ server/src/feed/feed.api-docs.ts | 380 ------------------ server/src/feed/feed.controller.ts | 20 +- 7 files changed, 405 insertions(+), 391 deletions(-) create mode 100644 server/src/feed/api-docs/readFeedList.api-docs.ts create mode 100644 server/src/feed/api-docs/readRecentFeedList.api-docs.ts create mode 100644 server/src/feed/api-docs/readTrendFeedList.api-docs.ts create mode 100644 server/src/feed/api-docs/searchFeedList.api-docs.ts create mode 100644 server/src/feed/api-docs/updateFeedViewCount.api-docs.ts delete mode 100644 server/src/feed/feed.api-docs.ts diff --git a/server/src/feed/api-docs/readFeedList.api-docs.ts b/server/src/feed/api-docs/readFeedList.api-docs.ts new file mode 100644 index 00000000..32851b5d --- /dev/null +++ b/server/src/feed/api-docs/readFeedList.api-docs.ts @@ -0,0 +1,88 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiOkResponse, + ApiOperation, + ApiQuery, +} from '@nestjs/swagger'; + +export function ApiReadFeedList() { + return applyDecorators( + ApiOperation({ + summary: `메인 화면 게시글 조회 API`, + }), + ApiQuery({ + name: 'lastId', + required: false, + type: Number, + description: '마지막으로 받은 피드의 ID', + example: 10, + }), + ApiQuery({ + name: 'limit', + required: false, + type: Number, + description: '한 번에 가져올 피드 수', + example: 5, + default: 12, + }), + ApiOkResponse({ + description: 'Ok', + schema: { + properties: { + message: { + type: 'string', + }, + data: { + type: 'object', + properties: { + result: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'number' }, + author: { type: 'string' }, + blogPlatform: { type: 'string' }, + title: { type: 'string' }, + path: { type: 'string', format: 'url' }, + createAt: { type: 'string', format: 'date-time' }, + thumbnail: { type: 'string', format: 'url' }, + viewCount: { type: 'number' }, + }, + }, + }, + lastId: { type: 'number' }, + hasMore: { type: 'boolean' }, + }, + }, + }, + }, + example: { + message: '피드 조회 완료', + data: { + result: [ + { + id: 3, + author: '블로그 이름', + blogPlatform: '블로그 서비스 플랫폼', + title: '피드 제목', + path: 'https://test.com', + createAt: '2024-06-16T20:00:57.000Z', + thumbnail: 'https://test.com/image.png', + viewCount: 1, + }, + ], + lastId: 3, + hasMore: true, + }, + }, + }), + ApiBadRequestResponse({ + description: 'Bad Request', + example: { + message: '오류 메세지', + }, + }), + ); +} diff --git a/server/src/feed/api-docs/readRecentFeedList.api-docs.ts b/server/src/feed/api-docs/readRecentFeedList.api-docs.ts new file mode 100644 index 00000000..0cca47da --- /dev/null +++ b/server/src/feed/api-docs/readRecentFeedList.api-docs.ts @@ -0,0 +1,54 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; + +export function ApiReadRecentFeedList() { + return applyDecorators( + ApiOperation({ + summary: '최신 피드 업데이트 API', + }), + ApiOkResponse({ + description: 'Ok', + schema: { + properties: { + message: { + type: 'string', + }, + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'number' }, + author: { type: 'string' }, + blogPlatform: { type: 'string' }, + title: { type: 'string' }, + path: { type: 'string' }, + createdAt: { + type: 'string', + format: 'date-time', + }, + thumbnail: { type: 'string' }, + viewCount: { type: 'number' }, + }, + }, + }, + }, + }, + example: { + message: '최신 피드 업데이트 완료', + data: [ + { + id: 1, + author: '블로그 이름', + blogPlatform: 'etc', + title: '게시글 제목', + path: 'https://test1.com/1', + createdAt: '2024-11-24T01:00:00.000Z', + thumbnail: 'https://test1.com/test.png', + viewCount: 0, + }, + ], + }, + }), + ); +} diff --git a/server/src/feed/api-docs/readTrendFeedList.api-docs.ts b/server/src/feed/api-docs/readTrendFeedList.api-docs.ts new file mode 100644 index 00000000..7ccdce62 --- /dev/null +++ b/server/src/feed/api-docs/readTrendFeedList.api-docs.ts @@ -0,0 +1,97 @@ +import { applyDecorators } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation } from '@nestjs/swagger'; + +export function ApiReadTrendFeedList() { + return applyDecorators( + ApiOperation({ + summary: '트렌드 게시글 조회 SSE', + }), + ApiOkResponse({ + description: 'Ok', + schema: { + properties: { + message: { + type: 'string', + }, + data: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'number' }, + author: { type: 'string' }, + blogPlatform: { type: 'string' }, + title: { type: 'string' }, + path: { type: 'string' }, + createdAt: { + type: 'string', + format: 'date-time', + }, + thumbnail: { type: 'string' }, + viewCount: { type: 'number' }, + }, + }, + }, + }, + }, + examples: { + connect: { + summary: '현재 트렌드 피드 수신 완료', + value: { + message: '현재 트렌드 피드 수신 완료', + data: [ + { + id: 1, + author: '블로그 이름', + blogPlatform: '블로그 서비스 플랫폼', + title: '피드 제목', + path: 'https://test1.com/1', + createdAt: '2024-11-24T01:00:00.000Z', + thumbnail: 'https://test1.com/test.png', + viewCount: 0, + }, + { + id: 2, + author: '블로그 이름', + blogPlatform: '블로그 서비스 플랫폼', + title: '피드 제목', + path: 'https://test2.com/1', + createdAt: '2024-11-24T02:00:00.000Z', + thumbnail: 'https://test2.com/test.png', + viewCount: 0, + }, + ], + }, + }, + continue: { + summary: '새로운 트렌드 피드 수신 완료', + value: { + message: '새로운 트렌드 피드 수신 완료', + data: [ + { + id: 3, + author: '블로그 이름', + blogPlatform: '블로그 서비스 플랫폼', + title: '피드 제목', + path: 'https://test3.com/1', + createdAt: '2024-11-24T03:00:00.000Z', + thumbnail: 'https://test3.com/test.png', + viewCount: 0, + }, + { + id: 4, + author: '블로그 이름', + blogPlatform: '블로그 서비스 플랫폼', + title: '피드 제목', + path: 'https://test4.com/1', + createdAt: '2024-11-24T04:00:00.000Z', + thumbnail: 'https://test4.com/test.png', + viewCount: 0, + }, + ], + }, + }, + }, + }), + ); +} diff --git a/server/src/feed/api-docs/searchFeedList.api-docs.ts b/server/src/feed/api-docs/searchFeedList.api-docs.ts new file mode 100644 index 00000000..813e1cd7 --- /dev/null +++ b/server/src/feed/api-docs/searchFeedList.api-docs.ts @@ -0,0 +1,116 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiOkResponse, + ApiOperation, + ApiQuery, +} from '@nestjs/swagger'; +import { SearchType } from '../dto/search-feed.dto'; + +export function ApiSearchFeedList() { + return applyDecorators( + ApiOperation({ + summary: `검색 API`, + }), + ApiQuery({ + name: 'find', + required: true, + type: String, + description: '검색어', + example: '데나무', + }), + ApiQuery({ + name: 'type', + required: true, + enum: SearchType, + description: '검색 타입', + example: SearchType.ALL, + }), + ApiQuery({ + name: 'page', + required: true, + type: Number, + description: '페이지 번호', + example: 1, + }), + ApiQuery({ + name: 'limit', + required: true, + type: Number, + description: '한 페이지에 보여줄 개수', + example: 4, + }), + ApiOkResponse({ + description: 'Ok', + schema: { + properties: { + message: { + type: 'string', + }, + data: { + type: 'object', + properties: { + totalCount: { + type: 'number', + }, + result: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number', + }, + blogName: { + type: 'string', + }, + title: { + type: 'string', + }, + path: { + type: 'string', + format: 'url', + }, + createdAt: { + type: 'string', + format: 'date-time', + }, + }, + }, + }, + totalPages: { + type: 'number', + }, + limit: { + type: 'number', + }, + }, + }, + }, + }, + example: { + message: '검색 결과 조회 완료', + data: { + totalCount: 1, + result: [ + { + id: 1, + blogName: '블로그 이름', + title: '데나무', + path: 'https://test.com/1', + createdAt: '2024-10-27T02:08:55.000Z', + }, + ], + totalPages: 3, + limit: 1, + }, + }, + }), + ApiBadRequestResponse({ + description: 'Bad Request', + example: { + message: '오류 메세지', + }, + }), + ); +} diff --git a/server/src/feed/api-docs/updateFeedViewCount.api-docs.ts b/server/src/feed/api-docs/updateFeedViewCount.api-docs.ts new file mode 100644 index 00000000..490a7999 --- /dev/null +++ b/server/src/feed/api-docs/updateFeedViewCount.api-docs.ts @@ -0,0 +1,41 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiParam, +} from '@nestjs/swagger'; + +export function ApiUpdateFeedViewCount() { + return applyDecorators( + ApiOperation({ + summary: `피드 조회수 업데이트 API`, + }), + ApiParam({ + name: 'feedId', + required: true, + type: Number, + description: '클릭한 피드의 id', + example: 1, + }), + ApiOkResponse({ + description: 'Ok', + schema: { + properties: { + message: { + type: 'string', + }, + }, + }, + example: { + message: '요청이 성공적으로 처리되었습니다.', + }, + }), + ApiNotFoundResponse({ + description: '해당 ID의 피드가 존재하지 않는 경우', + example: { + message: '{feedId}번 피드를 찾을 수 없습니다.', + }, + }), + ); +} diff --git a/server/src/feed/feed.api-docs.ts b/server/src/feed/feed.api-docs.ts deleted file mode 100644 index 8829b9a0..00000000 --- a/server/src/feed/feed.api-docs.ts +++ /dev/null @@ -1,380 +0,0 @@ -import { - ApiBadRequestResponse, - ApiNotFoundResponse, - ApiOkResponse, - ApiOperation, - ApiParam, - ApiQuery, -} from '@nestjs/swagger'; -import { applyDecorators, NotFoundException } from '@nestjs/common'; -import { SearchType } from './dto/search-feed.dto'; - -export function ApiGetFeedList() { - return applyDecorators( - ApiOperation({ - summary: `메인 화면 게시글 조회 API`, - }), - ApiQuery({ - name: 'lastId', - required: false, - type: Number, - description: '마지막으로 받은 피드의 ID', - example: 10, - }), - ApiQuery({ - name: 'limit', - required: false, - type: Number, - description: '한 번에 가져올 피드 수', - example: 5, - default: 12, - }), - ApiOkResponse({ - description: 'Ok', - schema: { - properties: { - message: { - type: 'string', - }, - data: { - type: 'object', - properties: { - result: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'number' }, - author: { type: 'string' }, - blogPlatform: { type: 'string' }, - title: { type: 'string' }, - path: { type: 'string', format: 'url' }, - createAt: { type: 'string', format: 'date-time' }, - thumbnail: { type: 'string', format: 'url' }, - viewCount: { type: 'number' }, - }, - }, - }, - lastId: { type: 'number' }, - hasMore: { type: 'boolean' }, - }, - }, - }, - }, - example: { - message: '피드 조회 완료', - data: { - result: [ - { - id: 3, - author: '블로그 이름', - blogPlatform: '블로그 서비스 플랫폼', - title: '피드 제목', - path: 'https://test.com', - createAt: '2024-06-16T20:00:57.000Z', - thumbnail: 'https://test.com/image.png', - viewCount: 1, - }, - ], - lastId: 3, - hasMore: true, - }, - }, - }), - ApiBadRequestResponse({ - description: 'Bad Request', - example: { - message: '오류 메세지', - }, - }), - ); -} - -export function ApiSearchFeed() { - return applyDecorators( - ApiOperation({ - summary: `검색 API`, - }), - ApiQuery({ - name: 'find', - required: true, - type: String, - description: '검색어', - example: '데나무', - }), - ApiQuery({ - name: 'type', - required: true, - enum: SearchType, - description: '검색 타입', - example: SearchType.ALL, - }), - ApiQuery({ - name: 'page', - required: true, - type: Number, - description: '페이지 번호', - example: 1, - }), - ApiQuery({ - name: 'limit', - required: true, - type: Number, - description: '한 페이지에 보여줄 개수', - example: 4, - }), - ApiOkResponse({ - description: 'Ok', - schema: { - properties: { - message: { - type: 'string', - }, - data: { - type: 'object', - properties: { - totalCount: { - type: 'number', - }, - result: { - type: 'array', - items: { - type: 'object', - properties: { - id: { - type: 'number', - }, - blogName: { - type: 'string', - }, - title: { - type: 'string', - }, - path: { - type: 'string', - format: 'url', - }, - createdAt: { - type: 'string', - format: 'date-time', - }, - }, - }, - }, - totalPages: { - type: 'number', - }, - limit: { - type: 'number', - }, - }, - }, - }, - }, - example: { - message: '검색 결과 조회 완료', - data: { - totalCount: 1, - result: [ - { - id: 1, - blogName: '블로그 이름', - title: '데나무', - path: 'https://test.com/1', - createdAt: '2024-10-27T02:08:55.000Z', - }, - ], - totalPages: 3, - limit: 1, - }, - }, - }), - ApiBadRequestResponse({ - description: 'Bad Request', - example: { - message: '오류 메세지', - }, - }), - ); -} - -export function ApiGetTrendSse() { - return applyDecorators( - ApiOperation({ - summary: '트렌드 게시글 조회 SSE', - }), - ApiOkResponse({ - description: 'Ok', - schema: { - properties: { - message: { - type: 'string', - }, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'number' }, - author: { type: 'string' }, - blogPlatform: { type: 'string' }, - title: { type: 'string' }, - path: { type: 'string' }, - createdAt: { - type: 'string', - format: 'date-time', - }, - thumbnail: { type: 'string' }, - viewCount: { type: 'number' }, - }, - }, - }, - }, - }, - examples: { - connect: { - summary: '현재 트렌드 피드 수신 완료', - value: { - message: '현재 트렌드 피드 수신 완료', - data: [ - { - id: 1, - author: '블로그 이름', - blogPlatform: '블로그 서비스 플랫폼', - title: '피드 제목', - path: 'https://test1.com/1', - createdAt: '2024-11-24T01:00:00.000Z', - thumbnail: 'https://test1.com/test.png', - viewCount: 0, - }, - { - id: 2, - author: '블로그 이름', - blogPlatform: '블로그 서비스 플랫폼', - title: '피드 제목', - path: 'https://test2.com/1', - createdAt: '2024-11-24T02:00:00.000Z', - thumbnail: 'https://test2.com/test.png', - viewCount: 0, - }, - ], - }, - }, - continue: { - summary: '새로운 트렌드 피드 수신 완료', - value: { - message: '새로운 트렌드 피드 수신 완료', - data: [ - { - id: 3, - author: '블로그 이름', - blogPlatform: '블로그 서비스 플랫폼', - title: '피드 제목', - path: 'https://test3.com/1', - createdAt: '2024-11-24T03:00:00.000Z', - thumbnail: 'https://test3.com/test.png', - viewCount: 0, - }, - { - id: 4, - author: '블로그 이름', - blogPlatform: '블로그 서비스 플랫폼', - title: '피드 제목', - path: 'https://test4.com/1', - createdAt: '2024-11-24T04:00:00.000Z', - thumbnail: 'https://test4.com/test.png', - viewCount: 0, - }, - ], - }, - }, - }, - }), - ); -} - -export function ApiUpdateFeedViewCount() { - return applyDecorators( - ApiOperation({ - summary: `피드 조회수 업데이트 API`, - }), - ApiParam({ - name: 'feedId', - required: true, - type: Number, - description: '클릭한 피드의 id', - example: 1, - }), - ApiOkResponse({ - description: 'Ok', - schema: { - properties: { - message: { - type: 'string', - }, - }, - }, - example: { - message: '요청이 성공적으로 처리되었습니다.', - }, - }), - ApiNotFoundResponse({ - description: '해당 ID의 피드가 존재하지 않는 경우', - example: { - message: '{feedId}번 피드를 찾을 수 없습니다.', - }, - }), - ); -} - -export function ApiGetRecentFeedList() { - return applyDecorators( - ApiOperation({ - summary: '최신 피드 업데이트 API', - }), - ApiOkResponse({ - description: 'Ok', - schema: { - properties: { - message: { - type: 'string', - }, - data: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'number' }, - author: { type: 'string' }, - blogPlatform: { type: 'string' }, - title: { type: 'string' }, - path: { type: 'string' }, - createdAt: { - type: 'string', - format: 'date-time', - }, - thumbnail: { type: 'string' }, - viewCount: { type: 'number' }, - }, - }, - }, - }, - }, - example: { - message: '최신 피드 업데이트 완료', - data: [ - { - id: 1, - author: '블로그 이름', - blogPlatform: 'etc', - title: '게시글 제목', - path: 'https://test1.com/1', - createdAt: '2024-11-24T01:00:00.000Z', - thumbnail: 'https://test1.com/test.png', - viewCount: 0, - }, - ], - }, - }), - ); -} diff --git a/server/src/feed/feed.controller.ts b/server/src/feed/feed.controller.ts index f61f2aac..35fa8868 100644 --- a/server/src/feed/feed.controller.ts +++ b/server/src/feed/feed.controller.ts @@ -17,16 +17,14 @@ import { import { FeedService } from './feed.service'; import { QueryFeedDto } from './dto/query-feed.dto'; import { SearchFeedReq } from './dto/search-feed.dto'; -import { - ApiGetFeedList, - ApiSearchFeed, - ApiUpdateFeedViewCount, - ApiGetTrendSse, - ApiGetRecentFeedList, -} from './feed.api-docs'; import { Response } from 'express'; import { Observable } from 'rxjs'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ApiReadFeedList } from './api-docs/readFeedList.api-docs'; +import { ApiReadTrendFeedList } from './api-docs/readTrendFeedList.api-docs'; +import { ApiSearchFeedList } from './api-docs/searchFeedList.api-docs'; +import { ApiUpdateFeedViewCount } from './api-docs/updateFeedViewCount.api-docs'; +import { ApiReadRecentFeedList } from './api-docs/readRecentFeedList.api-docs'; @ApiTags('Feed') @Controller('feed') @@ -36,7 +34,7 @@ export class FeedController { private readonly eventService: EventEmitter2, ) {} - @ApiGetFeedList() + @ApiReadFeedList() @Get('') @HttpCode(HttpStatus.OK) @UsePipes( @@ -51,7 +49,7 @@ export class FeedController { ); } - @ApiGetTrendSse() + @ApiReadTrendFeedList() @Sse('trend/sse') async readTrendFeedList() { return new Observable((observer) => { @@ -74,7 +72,7 @@ export class FeedController { }); } - @ApiSearchFeed() + @ApiSearchFeedList() @Get('search') @HttpCode(HttpStatus.OK) @UsePipes( @@ -109,7 +107,7 @@ export class FeedController { ); } - @ApiGetRecentFeedList() + @ApiReadRecentFeedList() @Get('/recent') @HttpCode(HttpStatus.OK) async readRecentFeedList() { From 79da3a998cad39b1b4aa3deb046aee2cdc997d1c Mon Sep 17 00:00:00 2001 From: codeVac513 Date: Mon, 2 Dec 2024 18:53:30 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20service=20?= =?UTF-8?q?=EA=B3=84=EC=B8=B5=EC=97=90=20=EC=9E=88=EB=8D=98=20DB=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B4=80=EB=A0=A8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20Repository=20=EA=B3=84=EC=B8=B5=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/feed/feed.repository.ts | 58 ++++++++++++++++++++++++++++- server/src/feed/feed.service.ts | 60 ++++++------------------------ 2 files changed, 69 insertions(+), 49 deletions(-) diff --git a/server/src/feed/feed.repository.ts b/server/src/feed/feed.repository.ts index 480059c6..78beb868 100644 --- a/server/src/feed/feed.repository.ts +++ b/server/src/feed/feed.repository.ts @@ -1,7 +1,8 @@ import { DataSource, LessThan, Repository } from 'typeorm'; import { Feed } from './feed.entity'; -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { QueryFeedDto } from './dto/query-feed.dto'; +import { SearchType } from './dto/search-feed.dto'; @Injectable() export class FeedRepository extends Repository { @@ -26,4 +27,59 @@ export class FeedRepository extends Repository { relations: ['blog'], }); } + + async searchFeedList( + find: string, + limit: number, + type: SearchType, + offset: number, + ) { + const queryBuilder = this.createQueryBuilder('feed') + .leftJoinAndSelect('feed.blog', 'rss_accept') + .addSelect(this.getMatchAgainstExpression(type, 'find'), 'relevance') + .where(this.getWhereCondition(type)) + .setParameters({ find }) + .orderBy('relevance', 'DESC') + .addOrderBy('feed.createdAt', 'DESC') + .skip(offset) + .take(limit); + + return queryBuilder.getManyAndCount(); + } + + private getMatchAgainstExpression(type: string, parameter: string): string { + switch (type) { + case 'title': + return `MATCH(feed.title) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE)`; + case 'blogName': + return `MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE)`; + case 'all': + return `(MATCH(feed.title) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE) + MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE))`; + default: + throw new BadRequestException('검색 타입이 잘못되었습니다.'); + } + } + + private getWhereCondition(type: string): string { + switch (type) { + case 'title': + return 'MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE)'; + case 'blogName': + return 'MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE)'; + case 'all': + return '(MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE) OR MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE))'; + default: + throw new BadRequestException('검색 타입이 잘못되었습니다.'); + } + } + + async findFeedById(feedId: number) { + return this.findOne({ where: { id: feedId } }); + } + + async updateFeedViewCount(feedId: number) { + await this.update(feedId, { + viewCount: () => 'view_count + 1', + }); + } } diff --git a/server/src/feed/feed.service.ts b/server/src/feed/feed.service.ts index 741beac4..eb7fccce 100644 --- a/server/src/feed/feed.service.ts +++ b/server/src/feed/feed.service.ts @@ -1,8 +1,4 @@ -import { - BadRequestException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { FeedRepository } from './feed.repository'; import { QueryFeedDto } from './dto/query-feed.dto'; import { Feed } from './feed.entity'; @@ -19,6 +15,7 @@ import { import { Response } from 'express'; import { cookieConfig } from '../common/cookie/cookie.config'; import { redisKeys } from '../common/redis/redis.constant'; + @Injectable() export class FeedService { constructor( @@ -96,58 +93,27 @@ export class FeedService { const { find, page, limit, type } = searchFeedReq; const offset = (page - 1) * limit; - const qb = this.feedRepository - .createQueryBuilder('feed') - .leftJoinAndSelect('feed.blog', 'rss_accept') - .addSelect(this.getMatchAgainstExpression(type, 'find'), 'relevance') - .where(this.getWhereCondition(type)) - .setParameters({ find }) - .orderBy('relevance', 'DESC') - .addOrderBy('feed.createdAt', 'DESC') - .skip(offset) - .take(limit); - - const [result, totalCount] = await qb.getManyAndCount(); + const [result, totalCount] = await this.feedRepository.searchFeedList( + find, + limit, + type, + offset, + ); + const results = SearchFeedResult.feedsToResults(result); const totalPages = Math.ceil(totalCount / limit); return new SearchFeedRes(totalCount, results, totalPages, limit); } - private getMatchAgainstExpression(type: string, parameter: string): string { - switch (type) { - case 'title': - return `MATCH(feed.title) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE)`; - case 'blogName': - return `MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE)`; - case 'all': - return `(MATCH(feed.title) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE) + MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE))`; - default: - throw new BadRequestException('검색 타입이 잘못되었습니다.'); - } - } - - private getWhereCondition(type: string): string { - switch (type) { - case 'title': - return 'MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE)'; - case 'blogName': - return 'MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE)'; - case 'all': - return '(MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE) OR MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE))'; - default: - throw new BadRequestException('검색 타입이 잘못되었습니다.'); - } - } - async updateFeedViewCount(feedId: number, ip: string, cookie, response) { const redis = this.redisService.redisClient; const [feed, hasCookie, hasIpFlag] = await Promise.all([ - this.feedRepository.findOne({ where: { id: feedId } }), + this.feedRepository.findFeedById(feedId), Boolean(cookie?.[`View_count_${feedId}`]), redis.sismember(`feed:${feedId}:ip`, ip), ]); - + console.log(hasIpFlag); if (!feed) { throw new NotFoundException(`${feedId}번 피드를 찾을 수 없습니다.`); } @@ -162,9 +128,7 @@ export class FeedService { await Promise.all([ redis.sadd(`feed:${feedId}:ip`, ip), - this.feedRepository.update(feedId, { - viewCount: feed.viewCount + 1, - }), + this.feedRepository.updateFeedViewCount(feedId), redis.zincrby(redisKeys.FEED_TREND_KEY, 1, feedId.toString()), ]); } From d137f5dad95b4488c537a1d0c3e37518e09ec410 Mon Sep 17 00:00:00 2001 From: codeVac513 Date: Tue, 3 Dec 2024 12:13:09 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20badRequest?= =?UTF-8?q?=20=EC=98=88=EC=99=B8=EC=B2=98=EB=A6=AC=EB=A5=BC=20Service?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/feed/feed.repository.ts | 4 --- server/src/feed/feed.service.ts | 45 +++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/server/src/feed/feed.repository.ts b/server/src/feed/feed.repository.ts index 78beb868..5814acd0 100644 --- a/server/src/feed/feed.repository.ts +++ b/server/src/feed/feed.repository.ts @@ -55,8 +55,6 @@ export class FeedRepository extends Repository { return `MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE)`; case 'all': return `(MATCH(feed.title) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE) + MATCH(rss_accept.name) AGAINST (:${parameter} IN NATURAL LANGUAGE MODE))`; - default: - throw new BadRequestException('검색 타입이 잘못되었습니다.'); } } @@ -68,8 +66,6 @@ export class FeedRepository extends Repository { return 'MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE)'; case 'all': return '(MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE) OR MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE))'; - default: - throw new BadRequestException('검색 타입이 잘못되었습니다.'); } } diff --git a/server/src/feed/feed.service.ts b/server/src/feed/feed.service.ts index eb7fccce..25dbc2f9 100644 --- a/server/src/feed/feed.service.ts +++ b/server/src/feed/feed.service.ts @@ -1,4 +1,8 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { FeedRepository } from './feed.repository'; import { QueryFeedDto } from './dto/query-feed.dto'; import { Feed } from './feed.entity'; @@ -92,18 +96,33 @@ export class FeedService { async searchFeedList(searchFeedReq: SearchFeedReq) { const { find, page, limit, type } = searchFeedReq; const offset = (page - 1) * limit; + if (this.validateSearchType(type)) { + const [result, totalCount] = await this.feedRepository.searchFeedList( + find, + limit, + type, + offset, + ); + + const results = SearchFeedResult.feedsToResults(result); + const totalPages = Math.ceil(totalCount / limit); + + return new SearchFeedRes(totalCount, results, totalPages, limit); + } + throw new BadRequestException('검색 타입이 잘못되었습니다.'); + } - const [result, totalCount] = await this.feedRepository.searchFeedList( - find, - limit, - type, - offset, - ); - - const results = SearchFeedResult.feedsToResults(result); - const totalPages = Math.ceil(totalCount / limit); - - return new SearchFeedRes(totalCount, results, totalPages, limit); + private validateSearchType(type: string) { + switch (type) { + case 'title': + return true; + case 'blogName': + return true; + case 'all': + return true; + default: + return false; + } } async updateFeedViewCount(feedId: number, ip: string, cookie, response) { @@ -113,7 +132,7 @@ export class FeedService { Boolean(cookie?.[`View_count_${feedId}`]), redis.sismember(`feed:${feedId}:ip`, ip), ]); - console.log(hasIpFlag); + if (!feed) { throw new NotFoundException(`${feedId}번 피드를 찾을 수 없습니다.`); } From 0354b671870575c1fae78f7e64581f4f10326d1e Mon Sep 17 00:00:00 2001 From: codeVac513 Date: Wed, 4 Dec 2024 02:29:52 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20repository?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=A1=A4=EB=B0=B1=20=EB=B0=8F?= =?UTF-8?q?=20switch=20case=EB=AC=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/feed/feed.repository.ts | 17 -------------- server/src/feed/feed.service.ts | 28 ++++++++++++----------- server/src/statistic/statistic.service.ts | 5 +++- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/server/src/feed/feed.repository.ts b/server/src/feed/feed.repository.ts index 5814acd0..7facec67 100644 --- a/server/src/feed/feed.repository.ts +++ b/server/src/feed/feed.repository.ts @@ -21,13 +21,6 @@ export class FeedRepository extends Repository { }); } - async findTrendFeed(feedId: number) { - return this.findOne({ - where: { id: feedId }, - relations: ['blog'], - }); - } - async searchFeedList( find: string, limit: number, @@ -68,14 +61,4 @@ export class FeedRepository extends Repository { return '(MATCH(feed.title) AGAINST (:find IN NATURAL LANGUAGE MODE) OR MATCH(rss_accept.name) AGAINST (:find IN NATURAL LANGUAGE MODE))'; } } - - async findFeedById(feedId: number) { - return this.findOne({ where: { id: feedId } }); - } - - async updateFeedViewCount(feedId: number) { - await this.update(feedId, { - viewCount: () => 'view_count + 1', - }); - } } diff --git a/server/src/feed/feed.service.ts b/server/src/feed/feed.service.ts index 25dbc2f9..a6a50607 100644 --- a/server/src/feed/feed.service.ts +++ b/server/src/feed/feed.service.ts @@ -55,7 +55,10 @@ export class FeedService { ); const trendFeeds = await Promise.all( trendFeedIdList.map(async (feedId) => { - const feed = await this.feedRepository.findTrendFeed(parseInt(feedId)); + const feed = await this.feedRepository.findOne({ + where: { id: parseInt(feedId) }, + relations: ['blog'], + }); if (!feed) { return null; } @@ -113,22 +116,19 @@ export class FeedService { } private validateSearchType(type: string) { - switch (type) { - case 'title': - return true; - case 'blogName': - return true; - case 'all': - return true; - default: - return false; - } + const searchType = { + title: 'title', + blogName: 'blogName', + all: 'all', + }; + + return searchType.hasOwnProperty(type); } async updateFeedViewCount(feedId: number, ip: string, cookie, response) { const redis = this.redisService.redisClient; const [feed, hasCookie, hasIpFlag] = await Promise.all([ - this.feedRepository.findFeedById(feedId), + this.feedRepository.findOne({ where: { id: feedId } }), Boolean(cookie?.[`View_count_${feedId}`]), redis.sismember(`feed:${feedId}:ip`, ip), ]); @@ -147,7 +147,9 @@ export class FeedService { await Promise.all([ redis.sadd(`feed:${feedId}:ip`, ip), - this.feedRepository.updateFeedViewCount(feedId), + this.feedRepository.update(feedId, { + viewCount: () => 'view_count + 1', + }), redis.zincrby(redisKeys.FEED_TREND_KEY, 1, feedId.toString()), ]); } diff --git a/server/src/statistic/statistic.service.ts b/server/src/statistic/statistic.service.ts index 13185b2e..e0bed705 100644 --- a/server/src/statistic/statistic.service.ts +++ b/server/src/statistic/statistic.service.ts @@ -25,7 +25,10 @@ export class StatisticService { const feedId = parseInt(ranking[i]); const score = parseFloat(ranking[i + 1]); - const feedData = await this.feedRepository.findTrendFeed(feedId); + const feedData = await this.feedRepository.findOne({ + where: { id: feedId }, + relations: ['blog'], + }); result.push({ id: feedData.id,