Skip to content

Commit

Permalink
Merge pull request #107 from modern-agile-team/feature/search
Browse files Browse the repository at this point in the history
Refactor(hobiJeong/search): 검색 기능 Controller에 ApiTags추가, 카테고리도 고려하여 검색하도록 기능 수정
  • Loading branch information
hobiJeong authored Nov 9, 2023
2 parents 7fe0202 + 6b9f301 commit f6c3efe
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 66 deletions.
2 changes: 1 addition & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SearchModule } from './search/search.module';
UserModule,
TypeOrmModule.forRoot({
...TypeORMconfig, // TypeORM 설정 객체 확장
synchronize: true, // DB 동기화 여부 설정
synchronize: false, // DB 동기화 여부 설정
}),
// TypeOrmModule.forFeature([Image]),
ConfigModule.forRoot({
Expand Down
2 changes: 1 addition & 1 deletion src/boards/entities/board.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class Board {
@JoinColumn({ name: 'user_id' })
user: User;

@OneToMany(() => BoardImage, (boardImage) => boardImage.board, {
@OneToMany(() => BoardImage, (boardImages) => boardImages.board, {
onDelete: 'CASCADE',
})
boardImages: BoardImage[];
Expand Down
36 changes: 0 additions & 36 deletions src/client.js

This file was deleted.

23 changes: 19 additions & 4 deletions src/search/controllers/search.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Controller,
Get,
Param,
ParseIntPipe,
Query,
UsePipes,
Expand All @@ -9,30 +10,44 @@ import {
import { SearchService } from '../services/search.service';
import { ApiSearchBoardsByHead } from '../swagger-decorators/search-boards-by-head.decorator';
import { ApiSearchBoardsByBody } from '../swagger-decorators/search-boards-by-body.decorator';
import { ApiTags } from '@nestjs/swagger';

@ApiTags('SEARCH')
@UsePipes(ValidationPipe)
@Controller('search')
export class SearchController {
constructor(private searchService: SearchService) {}

@ApiSearchBoardsByHead()
@Get('boards/head')
@Get('boards/:category/head')
async searchBoardsByHead(
@Param('category') category: string,
@Query('searchQuery') searchQuery: string,
@Query('page', ParseIntPipe) page: number,
@Query('limit', ParseIntPipe) limit: number,
) {
return this.searchService.searchBoardsByHead(searchQuery, page, limit);
return this.searchService.searchBoardsByHead(
category,
searchQuery,
page,
limit,
);
}

@ApiSearchBoardsByBody()
@Get('boards/body')
@Get('boards/:category/body')
async searchBoardsByBody(
@Param('category') category: string,
@Query('searchQuery') searchQuery: string,
@Query('page', ParseIntPipe) page: number,
@Query('limit', ParseIntPipe) limit: number,
) {
return this.searchService.searchBoardsByBody(searchQuery, page, limit);
return this.searchService.searchBoardsByBody(
category,
searchQuery,
page,
limit,
);
}

@Get('users')
Expand Down
147 changes: 137 additions & 10 deletions src/search/repositories/search.repository.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,167 @@
import { Injectable } from '@nestjs/common';
import { BoardImage } from 'src/boards/entities/board-image.entity';
import { Board } from 'src/boards/entities/board.entity';
import { User } from 'src/users/entities/user.entity';
import { EntityManager } from 'typeorm';

@Injectable()
export class SearchRepository {
constructor(private entityManager: EntityManager) {}
async searchBoardsByHead(searchQuery: string, skip: number, take: number) {
async searchBoardsByHead(
category: string,
searchQuery: string,
skip: number,
take: number,
) {
const boardRepository = this.entityManager.getRepository(Board);

if (category === '전체') {
return boardRepository
.createQueryBuilder('board')
.where(`MATCH(head) AGAINST (:searchQuery IN BOOLEAN MODE)`, {
searchQuery,
})
.leftJoinAndMapMany(
'board.user',
User,
'user',
'user.id = board.userId',
)
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndMapMany(
'board.boardImages',
BoardImage,
'boardImages',
'boardImages.boardId = board.id',
)
.select([
'board.id',
'board.head',
'board.body',
'board.main_category',
'board.sub_category',
'board.createAt',
'board.updateAt',
'user.name',
'userImage.id',
'userImage.userId',
'userImage.imageUrl',
'boardImages.id',
'boardImages.imageUrl',
])
.skip(skip)
.take(take)
.getManyAndCount();
}
return boardRepository
.createQueryBuilder('board')
.select()
.leftJoinAndSelect('board.user', 'user')
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndSelect('board.boardImages', 'boardImages')
.where(`MATCH(head) AGAINST (:searchQuery IN BOOLEAN MODE)`, {
searchQuery,
})
.andWhere('board.main_category = :category', { category })
.leftJoinAndMapMany('board.user', User, 'user', 'user.id = board.userId')
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndMapMany(
'board.boardImages',
BoardImage,
'boardImages',
'boardImages.boardId = board.id',
)
.select([
'board.id',
'board.head',
'board.body',
'board.main_category',
'board.sub_category',
'board.createAt',
'board.updateAt',
'user.name',
'userImage.id',
'userImage.userId',
'userImage.imageUrl',
'boardImages.id',
'boardImages.imageUrl',
])
.skip(skip)
.take(take)
.getManyAndCount();
}

async searchBoardsByBody(searchQuery: string, skip: number, take: number) {
async searchBoardsByBody(
category: string,
searchQuery: string,
skip: number,
take: number,
) {
const boardRepository = this.entityManager.getRepository(Board);

if (category === '전체') {
return boardRepository
.createQueryBuilder('board')
.where(`MATCH(body) AGAINST (:searchQuery IN BOOLEAN MODE)`, {
searchQuery,
})
.leftJoinAndMapMany(
'board.user',
User,
'user',
'user.id = board.userId',
)
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndMapMany(
'board.boardImages',
BoardImage,
'boardImages',
'boardImages.boardId = board.id',
)
.select([
'board.id',
'board.head',
'board.body',
'board.main_category',
'board.sub_category',
'board.createAt',
'board.updateAt',
'user.name',
'userImage.id',
'userImage.userId',
'userImage.imageUrl',
'boardImages.id',
'boardImages.imageUrl',
])
.skip(skip)
.take(take)
.getManyAndCount();
}
return boardRepository
.createQueryBuilder('board')
.select()
.leftJoinAndSelect('board.user', 'user')
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndSelect('board.boardImages', 'boardImages')
.where(`MATCH(body) AGAINST (:searchQuery IN BOOLEAN MODE)`, {
searchQuery,
})
.andWhere('board.main_category = :category', { category })
.leftJoinAndMapMany('board.user', User, 'user', 'user.id = board.userId')
.leftJoinAndSelect('user.userImage', 'userImage')
.leftJoinAndMapMany(
'board.boardImages',
BoardImage,
'boardImages',
'boardImages.boardId = board.id',
)
.select([
'board.id',
'board.head',
'board.body',
'board.main_category',
'board.sub_category',
'board.createAt',
'board.updateAt',
'user.name',
'userImage.id',
'userImage.userId',
'userImage.imageUrl',
'boardImages.id',
'boardImages.imageUrl',
])
.skip(skip)
.take(take)
.getManyAndCount();
Expand Down
38 changes: 29 additions & 9 deletions src/search/services/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ export class SearchService {
private searchRepository: SearchRepository,
private boardLikesRepository: BoardsLikeRepository,
) {}
async searchBoardsByHead(searchQuery: string, page: number, limit: number) {
async searchBoardsByHead(
category: string,
searchQuery: string,
page: number,
limit: number,
) {
const take = limit;
const skip = page <= 0 ? (page = 0) : (page - 1) * take;

const [returnedBoards, total] =
await this.searchRepository.searchBoardsByHead(searchQuery, skip, take);
await this.searchRepository.searchBoardsByHead(
category,
searchQuery,
skip,
take,
);

const last_page = Math.ceil(total / take);

Expand All @@ -33,8 +43,8 @@ export class SearchService {
createAt: board.createAt,
updateAt: board.updateAt,
userId: {
name: board.user.name,
userImage: board.user.userImage ? board.user.userImage : [],
name: board.user[0].name,
userImage: board.user[0].userImage ? board.user[0].userImage : [],
},
boardLike: like,
boardImages: board.boardImages.map((image) => ({
Expand All @@ -58,14 +68,24 @@ export class SearchService {
}
}

async searchBoardsByBody(searchQuery: string, page: number, limit: number) {
async searchBoardsByBody(
category: string,
searchQuery: string,
page: number,
limit: number,
) {
const take = limit;
const skip = page <= 0 ? (page = 0) : (page - 1) * take;

const [returnedBoards, total] =
await this.searchRepository.searchBoardsByBody(searchQuery, skip, take);
await this.searchRepository.searchBoardsByBody(
category,
searchQuery,
skip,
take,
);

const last_page = Math.ceil(total / limit);
const last_page = Math.ceil(total / take);

const boardResponse: BoardResponseDTO[] = await Promise.all(
returnedBoards.map(async (board) => {
Expand All @@ -82,8 +102,8 @@ export class SearchService {
createAt: board.createAt,
updateAt: board.updateAt,
userId: {
name: board.user.name,
userImage: board.user.userImage ? board.user.userImage : [],
name: board.user[0].name,
userImage: board.user[0].userImage ? board.user[0].userImage : [],
},
boardLike: like,
boardImages: board.boardImages.map((image) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export function ApiSearchBoardsByBody() {
return applyDecorators(
ApiOperation({
summary: '게시글 검색 API',
description: `Query String으로 입력된 값을 토대로 게시글의 본문에 일치하는 값을 조회합니다.
ex)'흑돼지고기' 검색 - '흑돼', '돼지', '지고', '고기'로 검색 (정확성 순으로 정렬됨)`,
description: `Query String의 내용과 Param으로 입력된 카테고리를 토대로 게시글의 본문에 일치하는 값을 조회합니다(카테고리에 "전체" 입력 시 전체 게시판 검색).
ex)'흑돼지 고기' 검색 - '흑돼지', '고기' 라는 단어 단위로 검색. (정확성 순으로 정렬됨)`,
}),
ApiResponse({
status: 200,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ export function ApiSearchBoardsByHead() {
return applyDecorators(
ApiOperation({
summary: '게시글 검색 API',
description: `Query String으로 입력된 값을 토대로 게시글의 제목에 일치하는 값을 조회합니다.
ex)'흑돼지고기' 검색 - '흑돼', '돼지', '지고', '고기'로 검색 (정확성 순으로 정렬됨)`,
description: `Query String의 내용과 Param으로 입력된 카테고리를 토대로 게시글의 제목에 일치하는 값을 조회합니다(카테고리에 "전체" 입력 시 전체 게시판 검색).
ex)'흑돼지 고기' 검색 - '흑돼지', '고기' 라는 단어 단위로 검색. (정확성 순으로 정렬됨)`,
}),
ApiResponse({
status: 200,
description: '성공적으로 검색한 게시글(제목 기준) 조회',
description: '성공적으로 검색한 게시글(제목, 카테고리 기준) 조회',
content: {
JSON: {
example: {
Expand Down

0 comments on commit f6c3efe

Please sign in to comment.