diff --git a/src/article/query/application/article-search/dto/get-article-search.request.dto.ts b/src/article/query/application/article-search/dto/get-article-search.request.dto.ts index 87d5219..eb80bca 100644 --- a/src/article/query/application/article-search/dto/get-article-search.request.dto.ts +++ b/src/article/query/application/article-search/dto/get-article-search.request.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export enum SearchType { + TITLE = 'title', + ORGANIZATION = 'organization', + ALL = 'all', +} export class GetArticleSearchRequestDto { @ApiProperty({ @@ -10,4 +16,15 @@ export class GetArticleSearchRequestDto { @IsString() @IsNotEmpty() keyword: string; + + @ApiProperty({ + description: '검색 유형 (title: 제목 검색, organization: 주관 기관 검색, all: 전체 검색)', + example: 'all', + enum: SearchType, + required: false, + default: SearchType.ALL, + }) + @IsEnum(SearchType) + @IsOptional() + searchType?: SearchType = SearchType.ALL; } diff --git a/src/article/query/application/article-search/get-article-search.use-case.ts b/src/article/query/application/article-search/get-article-search.use-case.ts index 996e702..46ae2d2 100644 --- a/src/article/query/application/article-search/get-article-search.use-case.ts +++ b/src/article/query/application/article-search/get-article-search.use-case.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { ARTICLE_QUERY_REPOSITORY, ArticleQueryRepository } from '../../domain/repository/article.query.repository'; import { ArticleModel } from '../../domain/article.model'; -import { GetArticleSearchRequestDto } from './dto/get-article-search.request.dto'; +import { GetArticleSearchRequestDto, SearchType } from './dto/get-article-search.request.dto'; @Injectable() export class GetArticleSearchUseCase { @@ -11,7 +11,7 @@ export class GetArticleSearchUseCase { ) {} async execute(reqDto: GetArticleSearchRequestDto): Promise { - const { keyword } = reqDto; - return await this.articleQueryRepository.searchByKeyword(keyword); + const { keyword, searchType = SearchType.ALL } = reqDto; + return await this.articleQueryRepository.searchByKeyword(keyword, searchType); } } diff --git a/src/article/query/domain/repository/article.query.repository.ts b/src/article/query/domain/repository/article.query.repository.ts index 2a59d22..e418b29 100644 --- a/src/article/query/domain/repository/article.query.repository.ts +++ b/src/article/query/domain/repository/article.query.repository.ts @@ -1,5 +1,6 @@ import { ArticleDetailModel } from '../article-detail.model'; import { ArticleModel } from '../article.model'; +import { SearchType } from '../../application/article-search/dto/get-article-search.request.dto'; export interface ArticleQueryRepository { findById(id: string): Promise; @@ -10,7 +11,7 @@ export interface ArticleQueryRepository { page?: number, limit?: number, ): Promise; - searchByKeyword(keyword: string): Promise; + searchByKeyword(keyword: string, searchType?: SearchType): Promise; } export const ARTICLE_QUERY_REPOSITORY = Symbol('ARTICLE_QUERY_REPOSITORY'); diff --git a/src/article/query/infrastructure/article.query.repository.impl.ts b/src/article/query/infrastructure/article.query.repository.impl.ts index 55eee83..a40a6d0 100644 --- a/src/article/query/infrastructure/article.query.repository.impl.ts +++ b/src/article/query/infrastructure/article.query.repository.impl.ts @@ -6,6 +6,7 @@ import { ArticleDetailModel } from '../domain/article-detail.model'; import { ArticleModel } from '../domain/article.model'; import { CustomException } from 'src/shared/exception/custom-exception'; import { CustomExceptionCode } from 'src/shared/exception/custom-exception-code'; +import { SearchType } from '../application/article-search/dto/get-article-search.request.dto'; export class ArticleQueryRepositoryImpl implements ArticleQueryRepository { constructor( @@ -218,7 +219,7 @@ export class ArticleQueryRepositoryImpl implements ArticleQueryRepository { return result; } - async searchByKeyword(keyword: string): Promise { + async searchByKeyword(keyword: string, searchType: SearchType = SearchType.ALL): Promise { const query = this.ormRepository.createQueryBuilder('a'); query .select([ @@ -242,8 +243,18 @@ export class ArticleQueryRepositoryImpl implements ArticleQueryRepository { .leftJoin('a.tags', 'tag') .groupBy('a.id'); - // 검색어 조건: 제목에서 검색 - query.andWhere(`a.title LIKE ?`, [`%${keyword}%`]); + // 검색어 조건: searchType에 따라 분기 + switch (searchType) { + case SearchType.TITLE: + query.andWhere(`a.title LIKE ?`, [`%${keyword}%`]); + break; + case SearchType.ORGANIZATION: + query.andWhere(`a.organization LIKE ?`, [`%${keyword}%`]); + break; + case SearchType.ALL: + query.andWhere(`(a.title LIKE ? OR a.organization LIKE ?)`, [`%${keyword}%`, `%${keyword}%`]); + break; + } // 정렬: 현재 시간에 가장 가까운 순서 (미래 우선) query.orderBy([ diff --git a/src/article/query/presentation/article.query.docs.ts b/src/article/query/presentation/article.query.docs.ts index c1a98bc..4e4459f 100644 --- a/src/article/query/presentation/article.query.docs.ts +++ b/src/article/query/presentation/article.query.docs.ts @@ -50,7 +50,11 @@ export const ArticleQueryDocs = createDocs({ applyDecorators( ApiOperation({ summary: '게시글 검색', - description: '제목을 기준으로 게시글을 검색합니다.', + description: + '게시글을 검색합니다. searchType 파라미터로 검색 대상을 지정할 수 있습니다.\n\n' + + '- **all**: 제목 + 주관 기관 전체 검색 (기본값)\n' + + '- **title**: 제목으로만 검색\n' + + '- **organization**: 주관 기관으로만 검색', }), ApiOkResponse({ description: '게시글 검색 성공',