Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -11,7 +11,7 @@ export class GetArticleSearchUseCase {
) {}

async execute(reqDto: GetArticleSearchRequestDto): Promise<ArticleModel[]> {
const { keyword } = reqDto;
return await this.articleQueryRepository.searchByKeyword(keyword);
const { keyword, searchType = SearchType.ALL } = reqDto;
return await this.articleQueryRepository.searchByKeyword(keyword, searchType);
}
}
Original file line number Diff line number Diff line change
@@ -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<ArticleDetailModel>;
Expand All @@ -10,7 +11,7 @@ export interface ArticleQueryRepository {
page?: number,
limit?: number,
): Promise<ArticleModel[]>;
searchByKeyword(keyword: string): Promise<ArticleModel[]>;
searchByKeyword(keyword: string, searchType?: SearchType): Promise<ArticleModel[]>;
}

export const ARTICLE_QUERY_REPOSITORY = Symbol('ARTICLE_QUERY_REPOSITORY');
17 changes: 14 additions & 3 deletions src/article/query/infrastructure/article.query.repository.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -218,7 +219,7 @@ export class ArticleQueryRepositoryImpl implements ArticleQueryRepository {
return result;
}

async searchByKeyword(keyword: string): Promise<ArticleModel[]> {
async searchByKeyword(keyword: string, searchType: SearchType = SearchType.ALL): Promise<ArticleModel[]> {
const query = this.ormRepository.createQueryBuilder('a');
query
.select([
Expand All @@ -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([
Expand Down
6 changes: 5 additions & 1 deletion src/article/query/presentation/article.query.docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ export const ArticleQueryDocs = createDocs<ArticleQueryEndpoint>({
applyDecorators(
ApiOperation({
summary: '게시글 검색',
description: '제목을 기준으로 게시글을 검색합니다.',
description:
'게시글을 검색합니다. searchType 파라미터로 검색 대상을 지정할 수 있습니다.\n\n' +
'- **all**: 제목 + 주관 기관 전체 검색 (기본값)\n' +
'- **title**: 제목으로만 검색\n' +
'- **organization**: 주관 기관으로만 검색',
}),
ApiOkResponse({
description: '게시글 검색 성공',
Expand Down