From f18228e2ffda6660bdcad6955491ccb18451c1f6 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 30 Dec 2024 01:30:45 +0900 Subject: [PATCH 01/26] =?UTF-8?q?refactor(search):=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20elastic?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=EC=97=B0=EA=B2=B0=20=EC=9C=A0=EB=AC=B4?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20api=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - elastic search enabled = true -> elastic search 검색 - elastic search enabled = false -> rdb 검색 - elastic search healthcheck를 통한 서버 연결 확인 기능 추가 --- .../CommunityBoardQueryApiController.java | 27 -- .../domain/CommunityBoardDocument.java | 29 -- .../CommunityBoardDocumentRepository.java | 10 - .../board/CommunityBoardRepository.java | 10 - .../board/CommunityBoardRepositoryImpl.java | 67 +--- .../CommunityBoardUpdateScheduler.java | 25 -- .../board/CommunityBoardDocumentService.java | 37 --- .../board/CommunityBoardDocumentUseCase.java | 12 - .../RecruitBoardQueryApiController.java | 79 ----- .../RecruitBoardSearchApiController.java | 84 ----- .../domain/RecruitBoardDocument.java | 30 -- .../RecruitBoardDocumentRepository.java | 13 - .../repository/RecruitBoardRepository.java | 5 - .../RecruitBoardRepositoryImpl.java | 124 +------ .../RecruitBoardUpdateScheduler.java | 24 -- .../service/RecruitBoardDocumentService.java | 45 --- .../usecase/RecruitBoardDocumentUseCase.java | 17 - .../search/config/ElasticsearchConfig.java | 26 ++ .../config/ElasticsearchHealthChecker.java | 32 ++ .../CommunityBoardSearchApiController.java | 50 +++ .../RecruitBoardSearchApiController.java | 131 ++++++++ .../search/domain/CommunityBoardDocument.java | 29 ++ .../search/domain/RecruitBoardDocument.java | 29 ++ .../CommunityBoardDocumentRepository.java | 15 + .../RecruitBoardDocumentRepository.java | 15 + .../repository/SearchBoardRepository.java | 26 ++ .../repository/SearchBoardRepositoryImpl.java | 305 ++++++++++++++++++ .../CommunityBoardUpdateScheduler.java | 31 ++ .../RecruitBoardUpdateScheduler.java | 31 ++ .../CommunityBoardDocumentService.java | 39 +++ .../service/RecruitBoardDocumentService.java | 47 +++ .../CommunityBoardDocumentUseCase.java | 12 + .../usecase/RecruitBoardDocumentUseCase.java | 17 + .../global/config/ElasticsearchConfig.java | 24 -- src/main/resources/application.yml | 5 +- 35 files changed, 842 insertions(+), 660 deletions(-) delete mode 100644 src/main/java/com/somemore/domains/community/domain/CommunityBoardDocument.java delete mode 100644 src/main/java/com/somemore/domains/community/repository/board/CommunityBoardDocumentRepository.java delete mode 100644 src/main/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateScheduler.java delete mode 100644 src/main/java/com/somemore/domains/community/service/board/CommunityBoardDocumentService.java delete mode 100644 src/main/java/com/somemore/domains/community/usecase/board/CommunityBoardDocumentUseCase.java delete mode 100644 src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiController.java delete mode 100644 src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoardDocument.java delete mode 100644 src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepository.java delete mode 100644 src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateScheduler.java create mode 100644 src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java create mode 100644 src/main/java/com/somemore/domains/search/config/ElasticsearchHealthChecker.java create mode 100644 src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java create mode 100644 src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java create mode 100644 src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java create mode 100644 src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java create mode 100644 src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java create mode 100644 src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java create mode 100644 src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java create mode 100644 src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java create mode 100644 src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java create mode 100644 src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java create mode 100644 src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java create mode 100644 src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java create mode 100644 src/main/java/com/somemore/domains/search/usecase/CommunityBoardDocumentUseCase.java create mode 100644 src/main/java/com/somemore/domains/search/usecase/RecruitBoardDocumentUseCase.java delete mode 100644 src/main/java/com/somemore/global/config/ElasticsearchConfig.java diff --git a/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java b/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java index e5198817b..2c4ce1437 100644 --- a/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java +++ b/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java @@ -24,7 +24,6 @@ public class CommunityBoardQueryApiController { private final CommunityBoardQueryUseCase communityBoardQueryUseCase; -// private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; @GetMapping("/community-boards") @Operation(summary = "전체 커뮤니티 게시글 조회", description = "전체 커뮤니티 게시글 목록을 조회합니다.") @@ -51,32 +50,6 @@ public ApiResponse> getByWriterId( ); } -// @GetMapping("/community-boards/search") -// @Operation(summary = "커뮤니티 게시글 키워드 검색", description = "키워드로 포함한 커뮤니티 게시글 목록을 조회합니다.") -// public ApiResponse> getCommunityBoardsBySearch( -// String keyword, -// Pageable pageable -// ) { -// return ApiResponse.ok( -// 200, -// communityBoardDocumentUseCase.getCommunityBoardBySearch(keyword, pageable.getPageNumber()), -// "커뮤니티 게시글 검색 리스트 조회 성공" -// ); -// } - - @GetMapping("/community-boards/search") - @Operation(summary = "커뮤니티 게시글 키워드 검색", description = "키워드를 포함한 커뮤니티 게시글 목록을 조회합니다.") - public ApiResponse> getCommunityBoardsBySearch( - @RequestParam String keyword, - Pageable pageable - ) { - return ApiResponse.ok( - 200, - communityBoardQueryUseCase.getCommunityBoards(keyword, pageable.getPageNumber()), - "커뮤니티 게시글 검색 리스트 조회 성공" - ); - } - @GetMapping("/community-board/{id}") @Operation(summary = "커뮤니티 게시글 상세 조회", description = "커뮤니티 게시글의 상세 정보를 조회합니다.") public ApiResponse getById( diff --git a/src/main/java/com/somemore/domains/community/domain/CommunityBoardDocument.java b/src/main/java/com/somemore/domains/community/domain/CommunityBoardDocument.java deleted file mode 100644 index 04a021924..000000000 --- a/src/main/java/com/somemore/domains/community/domain/CommunityBoardDocument.java +++ /dev/null @@ -1,29 +0,0 @@ -//package com.somemore.community.domain; -// -//import jakarta.persistence.Id; -//import lombok.Builder; -//import lombok.Getter; -//import org.springframework.data.elasticsearch.annotations.Document; -//import org.springframework.data.elasticsearch.annotations.Field; -//import org.springframework.data.elasticsearch.annotations.FieldType; -// -//@Getter -//@Document(indexName = "community_board") -//public class CommunityBoardDocument { -// -// @Id -// private Long id; -// -// @Field(type = FieldType.Text, analyzer = "nori_analyzer") -// private String title; -// -// @Field(type = FieldType.Text, analyzer = "nori_analyzer") -// private String content; -// -// @Builder -// public CommunityBoardDocument(Long id, String title, String content) { -// this.id = id; -// this.title = title; -// this.content = content; -// } -//} diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardDocumentRepository.java deleted file mode 100644 index 574b3793e..000000000 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardDocumentRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -//package com.somemore.community.repository.board; -// -//import com.somemore.community.domain.CommunityBoardDocument; -//import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -// -//public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { -/// / List findAll(); -/// / @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") -/// / List findIdsByTitleOrContentContaining(String keyword); -//} diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java index d0e5bc988..8e488f467 100644 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java +++ b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java @@ -11,23 +11,13 @@ public interface CommunityBoardRepository { CommunityBoard save(CommunityBoard communityBoard); - Optional findById(Long id); - Page findCommunityBoards(String keyword, Pageable pageable); - Page findByWriterId(UUID writerId, Pageable pageable); - boolean existsById(Long id); - default boolean doesNotExistById(Long id) { return !existsById(id); } - void deleteAllInBatch(); - - // Page findByCommunityBoardsContaining(String keyword, Pageable pageable); -// void saveDocuments(List communityBoards); List findAll(); -// void deleteDocument(Long id); } diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java index 94a211706..d7c471462 100644 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java @@ -25,7 +25,6 @@ public class CommunityBoardRepositoryImpl implements CommunityBoardRepository { private final JPAQueryFactory queryFactory; private final CommunityBoardJpaRepository communityBoardJpaRepository; -// private final CommunityBoardDocumentRepository documentRepository; private static final QCommunityBoard communityBoard = QCommunityBoard.communityBoard; private static final QVolunteer volunteer = QVolunteer.volunteer; @@ -87,47 +86,6 @@ public boolean existsById(Long id) { return communityBoardJpaRepository.existsByIdAndDeletedFalse(id); } -// @Override -// public Page findByCommunityBoardsContaining(String keyword, Pageable pageable) { -// List boardDocuments = getBoardDocuments(keyword); -// -// List boardIds = boardDocuments.stream() -// .map(CommunityBoardDocument::getId) -// .toList(); -// -// List content = getCommunityBoardsQuery() -// .where(communityBoard.id.in(boardIds) -// .and(isNotDeleted())) -// .offset(pageable.getOffset()) -// .limit(pageable.getPageSize()) -// .fetch(); -// -// JPAQuery countQuery = queryFactory -// .select(communityBoard.count()) -// .from(communityBoard) -// .join(volunteer).on(communityBoard.writerId.eq(volunteer.id)) -// .where(communityBoard.id.in(boardIds) -// .and(isNotDeleted())); -// -// return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); -// } - -// @Override -// public void saveDocuments(List communityBoards) { -// List communityBoardDocuments = convertEntityToDocuments(communityBoards); -// documentRepository.saveAll(communityBoardDocuments); -// } - -// @Override -// public void deleteDocument(Long id) { -// documentRepository.deleteById(id); -// } - - @Override - public List findAll() { - return communityBoardJpaRepository.findAll(); - } - @Override public void deleteAllInBatch() { communityBoardJpaRepository.deleteAllInBatch(); @@ -143,19 +101,10 @@ private JPAQuery getCommunityBoardsQuery() { .orderBy(communityBoard.createdAt.desc()); } -// private List convertEntityToDocuments(List communityBoards) { -// List communityBoardDocuments = new ArrayList<>(); -// -// for (CommunityBoard communityboard : communityBoards) { -// CommunityBoardDocument document = CommunityBoardDocument.builder() -// .id(communityboard.getId()) -// .title(communityboard.getTitle()) -// .content(communityboard.getContent()) -// .build(); -// communityBoardDocuments.add(document); -// } -// return communityBoardDocuments; -// } + @Override + public List findAll() { + return communityBoardJpaRepository.findAll(); + } private BooleanExpression isNotDeleted() { return communityBoard.deleted.eq(false); @@ -170,12 +119,4 @@ private BooleanExpression keywordEq(String keyword) { ? communityBoard.title.containsIgnoreCase(keyword) : null; } - -// private List getBoardDocuments(String keyword) { -// -// if (keyword == null || keyword.isEmpty()) { -// return documentRepository.findAll(); -// } -// return documentRepository.findIdsByTitleOrContentContaining(keyword); -// } } diff --git a/src/main/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateScheduler.java deleted file mode 100644 index 96a303c89..000000000 --- a/src/main/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateScheduler.java +++ /dev/null @@ -1,25 +0,0 @@ -//package com.somemore.community.scheduler; -// -//import com.somemore.community.domain.CommunityBoard; -//import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase; -//import com.somemore.community.usecase.board.CommunityBoardQueryUseCase; -//import lombok.RequiredArgsConstructor; -//import org.springframework.scheduling.annotation.Scheduled; -//import org.springframework.stereotype.Component; -// -//import java.util.List; -// -//@Component -//@RequiredArgsConstructor -//public class CommunityBoardUpdateScheduler { -// -// private final CommunityBoardQueryUseCase communityBoardQueryUseCase; -// private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; -// -// -// @Scheduled(cron = "${spring.schedules.cron.updateCommunityBoardDocuments}") -// public void updateCommunityBoardDocuments() { -// List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); -// communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards); -// } -//} diff --git a/src/main/java/com/somemore/domains/community/service/board/CommunityBoardDocumentService.java b/src/main/java/com/somemore/domains/community/service/board/CommunityBoardDocumentService.java deleted file mode 100644 index 2489e1ad4..000000000 --- a/src/main/java/com/somemore/domains/community/service/board/CommunityBoardDocumentService.java +++ /dev/null @@ -1,37 +0,0 @@ -//package com.somemore.community.service.board; -// -//import com.somemore.community.domain.CommunityBoard; -//import com.somemore.community.dto.response.CommunityBoardResponseDto; -//import com.somemore.community.repository.board.CommunityBoardRepository; -//import com.somemore.community.repository.mapper.CommunityBoardView; -//import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase; -//import lombok.RequiredArgsConstructor; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.List; -// -//@RequiredArgsConstructor -//@Service -//public class CommunityBoardDocumentService implements CommunityBoardDocumentUseCase { -// -// private final CommunityBoardRepository communityBoardRepository; -// private static final int PAGE_SIZE = 10; -// -// @Transactional(readOnly = true) -// @Override -// public Page getCommunityBoardBySearch(String keyword, int page) { -// Pageable pageable = PageRequest.of(page, PAGE_SIZE); -// Page boards = communityBoardRepository.findByCommunityBoardsContaining(keyword, pageable); -// return boards.map(CommunityBoardResponseDto::from); -// } -// -// @Transactional -// @Override -// public void saveCommunityBoardDocuments(List communityBoards) { -// communityBoardRepository.saveDocuments(communityBoards); -// } -//} diff --git a/src/main/java/com/somemore/domains/community/usecase/board/CommunityBoardDocumentUseCase.java b/src/main/java/com/somemore/domains/community/usecase/board/CommunityBoardDocumentUseCase.java deleted file mode 100644 index 155aa7a8d..000000000 --- a/src/main/java/com/somemore/domains/community/usecase/board/CommunityBoardDocumentUseCase.java +++ /dev/null @@ -1,12 +0,0 @@ -//package com.somemore.community.usecase.board; -// -//import com.somemore.community.domain.CommunityBoard; -//import com.somemore.community.dto.response.CommunityBoardResponseDto; -//import org.springframework.data.domain.Page; -// -//import java.util.List; -// -//public interface CommunityBoardDocumentUseCase { -// Page getCommunityBoardBySearch(String keyword, int page); -// void saveCommunityBoardDocuments(List communityBoards); -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java index 06e10c797..2bd0adacb 100644 --- a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java +++ b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java @@ -65,85 +65,6 @@ public ApiResponse> getAll( ); } - @GetMapping("/recruit-boards/search") - @Operation(summary = "모집글 검색 조회", description = "검색 조건을 기반으로 모집글을 조회합니다.") - public ApiResponse> getAllBySearch( - @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, - @RequestParam(required = false) String keyword, - @RequestParam(required = false) VolunteerCategory category, - @RequestParam(required = false) String region, - @RequestParam(required = false) Boolean admitted, - @RequestParam(required = false) RecruitStatus status - ) { - RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .keyword(keyword) - .category(category) - .region(region) - .admitted(admitted) - .status(status) - .pageable(pageable) - .build(); - - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getAllWithCenter(condition), - "봉사 활동 모집글 검색 조회 성공" - ); - } - - @GetMapping("/recruit-boards/nearby") - @Operation(summary = "근처 모집글 조회", description = "주변 반경 내의 봉사 모집글을 조회합니다.") - public ApiResponse> getNearby( - @RequestParam double latitude, - @RequestParam double longitude, - @RequestParam(required = false, defaultValue = "5") double radius, - @RequestParam(required = false) String keyword, - @RequestParam(required = false, defaultValue = "RECRUITING") RecruitStatus status, - @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable - ) { - RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() - .latitude(latitude) - .longitude(longitude) - .radius(radius) - .keyword(keyword) - .status(status) - .pageable(pageable) - .build(); - - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getRecruitBoardsNearby(condition), - "근처 봉사 활동 모집글 조회 성공" - ); - } - - @GetMapping("/recruit-boards/center/{centerId}") - @Operation(summary = "특정 기관 모집글 조회", description = "특정 기관의 봉사 모집글을 조회합니다.") - public ApiResponse> getRecruitBoardsByCenterId( - @PathVariable UUID centerId, - @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, - @RequestParam(required = false) String keyword, - @RequestParam(required = false) VolunteerCategory category, - @RequestParam(required = false) String region, - @RequestParam(required = false) Boolean admitted, - @RequestParam(required = false) RecruitStatus status - ) { - RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .keyword(keyword) - .category(category) - .region(region) - .admitted(admitted) - .status(status) - .pageable(pageable) - .build(); - - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getRecruitBoardsByCenterId(centerId, condition), - "특정 기관 봉사 활동 모집글 조회 성공" - ); - } - @Secured("ROLE_CENTER") @GetMapping("/recruit-boards/me") @Operation(summary = "기관이 작성한 모집글 조회", description = "기관의 봉사 모집글을 조회합니다.") diff --git a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiController.java deleted file mode 100644 index 71fa91fb6..000000000 --- a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiController.java +++ /dev/null @@ -1,84 +0,0 @@ -//package com.somemore.recruitboard.controller; -// -//import com.somemore.global.common.response.ApiResponse; -//import com.somemore.recruitboard.domain.RecruitStatus; -//import com.somemore.recruitboard.domain.VolunteerCategory; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; -//import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -//import com.somemore.recruitboard.usecase.query.RecruitBoardDocumentUseCase; -//import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase; -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.tags.Tag; -//import lombok.RequiredArgsConstructor; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.web.PageableDefault; -//import org.springframework.web.bind.annotation.GetMapping; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RequestParam; -//import org.springframework.web.bind.annotation.RestController; -// -//import static org.springframework.data.domain.Sort.Direction.DESC; -// -//@Tag(name = "Recruit Board Search API", description = "봉사 활동 모집 검색 관련 API") -//@RequiredArgsConstructor -//@RequestMapping("/api/v1") -//@RestController -//public class RecruitBoardSearchApiController { -// -// private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; -// -// @GetMapping("/recruit-boards/search") -// @Operation(summary = "모집글 검색 조회", description = "검색 조건을 기반으로 모집글을 조회합니다.") -// public ApiResponse> getAllBySearch( -// @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, -// @RequestParam(required = false) String keyword, -// @RequestParam(required = false) VolunteerCategory category, -// @RequestParam(required = false) String region, -// @RequestParam(required = false) Boolean admitted, -// @RequestParam(required = false) RecruitStatus status -// ) { -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword(keyword) -// .category(category) -// .region(region) -// .admitted(admitted) -// .status(status) -// .pageable(pageable) -// .build(); -// -// return ApiResponse.ok( -// 200, -// recruitBoardDocumentUseCase.getRecruitBoardBySearch(condition), -// "봉사 활동 모집글 검색 조회 성공" -// ); -// } -// -// @GetMapping("/recruit-boards/nearby") -// @Operation(summary = "근처 모집글 조회", description = "주변 반경 내의 봉사 모집글을 조회합니다.") -// public ApiResponse> getNearby( -// @RequestParam double latitude, -// @RequestParam double longitude, -// @RequestParam(required = false, defaultValue = "5") double radius, -// @RequestParam(required = false) String keyword, -// @RequestParam(required = false, defaultValue = "RECRUITING") RecruitStatus status, -// @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable -// ) { -// RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() -// .latitude(latitude) -// .longitude(longitude) -// .radius(radius) -// .keyword(keyword) -// .status(status) -// .pageable(pageable) -// .build(); -// -// return ApiResponse.ok( -// 200, -// recruitBoardDocumentUseCase.getRecruitBoardsNearbyWithKeyword(condition), -// "근처 봉사 활동 모집글 조회 성공" -// ); -// } -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoardDocument.java b/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoardDocument.java deleted file mode 100644 index c70ac1fed..000000000 --- a/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoardDocument.java +++ /dev/null @@ -1,30 +0,0 @@ -//package com.somemore.recruitboard.domain; -// -//import jakarta.persistence.Id; -//import lombok.Builder; -//import lombok.Getter; -//import org.springframework.data.elasticsearch.annotations.Document; -//import org.springframework.data.elasticsearch.annotations.Field; -//import org.springframework.data.elasticsearch.annotations.FieldType; -// -//@Getter -//@Document(indexName = "recruit_board") -//public class RecruitBoardDocument { -// -// @Id -// private Long id; -// -// @Field(type = FieldType.Text, analyzer = "nori_analyzer") -// private String title; -// -// @Field(type = FieldType.Text, analyzer = "nori_analyzer") -// private String content; -// -// -// @Builder -// public RecruitBoardDocument(Long id, String title, String content) { -// this.id = id; -// this.title = title; -// this.content = content; -// } -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepository.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepository.java deleted file mode 100644 index 524d14a1c..000000000 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -//package com.somemore.recruitboard.repository; -// -//import com.somemore.recruitboard.domain.RecruitBoardDocument; -//import org.springframework.data.elasticsearch.annotations.Query; -//import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -// -//import java.util.List; -// -//public interface RecruitBoardDocumentRepository extends ElasticsearchRepository { -// List findAll(); -// @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") -// List findIdsByTitleOrContentContaining(String keyword); -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java index abc0078cf..a596d76ab 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java @@ -37,9 +37,4 @@ public interface RecruitBoardRepository { long updateStatusToClosedForDateRange(LocalDateTime startTime, LocalDateTime endTime); long updateStatusToCompletedForDateRange(LocalDateTime startTime, LocalDateTime endTime); - -// Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); -// Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); -// void saveDocuments(List recruitBoards); -// void deleteDocument(Long id); } diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java index f7ecc5433..ee9898d2c 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -1,9 +1,5 @@ package com.somemore.domains.recruitboard.repository; -import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; -import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; -import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; - import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; @@ -41,7 +37,6 @@ public class RecruitBoardRepositoryImpl implements RecruitBoardRepository { private final RecruitBoardJpaRepository recruitBoardJpaRepository; private final JPAQueryFactory queryFactory; - // private final RecruitBoardDocumentRepository documentRepository; private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; private static final QLocation location = QLocation.location; @@ -216,7 +211,7 @@ public List findAll() { @Override public long updateStatusToClosedForDateRange(LocalDateTime startTime, - LocalDateTime endTime) { + LocalDateTime endTime) { return queryFactory.update(recruitBoard) .set(recruitBoard.recruitStatus, CLOSED) .where( @@ -229,7 +224,7 @@ public long updateStatusToClosedForDateRange(LocalDateTime startTime, @Override public long updateStatusToCompletedForDateRange(LocalDateTime startTime, - LocalDateTime endTime) { + LocalDateTime endTime) { return queryFactory.update(recruitBoard) .set(recruitBoard.recruitStatus, COMPLETED) .where( @@ -240,99 +235,6 @@ public long updateStatusToCompletedForDateRange(LocalDateTime startTime, .execute(); } -// @Override -// public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { -// QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; -// QLocation location = QLocation.location; -// QCenter center = QCenter.center; -// -// List boardDocuments = getBoardDocuments(condition.keyword()); -// -// List boardIds = boardDocuments.stream() -// .map(RecruitBoardDocument::getId) -// .toList(); -// -// Pageable pageable = condition.pageable(); -// -// BooleanExpression predicate = locationBetween(condition) -// .and(statusEq(condition.status())) -// .and(isNotDeleted()); -// -// List content = queryFactory -// .select(getRecruitBoardDetailConstructorExpression(recruitBoard, location, center)) -// .from(recruitBoard) -// .join(location).on(recruitBoard.locationId.eq(location.id)) -// .join(center).on(recruitBoard.centerId.eq(center.id)) -// .where(recruitBoard.id.in(boardIds) -// .and(predicate)) -// .offset(pageable.getOffset()) -// .limit(pageable.getPageSize()) -// .orderBy(toOrderSpecifiers(pageable.getSort())) -// .fetch(); -// -// JPAQuery countQuery = queryFactory -// .select(recruitBoard.count()) -// .from(recruitBoard) -// .join(location).on(recruitBoard.locationId.eq(location.id)) -// .join(center).on(recruitBoard.centerId.eq(center.id)) -// .where(recruitBoard.id.in(boardIds) -// .and(predicate)); -// -// return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); - -// } - -// @Override -// public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { -// QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; -// QCenter center = QCenter.center; -// -// -// List boardDocuments = getBoardDocuments(condition.keyword()); -// -// List boardIds = boardDocuments.stream() -// .map(RecruitBoardDocument::getId) -// .toList(); -// -// Pageable pageable = condition.pageable(); -// BooleanExpression predicate = isNotDeleted() -// .and(volunteerCategoryEq(condition.category())) -// .and(regionEq(condition.region())) -// .and(admittedEq(condition.admitted())) -// .and(statusEq(condition.status())); -// -// List content = queryFactory -// .select(getRecruitBoardWithCenterConstructorExpression(recruitBoard, center)) -// .from(recruitBoard) -// .where(recruitBoard.id.in(boardIds) -// .and(predicate)) -// .join(center).on(recruitBoard.centerId.eq(center.id)) -// .offset(pageable.getOffset()) -// .limit(pageable.getPageSize()) -// .orderBy(toOrderSpecifiers(pageable.getSort())) -// .fetch(); -// -// JPAQuery countQuery = queryFactory -// .select(recruitBoard.count()) -// .from(recruitBoard) -// .join(center).on(recruitBoard.centerId.eq(center.id)) -// .where(recruitBoard.id.in(boardIds) -// .and(predicate)); -// -// return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); -// } -// -// @Override -// public void saveDocuments(List recruitBoards) { -// List recruitBoardDocuments = convertEntityToDocuments(recruitBoards); -// documentRepository.saveAll(recruitBoardDocuments); -// } - -// @Override -// public void deleteDocument(Long id) { -// documentRepository.deleteById(id); -// } - private static BooleanExpression idEq(Long id) { return recruitBoard.id.eq(id); } @@ -434,26 +336,4 @@ private static ConstructorExpression getRecruitBoardDetailCo recruitBoard, location.address, location.latitude, location.longitude, userCommonAttribute.name); } - -// private List convertEntityToDocuments(List recruitBoards) { -// List communityBoardDocuments = new ArrayList<>(); -// -// for (RecruitBoard recruitBoard : recruitBoards) { -// RecruitBoardDocument document = RecruitBoardDocument.builder() -// .id(recruitBoard.getId()) -// .title(recruitBoard.getTitle()) -// .content(recruitBoard.getContent()) -// .build(); -// communityBoardDocuments.add(document); -// } -// return communityBoardDocuments; -// } -// -// private List getBoardDocuments(String keyword) { -// -// if (keyword == null || keyword.isEmpty()) { -// return documentRepository.findAll(); -// } -// return documentRepository.findIdsByTitleOrContentContaining(keyword); -// } } diff --git a/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateScheduler.java deleted file mode 100644 index 795ab7cb0..000000000 --- a/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateScheduler.java +++ /dev/null @@ -1,24 +0,0 @@ -//package com.somemore.recruitboard.scheduler; -// -//import com.somemore.recruitboard.domain.RecruitBoard; -//import com.somemore.recruitboard.usecase.query.RecruitBoardDocumentUseCase; -//import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase; -//import lombok.RequiredArgsConstructor; -//import org.springframework.scheduling.annotation.Scheduled; -//import org.springframework.stereotype.Component; -// -//import java.util.List; -// -//@Component -//@RequiredArgsConstructor -//public class RecruitBoardUpdateScheduler { -// -// private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; -// private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; -// -// @Scheduled(cron = "${spring.schedules.cron.updateRecruitBoardDocuments}") -// public void updateRecruitBoardDocuments() { -// List recruitBoards = recruitBoardQueryUseCase.getAllRecruitBoards(); -// recruitBoardDocumentUseCase.saveRecruitBoardDocuments(recruitBoards); -// } -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentService.java b/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentService.java index f77440783..e69de29bb 100644 --- a/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentService.java +++ b/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentService.java @@ -1,45 +0,0 @@ -//package com.somemore.recruitboard.service.query; -// -//import com.somemore.recruitboard.domain.RecruitBoard; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; -//import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -//import com.somemore.recruitboard.repository.RecruitBoardRepository; -//import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; -//import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; -//import com.somemore.recruitboard.usecase.query.RecruitBoardDocumentUseCase; -//import lombok.RequiredArgsConstructor; -//import org.springframework.data.domain.Page; -//import org.springframework.stereotype.Service; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.List; -// -//@RequiredArgsConstructor -//@Service -//public class RecruitBoardDocumentService implements RecruitBoardDocumentUseCase { -// -// private final RecruitBoardRepository recruitBoardRepository; -// -// @Transactional(readOnly = true) -// @Override -// public Page getRecruitBoardBySearch(RecruitBoardSearchCondition condition) { -// Page boards = recruitBoardRepository.findByRecruitBoardsContaining(condition); -// return boards.map(RecruitBoardWithCenterResponseDto::from); -// } -// -// @Transactional(readOnly = true) -// @Override -// public Page getRecruitBoardsNearbyWithKeyword( -// RecruitBoardNearByCondition condition) { -// Page boards = recruitBoardRepository.findAllNearbyWithKeyword(condition); -// return boards.map(RecruitBoardDetailResponseDto::from); -// } -// -// @Transactional -// @Override -// public void saveRecruitBoardDocuments(List recruitBoards) { -// recruitBoardRepository.saveDocuments(recruitBoards); -// } -//} diff --git a/src/main/java/com/somemore/domains/recruitboard/usecase/RecruitBoardDocumentUseCase.java b/src/main/java/com/somemore/domains/recruitboard/usecase/RecruitBoardDocumentUseCase.java index 22f97e567..e69de29bb 100644 --- a/src/main/java/com/somemore/domains/recruitboard/usecase/RecruitBoardDocumentUseCase.java +++ b/src/main/java/com/somemore/domains/recruitboard/usecase/RecruitBoardDocumentUseCase.java @@ -1,17 +0,0 @@ -//package com.somemore.recruitboard.usecase.query; -// -//import com.somemore.recruitboard.domain.RecruitBoard; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; -//import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -//import org.springframework.data.domain.Page; -// -//import java.util.List; -// -//public interface RecruitBoardDocumentUseCase { -// Page getRecruitBoardBySearch(RecruitBoardSearchCondition condition); -// Page getRecruitBoardsNearbyWithKeyword( -// RecruitBoardNearByCondition condition); -// void saveRecruitBoardDocuments(List recruitBoards); -//} diff --git a/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java b/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java new file mode 100644 index 000000000..b80f34c2a --- /dev/null +++ b/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java @@ -0,0 +1,26 @@ +package com.somemore.domains.search.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.elasticsearch.client.ClientConfiguration; +import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; + +@Configuration +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class ElasticsearchConfig extends ElasticsearchConfiguration { + @Value("${elastic.search.uri}") + private String uri; + @Value("${elastic.search.username}") + private String username; + @Value("${elastic.search.password}") + private String password; + + @Override + public ClientConfiguration clientConfiguration() { + return ClientConfiguration.builder() + .connectedTo(uri) + .withBasicAuth(username, password) + .build(); + } +} diff --git a/src/main/java/com/somemore/domains/search/config/ElasticsearchHealthChecker.java b/src/main/java/com/somemore/domains/search/config/ElasticsearchHealthChecker.java new file mode 100644 index 000000000..07cf4bd72 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/config/ElasticsearchHealthChecker.java @@ -0,0 +1,32 @@ +package com.somemore.domains.search.config; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.HealthStatus; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; + +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Configuration +@Component +@RequiredArgsConstructor +public class ElasticsearchHealthChecker { + + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchHealthChecker.class); + private final ElasticsearchClient elasticsearchClient; + + public boolean isElasticsearchRunning() { + try { + HealthResponse healthResponse = elasticsearchClient.cluster().health(); + return healthResponse.status() != HealthStatus.Red; + } catch (RuntimeException | IOException e) { + logger.info("Elasticsearch is not available: {}", e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java new file mode 100644 index 000000000..e080b4c4e --- /dev/null +++ b/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java @@ -0,0 +1,50 @@ +package com.somemore.domains.search.controller; + +import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.community.usecase.board.CommunityBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; +import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; +import com.somemore.global.common.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Optional; + +@Tag(name = "Community Board Search API", description = "커뮤니티 게시글 검색 관련 API") +@RequiredArgsConstructor +@RequestMapping("/api") +@RestController +public class CommunityBoardSearchApiController { + + private final CommunityBoardQueryUseCase communityBoardQueryUseCase; + private final Optional communityBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @GetMapping("/community-boards/search") + @Operation(summary = "커뮤니티 게시글 키워드 검색", description = "키워드를 포함한 커뮤니티 게시글 목록을 조회합니다.") + public ApiResponse> getCommunityBoardsBySearch( + @RequestParam String keyword, + Pageable pageable + ) { + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + return ApiResponse.ok( + 200, + communityBoardDocumentUseCase.get().getCommunityBoardBySearch(keyword, pageable.getPageNumber()), + "커뮤니티 게시글 검색 리스트 조회 성공" + ); + } else { + return ApiResponse.ok( + 200, + communityBoardQueryUseCase.getCommunityBoards(keyword, pageable.getPageNumber()), + "커뮤니티 게시글 검색 리스트 조회 성공" + ); + } + } +} diff --git a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java new file mode 100644 index 000000000..48e5b1f4e --- /dev/null +++ b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java @@ -0,0 +1,131 @@ +package com.somemore.domains.search.controller; + +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.VolunteerCategory; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.recruitboard.usecase.query.RecruitBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; +import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; +import com.somemore.global.common.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.web.bind.annotation.*; + +import java.util.Optional; +import java.util.UUID; + +import static org.springframework.data.domain.Sort.Direction.DESC; + +@Tag(name = "Recruit Board Search API", description = "봉사 활동 모집글 검색 관련 API") +@RequiredArgsConstructor +@RequestMapping("/api") +@RestController +public class RecruitBoardSearchApiController { + + private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; + private final Optional recruitBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @GetMapping("/recruit-boards/search") + @Operation(summary = "모집글 검색 조회", description = "검색 조건을 기반으로 모집글을 조회합니다.") + public ApiResponse> getAllBySearch( + @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) VolunteerCategory category, + @RequestParam(required = false) String region, + @RequestParam(required = false) Boolean admitted, + @RequestParam(required = false) RecruitStatus status + ) { + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword(keyword) + .category(category) + .region(region) + .admitted(admitted) + .status(status) + .pageable(pageable) + .build(); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + return ApiResponse.ok( + 200, + recruitBoardDocumentUseCase.get().getRecruitBoardBySearch(condition), + "봉사 활동 모집글 검색 조회 성공" + ); + } else { + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getAllWithCenter(condition), + "봉사 활동 모집글 검색 조회 성공" + ); + } + } + + @GetMapping("/recruit-boards/nearby") + @Operation(summary = "근처 모집글 조회", description = "주변 반경 내의 봉사 모집글을 조회합니다.") + public ApiResponse> getNearbyBySearch( + @RequestParam double latitude, + @RequestParam double longitude, + @RequestParam(required = false, defaultValue = "5") double radius, + @RequestParam(required = false) String keyword, + @RequestParam(required = false, defaultValue = "RECRUITING") RecruitStatus status, + @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable + ) { + RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() + .latitude(latitude) + .longitude(longitude) + .radius(radius) + .keyword(keyword) + .status(status) + .pageable(pageable) + .build(); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + return ApiResponse.ok( + 200, + recruitBoardDocumentUseCase.get().getRecruitBoardsNearbyWithKeyword(condition), + "근처 봉사 활동 모집글 조회 성공" + ); + } else { + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getRecruitBoardsNearby(condition), + "근처 봉사 활동 모집글 조회 성공" + ); + } + } + + @GetMapping("/recruit-boards/center/{centerId}") + @Operation(summary = "특정 기관 모집글 조회", description = "특정 기관의 봉사 모집글을 조회합니다.") + public ApiResponse> getRecruitBoardsByCenterId( + @PathVariable UUID centerId, + @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) VolunteerCategory category, + @RequestParam(required = false) String region, + @RequestParam(required = false) Boolean admitted, + @RequestParam(required = false) RecruitStatus status + ) { + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword(keyword) + .category(category) + .region(region) + .admitted(admitted) + .status(status) + .pageable(pageable) + .build(); + + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getRecruitBoardsByCenterId(centerId, condition), + "기관 봉사 활동 모집글 조회 성공" + ); + } +} diff --git a/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java new file mode 100644 index 000000000..0a22e77b3 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java @@ -0,0 +1,29 @@ +package com.somemore.domains.search.domain; + +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +@Getter +@Document(indexName = "community_board") +public class CommunityBoardDocument { + + @Id + private Long id; + + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String title; + + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String content; + + @Builder + public CommunityBoardDocument(Long id, String title, String content) { + this.id = id; + this.title = title; + this.content = content; + } +} diff --git a/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java b/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java new file mode 100644 index 000000000..74a6db9cd --- /dev/null +++ b/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java @@ -0,0 +1,29 @@ +package com.somemore.domains.search.domain; + +import jakarta.persistence.Id; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.elasticsearch.annotations.Document; +import org.springframework.data.elasticsearch.annotations.Field; +import org.springframework.data.elasticsearch.annotations.FieldType; + +@Getter +@Document(indexName = "recruit_board") +public class RecruitBoardDocument { + + @Id + private Long id; + + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String title; + + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String content; + + @Builder + public RecruitBoardDocument(Long id, String title, String content) { + this.id = id; + this.title = title; + this.content = content; + } +} diff --git a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java new file mode 100644 index 000000000..8f40913ba --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -0,0 +1,15 @@ +package com.somemore.domains.search.repository; + +import com.somemore.domains.search.domain.CommunityBoardDocument; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.elasticsearch.annotations.Query; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") +public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { + List findAll(); + @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") + List findIdsByTitleOrContentContaining(String keyword); +} diff --git a/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java new file mode 100644 index 000000000..6de3e3131 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java @@ -0,0 +1,15 @@ +package com.somemore.domains.search.repository; + +import com.somemore.domains.search.domain.RecruitBoardDocument; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.elasticsearch.annotations.Query; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") +public interface RecruitBoardDocumentRepository extends ElasticsearchRepository { + List findAll(); + @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") + List findIdsByTitleOrContentContaining(String keyword); +} diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java new file mode 100644 index 000000000..d1a89eadb --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java @@ -0,0 +1,26 @@ +package com.somemore.domains.search.repository; + +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface SearchBoardRepository { + + Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); + Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); + void saveRecruitBoardDocuments(List recruitBoards); + void deleteRecruitBoardDocument(Long id); + + + Page findByCommunityBoardsContaining(String keyword, Pageable pageable); + void saveCommunityBoardDocuments(List communityBoards); + void deleteCommunityBoardDocument(Long id); +} diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java new file mode 100644 index 000000000..55a610b62 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -0,0 +1,305 @@ +package com.somemore.domains.search.repository; + +import com.querydsl.core.types.ConstructorExpression; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.somemore.domains.center.domain.QCenter; +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.domain.QCommunityBoard; +import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.location.domain.QLocation; +import com.somemore.domains.location.utils.GeoUtils; +import com.somemore.domains.recruitboard.domain.QRecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.VolunteerCategory; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.CommunityBoardDocument; +import com.somemore.domains.search.domain.RecruitBoardDocument; +import com.somemore.domains.volunteer.domain.QVolunteer; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Repository +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class SearchBoardRepositoryImpl implements SearchBoardRepository { + + private final RecruitBoardDocumentRepository recruitBoardDocumentRepository; + private final CommunityBoardDocumentRepository communityBoardDocumentRepository; + private final JPAQueryFactory queryFactory; + + private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; + private static final QLocation location = QLocation.location; + private static final QCenter center = QCenter.center; + + private static final QCommunityBoard communityBoard = QCommunityBoard.communityBoard; + private static final QVolunteer volunteer = QVolunteer.volunteer; + + @Override + public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { + + List boardDocuments = getRecruitBoardDocuments(condition.keyword()); + + List boardIds = boardDocuments.stream() + .map(RecruitBoardDocument::getId) + .toList(); + + Pageable pageable = condition.pageable(); + BooleanExpression predicate = isNotDeletedRecruitBoard() + .and(volunteerCategoryEq(condition.category())) + .and(regionEq(condition.region())) + .and(admittedEq(condition.admitted())) + .and(statusEq(condition.status())); + + List content = queryFactory + .select(getRecruitBoardWithCenterConstructorExpression()) + .from(recruitBoard) + .where(recruitBoard.id.in(boardIds) + .and(predicate)) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(recruitBoard.count()) + .from(recruitBoard) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .where(recruitBoard.id.in(boardIds) + .and(predicate)); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + @Override + public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { + QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; + QLocation location = QLocation.location; + QCenter center = QCenter.center; + + List boardDocuments = getRecruitBoardDocuments(condition.keyword()); + + List boardIds = boardDocuments.stream() + .map(RecruitBoardDocument::getId) + .toList(); + + Pageable pageable = condition.pageable(); + + BooleanExpression predicate = locationBetween(condition) + .and(statusEq(condition.status())) + .and(isNotDeletedRecruitBoard()); + + List content = queryFactory + .select(getRecruitBoardDetailConstructorExpression()) + .from(recruitBoard) + .join(location).on(recruitBoard.locationId.eq(location.id)) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .where(recruitBoard.id.in(boardIds) + .and(predicate)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(recruitBoard.count()) + .from(recruitBoard) + .join(location).on(recruitBoard.locationId.eq(location.id)) + .join(center).on(recruitBoard.centerId.eq(center.id)) + .where(recruitBoard.id.in(boardIds) + .and(predicate)); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + @Override + public void saveRecruitBoardDocuments(List recruitBoards) { + List recruitBoardDocuments = convertRecruitBoardToDocuments(recruitBoards); + recruitBoardDocumentRepository.saveAll(recruitBoardDocuments); + } + + + @Override + public void deleteRecruitBoardDocument(Long id) { + recruitBoardDocumentRepository.deleteById(id); + } + + + @Override + public Page findByCommunityBoardsContaining(String keyword, Pageable pageable) { + List boardDocuments = getCommunityBoardDocuments(keyword); + + List boardIds = boardDocuments.stream() + .map(CommunityBoardDocument::getId) + .toList(); + + List content = getCommunityBoardsQuery() + .where(communityBoard.id.in(boardIds) + .and(isNotDeletedCommunityBoard())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(communityBoard.count()) + .from(communityBoard) + .join(volunteer).on(communityBoard.writerId.eq(volunteer.id)) + .where(communityBoard.id.in(boardIds) + .and(isNotDeletedCommunityBoard())); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + @Override + public void saveCommunityBoardDocuments(List communityBoards) { + List communityBoardDocuments = convertCommunityBoardToDocuments(communityBoards); + communityBoardDocumentRepository.saveAll(communityBoardDocuments); + } + + @Override + public void deleteCommunityBoardDocument(Long id) { + communityBoardDocumentRepository.deleteById(id); + } + + private List convertRecruitBoardToDocuments(List recruitBoards) { + List communityBoardDocuments = new ArrayList<>(); + + for (RecruitBoard recruitBoard : recruitBoards) { + RecruitBoardDocument document = RecruitBoardDocument.builder() + .id(recruitBoard.getId()) + .title(recruitBoard.getTitle()) + .content(recruitBoard.getContent()) + .build(); + communityBoardDocuments.add(document); + } + return communityBoardDocuments; + } + + private List convertCommunityBoardToDocuments(List communityBoards) { + List communityBoardDocuments = new ArrayList<>(); + + for (CommunityBoard communityboard : communityBoards) { + CommunityBoardDocument document = CommunityBoardDocument.builder() + .id(communityboard.getId()) + .title(communityboard.getTitle()) + .content(communityboard.getContent()) + .build(); + communityBoardDocuments.add(document); + } + return communityBoardDocuments; + } + + private JPAQuery getCommunityBoardsQuery() { + return queryFactory + .select(Projections.constructor(CommunityBoardView.class, + communityBoard, + volunteer.nickname)) + .from(communityBoard) + .join(volunteer).on(communityBoard.writerId.eq(volunteer.id)) + .orderBy(communityBoard.createdAt.desc()); + } + + private BooleanExpression isNotDeletedRecruitBoard() { + return recruitBoard.deleted.eq(false); + } + + private BooleanExpression isNotDeletedCommunityBoard() { + return communityBoard.deleted.eq(false); + } + + private BooleanExpression volunteerCategoryEq(VolunteerCategory category) { + return category != null ? recruitBoard.recruitmentInfo.volunteerCategory.eq(category) + : null; + } + + private BooleanExpression regionEq(String region) { + return StringUtils.isNotBlank(region) + ? recruitBoard.recruitmentInfo.region.eq( + region) : null; + } + + private BooleanExpression admittedEq(Boolean admitted) { + return admitted != null ? recruitBoard.recruitmentInfo.admitted.eq(admitted) + : null; + } + + private BooleanExpression statusEq(RecruitStatus status) { + return status != null ? recruitBoard.recruitStatus.eq(status) : null; + } + + private BooleanExpression locationBetween(RecruitBoardNearByCondition condition) { + double[] coordinates = GeoUtils.calculateMaxMinCoordinates( + condition.latitude(), + condition.longitude(), + condition.radius()); + + double minLatitude = coordinates[0]; + double minLongitude = coordinates[1]; + double maxLatitude = coordinates[2]; + double maxLongitude = coordinates[3]; + + return location.latitude.between(minLatitude, maxLatitude) + .and(location.longitude.between(minLongitude, maxLongitude)); + } + + private OrderSpecifier[] toOrderSpecifiers(Sort sort) { + return sort.stream() + .map(order -> { + String property = order.getProperty(); + if ("created_at".equals(property)) { + return order.isAscending() + ? recruitBoard.createdAt.asc() + : recruitBoard.createdAt.desc(); + } + if ("volunteer_start_date_time".equals(property)) { + return order.isAscending() + ? recruitBoard.recruitmentInfo.volunteerStartDateTime.asc() + : recruitBoard.recruitmentInfo.volunteerStartDateTime.desc(); + } + throw new IllegalStateException("Invalid sort property: " + property); + }) + .toArray(OrderSpecifier[]::new); + } + + private static ConstructorExpression getRecruitBoardWithCenterConstructorExpression() { + return Projections.constructor(RecruitBoardWithCenter.class, + recruitBoard, center.name); + } + + private static ConstructorExpression getRecruitBoardDetailConstructorExpression() { + return Projections.constructor(RecruitBoardDetail.class, + recruitBoard, location.address, location.latitude, location.longitude, center.name); + } + private List getRecruitBoardDocuments(String keyword) { + + if (keyword == null || keyword.isEmpty()) { + return recruitBoardDocumentRepository.findAll(); + } + return recruitBoardDocumentRepository.findIdsByTitleOrContentContaining(keyword); + } + + private List getCommunityBoardDocuments(String keyword) { + + if (keyword == null || keyword.isEmpty()) { + return communityBoardDocumentRepository.findAll(); + } + return communityBoardDocumentRepository.findIdsByTitleOrContentContaining(keyword); + } +} diff --git a/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java new file mode 100644 index 000000000..a29e0e329 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java @@ -0,0 +1,31 @@ +package com.somemore.domains.search.scheduler; + +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.usecase.board.CommunityBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; +import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class CommunityBoardUpdateScheduler { + + private final CommunityBoardQueryUseCase communityBoardQueryUseCase; + private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @Scheduled(cron = "${spring.schedules.cron.updateCommunityBoardDocuments}") + public void updateCommunityBoardDocuments() { + List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java new file mode 100644 index 000000000..5fcee4022 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java @@ -0,0 +1,31 @@ +package com.somemore.domains.search.scheduler; + +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.usecase.query.RecruitBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; +import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class RecruitBoardUpdateScheduler { + + private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; + private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @Scheduled(cron = "${spring.schedules.cron.updateRecruitBoardDocuments}") + public void updateRecruitBoardDocuments() { + List recruitBoards = recruitBoardQueryUseCase.getAllRecruitBoards(); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + recruitBoardDocumentUseCase.saveRecruitBoardDocuments(recruitBoards); + } + } +} diff --git a/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java new file mode 100644 index 000000000..4434c34a3 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java @@ -0,0 +1,39 @@ +package com.somemore.domains.search.service; + +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Service +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class CommunityBoardDocumentService implements CommunityBoardDocumentUseCase { + + private final SearchBoardRepository searchBoardRepository; + private static final int PAGE_SIZE = 10; + + @Transactional(readOnly = true) + @Override + public Page getCommunityBoardBySearch(String keyword, int page) { + Pageable pageable = PageRequest.of(page, PAGE_SIZE); + Page boards = searchBoardRepository.findByCommunityBoardsContaining(keyword, pageable); + return boards.map(CommunityBoardResponseDto::from); + } + + @Transactional + @Override + public void saveCommunityBoardDocuments(List communityBoards) { + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); + } +} diff --git a/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java new file mode 100644 index 000000000..6cd7feea8 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java @@ -0,0 +1,47 @@ +package com.somemore.domains.search.service; + +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Service +@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +public class RecruitBoardDocumentService implements RecruitBoardDocumentUseCase { + + private final SearchBoardRepository searchBoardRepository; + + @Transactional(readOnly = true) + @Override + public Page getRecruitBoardBySearch(RecruitBoardSearchCondition condition) { + Page boards = searchBoardRepository.findByRecruitBoardsContaining(condition); + return boards.map(RecruitBoardWithCenterResponseDto::from); + } + + @Transactional(readOnly = true) + @Override + public Page getRecruitBoardsNearbyWithKeyword( + RecruitBoardNearByCondition condition) { + Page boards = searchBoardRepository.findAllNearbyWithKeyword(condition); + return boards.map(RecruitBoardDetailResponseDto::from); + } + + @Transactional + @Override + public void saveRecruitBoardDocuments(List recruitBoards) { + searchBoardRepository.saveRecruitBoardDocuments(recruitBoards); + } +} diff --git a/src/main/java/com/somemore/domains/search/usecase/CommunityBoardDocumentUseCase.java b/src/main/java/com/somemore/domains/search/usecase/CommunityBoardDocumentUseCase.java new file mode 100644 index 000000000..a3728704e --- /dev/null +++ b/src/main/java/com/somemore/domains/search/usecase/CommunityBoardDocumentUseCase.java @@ -0,0 +1,12 @@ +package com.somemore.domains.search.usecase; + +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import org.springframework.data.domain.Page; + +import java.util.List; + +public interface CommunityBoardDocumentUseCase { + Page getCommunityBoardBySearch(String keyword, int page); + void saveCommunityBoardDocuments(List communityBoards); +} diff --git a/src/main/java/com/somemore/domains/search/usecase/RecruitBoardDocumentUseCase.java b/src/main/java/com/somemore/domains/search/usecase/RecruitBoardDocumentUseCase.java new file mode 100644 index 000000000..14b0f12aa --- /dev/null +++ b/src/main/java/com/somemore/domains/search/usecase/RecruitBoardDocumentUseCase.java @@ -0,0 +1,17 @@ +package com.somemore.domains.search.usecase; + +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import org.springframework.data.domain.Page; + +import java.util.List; + +public interface RecruitBoardDocumentUseCase { + Page getRecruitBoardBySearch(RecruitBoardSearchCondition condition); + Page getRecruitBoardsNearbyWithKeyword( + RecruitBoardNearByCondition condition); + void saveRecruitBoardDocuments(List recruitBoards); +} diff --git a/src/main/java/com/somemore/global/config/ElasticsearchConfig.java b/src/main/java/com/somemore/global/config/ElasticsearchConfig.java deleted file mode 100644 index 9216bd000..000000000 --- a/src/main/java/com/somemore/global/config/ElasticsearchConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -//package com.somemore.global.configure; -// -//import org.springframework.beans.factory.annotation.Value; -//import org.springframework.context.annotation.Configuration; -//import org.springframework.data.elasticsearch.client.ClientConfiguration; -//import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; -// -//@Configuration -//public class ElasticsearchConfig extends ElasticsearchConfiguration { -// @Value("${elastic.search.uri}") -// private String uri; -// @Value("${elastic.search.username}") -// private String username; -// @Value("${elastic.search.password}") -// private String password; -// -// @Override -// public ClientConfiguration clientConfiguration() { -// return ClientConfiguration.builder() -// .connectedTo(uri) -// .withBasicAuth(username, password) -// .build(); -// } -//} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f72b3f8e6..c95439dfd 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -42,10 +42,6 @@ spring: port: ${REDIS_PORT} password: ${REDIS_PASSWORD} - elasticsearch: - repositories: - enabled: true - security: oauth2: client: @@ -132,6 +128,7 @@ elastic: uri: ${ELASTIC_URI} username: ${ELASTIC_USERNAME} password: ${ELASTIC_PASSWORD} + enabled: false management: health: From c1a677f337f28b74fdb3dad21b881c74986989b0 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Thu, 9 Jan 2025 16:24:09 +0900 Subject: [PATCH 02/26] =?UTF-8?q?test(search):=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20elastic?= =?UTF-8?q?=20=EC=84=9C=EB=B2=84=20=EC=97=B0=EA=B2=B0=20=EC=9C=A0=EB=AC=B4?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20test=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - elastic search enabled = true -> test 실행 (local에서 테스트 시 사용 예정) - elastic search enabled = false -> test ignore --- .../CommunityBoardQueryApiControllerTest.java | 49 ---- .../CommunityBoardDocumentRepositoryTest.java | 79 ----- .../CommunityBoardRepositoryTest.java | 32 +- .../CommunityBoardUpdateSchedulerTest.java | 53 ---- .../CommunityBoardDocumentServiceTest.java | 83 ------ .../board/CommunityBoardQueryServiceTest.java | 29 +- .../RecruitBoardQueryApiControllerTest.java | 49 ---- .../RecruitBoardSearchApiControllerTest.java | 84 ------ .../RecruitBoardDocumentRepositoryTest.java | 128 -------- .../RecruitBoardRepositoryImplTest.java | 33 --- .../RecruitBoardUpdateSchedulerTest.java | 60 ---- .../RecruitBoardDocumentServiceTest.java | 157 ---------- .../service/RecruitBoardQueryServiceTest.java | 38 --- ...CommunityBoardSearchApiControllerTest.java | 56 ++++ .../RecruitBoardSearchApiControllerTest.java | 84 ++++++ .../repository/SearchBoardRepositoryTest.java | 277 ++++++++++++++++++ .../CommunityBoardUpdateSchedulerTest.java | 59 ++++ .../RecruitBoardUpdateSchedulerTest.java | 66 +++++ .../CommunityBoardDocumentServiceTest.java | 113 +++++++ .../RecruitBoardDocumentServiceTest.java | 198 +++++++++++++ src/test/resources/application-test.yml | 12 +- 21 files changed, 858 insertions(+), 881 deletions(-) delete mode 100644 src/test/java/com/somemore/domains/community/repository/CommunityBoardDocumentRepositoryTest.java delete mode 100644 src/test/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateSchedulerTest.java delete mode 100644 src/test/java/com/somemore/domains/community/service/board/CommunityBoardDocumentServiceTest.java delete mode 100644 src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiControllerTest.java delete mode 100644 src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java delete mode 100644 src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateSchedulerTest.java create mode 100644 src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java create mode 100644 src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java create mode 100644 src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java create mode 100644 src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java create mode 100644 src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java create mode 100644 src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java create mode 100644 src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java diff --git a/src/test/java/com/somemore/domains/community/controller/CommunityBoardQueryApiControllerTest.java b/src/test/java/com/somemore/domains/community/controller/CommunityBoardQueryApiControllerTest.java index 3ae460295..4711c18e7 100644 --- a/src/test/java/com/somemore/domains/community/controller/CommunityBoardQueryApiControllerTest.java +++ b/src/test/java/com/somemore/domains/community/controller/CommunityBoardQueryApiControllerTest.java @@ -27,9 +27,6 @@ public class CommunityBoardQueryApiControllerTest extends ControllerTestSupport @MockBean private CommunityBoardQueryUseCase communityBoardQueryUseCase; -// @MockBean -// private CommunityBoardDocumentUseCase communityBoardDocumentUseCase; - @Test @DisplayName("커뮤니티 게시글 전체 조회 성공") void getAll() throws Exception { @@ -100,50 +97,4 @@ void getById() throws Exception { verify(communityBoardQueryUseCase, times(1)) .getCommunityBoardDetail(any()); } - - @Test - @DisplayName("커뮤니티 게시글 검색 조회 성공") - void getBySearch() throws Exception { - //given - Page page = new PageImpl<>(Collections.emptyList()); - - given(communityBoardQueryUseCase.getCommunityBoards(any(), anyInt())) - .willReturn(page); - //when - //then - mockMvc.perform(get("/api/community-boards/search") - .param("keyword", "봉사") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message") - .value("커뮤니티 게시글 검색 리스트 조회 성공")); - - verify(communityBoardQueryUseCase, times(1)) - .getCommunityBoards(any(), anyInt()); - } - -// @Test -// @DisplayName("커뮤니티 게시글 검색 조회 성공") -// void getBySearch() throws Exception { -// // given -// Page page = new PageImpl<>(Collections.emptyList()); -// -// given(communityBoardDocumentUseCase.getCommunityBoardBySearch(any(), anyInt())) -// .willReturn(page); -// -// // when -// // then -// mockMvc.perform(get("/api/community-boards/search") -// .param("keyword", "봉사") -// .accept(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.code").value(200)) -// .andExpect(jsonPath("$.data").exists()) -// .andExpect(jsonPath("$.message").value("커뮤니티 게시글 검색 리스트 조회 성공")); -// -// verify(communityBoardDocumentUseCase, times(1)).getCommunityBoardBySearch( -// any(), anyInt()); -// } } diff --git a/src/test/java/com/somemore/domains/community/repository/CommunityBoardDocumentRepositoryTest.java b/src/test/java/com/somemore/domains/community/repository/CommunityBoardDocumentRepositoryTest.java deleted file mode 100644 index 58d7c224a..000000000 --- a/src/test/java/com/somemore/domains/community/repository/CommunityBoardDocumentRepositoryTest.java +++ /dev/null @@ -1,79 +0,0 @@ -//package com.somemore.community.repository; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.auth.oauth.OAuthProvider; -//import com.somemore.community.domain.CommunityBoard; -//import com.somemore.community.repository.board.CommunityBoardRepository; -//import com.somemore.community.repository.mapper.CommunityBoardView; -//import com.somemore.volunteer.domain.Volunteer; -//import com.somemore.volunteer.repository.VolunteerRepository; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.transaction.annotation.Transactional; -// -// -//import static com.somemore.common.fixture.CommunityBoardFixture.createCommunityBoard; -//import static org.assertj.core.api.Assertions.assertThat; -// -//@Transactional -//class CommunityBoardDocumentRepositoryTest extends IntegrationTestSupport { -// -// @Autowired -// private CommunityBoardRepository communityBoardRepository; -// @Autowired -// private VolunteerRepository volunteerRepository; -// -// @BeforeEach -// void setUp() { -// String oAuthId = "example-oauth-id2"; -// Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId); -// volunteerRepository.save(volunteer); -// -// for (int i = 1; i <= 20; i++) { -// String title = "제목" + i; -// CommunityBoard communityBoard = createCommunityBoard(title, volunteer.getId()); -// communityBoardRepository.save(communityBoard); -// } -// } -// -// @DisplayName("검색 키워드가 포함된 게시글을 조회할 수 있다. (repository)") -// @Test -// void findByCommunityBoardsContaining() { -// //given -// Pageable pageable = getPageable(); -// -// //when -// Page findBoards = communityBoardRepository.findByCommunityBoardsContaining("봉사", pageable); -// -// //then -// assertThat(findBoards).isNotNull(); -//// assertThat(findBoards.getTotalElements()).isEqualTo(10); -//// assertThat(findBoards.getSize()).isEqualTo(10); -//// assertThat(findBoards.getTotalPages()).isEqualTo(1); -// } -// -// @DisplayName("키워드 없이 검색시 전체 게시글을 조회할 수 있다. (repository)") -// @Test -// void findByCommunityBoardsContainingWithNull() { -// //given -// Pageable pageable = getPageable(); -// -// //when -// Page findBoards = communityBoardRepository.findByCommunityBoardsContaining(null, pageable); -// -// //then -// assertThat(findBoards).isNotNull(); -//// assertThat(findBoards.getTotalElements()).isEqualTo(16); -//// assertThat(findBoards.getSize()).isEqualTo(10); -//// assertThat(findBoards.getTotalPages()).isEqualTo(2); -// } -// -// private Pageable getPageable() { -// return PageRequest.of(0, 10); -// } -//} diff --git a/src/test/java/com/somemore/domains/community/repository/CommunityBoardRepositoryTest.java b/src/test/java/com/somemore/domains/community/repository/CommunityBoardRepositoryTest.java index ca635bcb0..f5264f5de 100644 --- a/src/test/java/com/somemore/domains/community/repository/CommunityBoardRepositoryTest.java +++ b/src/test/java/com/somemore/domains/community/repository/CommunityBoardRepositoryTest.java @@ -137,9 +137,9 @@ void existsById() { assertThat(isExist).isTrue(); } - @DisplayName("검색 키워드가 포함된 커뮤니티 게시글을 페이지로 조회할 수 있다. (Repository)") + @DisplayName("검색 키워드가 포함된 게시글을 페이지로 조회할 수 있다. (rdb)") @Test - void getCommunityBoardsBySearch() { + void findByCommunityBoardsContaining() { //given Pageable pageable = getPageable(); @@ -158,34 +158,6 @@ void getCommunityBoardsBySearch() { assertThat(communityBoards.getNumber()).isZero(); } -// @DisplayName("게시글을 elastic search index에 저장할 수 있다. (repository)") -// @Test -// void saveDocuments() { -// //given -// Pageable pageable = getPageable(); -// -// List communityBoards = new ArrayList<>(); -// -// CommunityBoard communityBoard1 = createCommunityBoard("저장 잘 되나요?", writerId); -// CommunityBoard savedBoard1 = communityBoardRepository.save(communityBoard1); -// CommunityBoard communityBoard2 = createCommunityBoard("잘 되나요?", "저장이요", writerId); -// CommunityBoard savedBoard2 = communityBoardRepository.save(communityBoard2); -// communityBoards.add(savedBoard1); -// communityBoards.add(savedBoard2); -// -// //when -// communityBoardRepository.saveDocuments(communityBoards); -// -// //then -// Page findBoard = communityBoardRepository.findByCommunityBoardsContaining("저장", pageable); -// -// assertThat(findBoard).isNotNull(); -// assertThat(findBoard.getTotalElements()).isEqualTo(2); -// -// communityBoardRepository.deleteDocument(savedBoard1.getId()); -// communityBoardRepository.deleteDocument(savedBoard2.getId()); -// } - private Pageable getPageable() { return PageRequest.of(0, 10); } diff --git a/src/test/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateSchedulerTest.java deleted file mode 100644 index bf84abd3c..000000000 --- a/src/test/java/com/somemore/domains/community/scheduler/CommunityBoardUpdateSchedulerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -//package com.somemore.community.scheduler; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.community.repository.board.CommunityBoardRepository; -//import com.somemore.community.repository.mapper.CommunityBoardView; -//import org.awaitility.Awaitility; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.concurrent.TimeUnit; -// -//import static org.assertj.core.api.Assertions.assertThat; -// -//@Transactional -//class CommunityBoardUpdateSchedulerTest extends IntegrationTestSupport { -// -// @Autowired -// private CommunityBoardRepository communityBoardRepository; -// -// @DisplayName("매일 자정 elastic search index에 전체 mysql 데이터를 저장한다.") -// @Test -// void updateCommunityBoardDocuments() { -// // given -// Pageable pageable = getPageable(); -// -// // when -// // then -// Page communityBoards = communityBoardRepository -// .findByCommunityBoardsContaining("", pageable); -// -// Awaitility.await() -// .atMost(3, TimeUnit.SECONDS) -// .untilAsserted(() -> { -// Page updatedBoards = communityBoardRepository -// .findCommunityBoards(pageable); -// assertThat(updatedBoards.getContent()) -// .usingRecursiveComparison() -// .ignoringFields("updatedAt") -// .isEqualTo(communityBoards.getContent()); -// assertThat(updatedBoards.getTotalElements()).isEqualTo(communityBoards.getTotalElements()); -// assertThat(updatedBoards.getSize()).isEqualTo(communityBoards.getSize()); -// }); -// } -// -// private Pageable getPageable() { -// return PageRequest.of(0, 10); -// } -//} diff --git a/src/test/java/com/somemore/domains/community/service/board/CommunityBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/community/service/board/CommunityBoardDocumentServiceTest.java deleted file mode 100644 index c547dd864..000000000 --- a/src/test/java/com/somemore/domains/community/service/board/CommunityBoardDocumentServiceTest.java +++ /dev/null @@ -1,83 +0,0 @@ -//package com.somemore.community.service.board; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.auth.oauth.OAuthProvider; -//import com.somemore.community.domain.CommunityBoard; -//import com.somemore.community.dto.response.CommunityBoardResponseDto; -//import com.somemore.community.repository.board.CommunityBoardRepository; -//import com.somemore.volunteer.domain.Volunteer; -//import com.somemore.volunteer.repository.VolunteerRepository; -//import org.junit.jupiter.api.AfterEach; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -// -//import java.util.UUID; -// -//import static com.somemore.common.fixture.CommunityBoardFixture.createCommunityBoard; -//import static org.assertj.core.api.Assertions.assertThat; -// -//class CommunityBoardDocumentServiceTest extends IntegrationTestSupport { -// -//// @Autowired -//// private CommunityBoardDocumentService communityBoardDocumentService; -// -// @Autowired -// private CommunityBoardRepository communityBoardRepository; -// -// @Autowired -// private VolunteerRepository volunteerRepository; -// -// private UUID writerId; -// -// @BeforeEach -// void setUp() { -// String oAuthId1 = "example-oauth-id1"; -// Volunteer volunteer1 = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); -// volunteerRepository.save(volunteer1); -// writerId = volunteer1.getId(); -// -// for (int i = 1; i <= 18; i++) { -// String title = "제목" + i; -// CommunityBoard communityBoard = createCommunityBoard(title, writerId); -// communityBoardRepository.save(communityBoard); -// } -// } -// -// @AfterEach -// void tearDown() { -// communityBoardRepository.deleteAllInBatch(); -// } -// -// @DisplayName("검색 키워드가 포함된 게시글을 조회한다. (service)") -// @Test -// void getCommunityBoardBySearch() { -// //given -// //when -// Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("봉사", 0); -// -// //then -// assertThat(dtos).isNotNull(); -//// assertThat(dtos.getContent()).isNotNull(); -//// assertThat(dtos.getTotalElements()).isEqualTo(10); -//// assertThat(dtos.getSize()).isEqualTo(10); -//// assertThat(dtos.getTotalPages()).isEqualTo(1); -// } -// -// @DisplayName("검색 키워드 없이 조회시 전체 게시글을 조회한다. (service)") -// @Test -// void getCommunityBoardBySearchWithNull() { -// //given -// //when -// Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); -// -// //then -// assertThat(dtos).isNotNull(); -// assertThat(dtos.getContent()).isNotNull(); -//// assertThat(dtos.getTotalElements()).isEqualTo(17); -//// assertThat(dtos.getSize()).isEqualTo(10); -//// assertThat(dtos.getTotalPages()).isEqualTo(2); -// } -//} diff --git a/src/test/java/com/somemore/domains/community/service/board/CommunityBoardQueryServiceTest.java b/src/test/java/com/somemore/domains/community/service/board/CommunityBoardQueryServiceTest.java index 07e1f6363..2aa176d77 100644 --- a/src/test/java/com/somemore/domains/community/service/board/CommunityBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/community/service/board/CommunityBoardQueryServiceTest.java @@ -42,8 +42,6 @@ class CommunityBoardQueryServiceTest extends IntegrationTestSupport { DeleteCommunityBoardUseCase deleteCommunityBoardUseCase; @Autowired CommunityBoardQueryService communityBoardQueryService; -// @Autowired -// private CommunityBoardDocumentService communityBoardDocumentService; private UUID writerId1; private Long communityId1; @@ -148,7 +146,7 @@ void getCommunityBoardDetailWithDeletedId() { .withMessage(ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD.getMessage()); } - @DisplayName("검색 키워드가 포함된 커뮤니티 게시글 리스트를 페이지로 조회한다.") + @DisplayName("검색 키워드가 포함된 커뮤니티 게시글 리스트를 페이지로 조회한다. (rdb)") @Test void getCommunityBoardsBySearch() { @@ -167,30 +165,5 @@ void getCommunityBoardsBySearch() { assertThat(dtos.getSize()).isEqualTo(10); assertThat(dtos.getTotalPages()).isEqualTo(1); } - -// @DisplayName("게시글을 elastic search index에 저장한다. (service)") -// @Test -// void saveCommunityBoardDocuments() { -// //given -// List communityBoards = new ArrayList<>(); -// -// CommunityBoard communityBoard = createCommunityBoard("저장 잘 되나요?", "안녕하세요", UUID.randomUUID()); -// CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); -// communityBoards.add(savedBoard); -// -// //when -// communityBoardDocumentService.saveCommunityBoardDocuments(communityBoards); -// -// //then -// Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); -// -// assertThat(dtos).isNotNull(); -// assertThat(dtos.getContent()).isNotNull(); -//// assertThat(dtos.getTotalElements()).isEqualTo(17); -//// assertThat(dtos.getSize()).isEqualTo(10); -//// assertThat(dtos.getTotalPages()).isEqualTo(2); -// -// communityBoardRepository.deleteDocument(savedBoard.getId()); -// } } diff --git a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiControllerTest.java b/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiControllerTest.java index 2c3f25ee5..b3df5cd0c 100644 --- a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiControllerTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiControllerTest.java @@ -74,55 +74,6 @@ void getAll() throws Exception { any(RecruitBoardSearchCondition.class)); } - @Test - @DisplayName("모집글을 검색 조건으로 페이징 조회할 수 있다.") - void getAllBySearch() throws Exception { - // given - Page page = new PageImpl<>(Collections.emptyList()); - - given(recruitBoardQueryUseCase.getAllWithCenter(any(RecruitBoardSearchCondition.class))) - .willReturn(page); - - // when - // then - mockMvc.perform(get("/api/recruit-boards/search") - .param("keyword", "volunteer") - .param("category", ADMINISTRATIVE_SUPPORT.name()) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message").value("봉사 활동 모집글 검색 조회 성공")); - - verify(recruitBoardQueryUseCase, times(1)).getAllWithCenter( - any(RecruitBoardSearchCondition.class)); - } - - @Test - @DisplayName("위치 기반으로 근처 있는 모집글 페이징 조회할 수 있다.") - void getNearby() throws Exception { - // given - Page page = new PageImpl<>(Collections.emptyList()); - given(recruitBoardQueryUseCase.getRecruitBoardsNearby( - any(RecruitBoardNearByCondition.class) - )).willReturn(page); - - // when - // then - mockMvc.perform(get("/api/recruit-boards/nearby") - .param("latitude", "37.5665") - .param("longitude", "126.9780") - .param("radius", "10") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message").value("근처 봉사 활동 모집글 조회 성공")); - - verify(recruitBoardQueryUseCase, times(1)).getRecruitBoardsNearby( - any(RecruitBoardNearByCondition.class)); - } - @Test @DisplayName("기관 ID로 모집글 페이징 조회할 수 있다.") void getRecruitBoardsByCenterId() throws Exception { diff --git a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiControllerTest.java b/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiControllerTest.java deleted file mode 100644 index 5aa1d0fdf..000000000 --- a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardSearchApiControllerTest.java +++ /dev/null @@ -1,84 +0,0 @@ -//package com.somemore.recruitboard.controller; -// -//import com.somemore.support.ControllerTestSupport; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; -//import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -//import com.somemore.recruitboard.usecase.query.RecruitBoardDocumentUseCase; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageImpl; -//import org.springframework.http.MediaType; -//import org.springframework.test.web.servlet.MockMvc; -// -//import java.util.Collections; -// -//import static com.somemore.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.BDDMockito.given; -//import static org.mockito.Mockito.times; -//import static org.mockito.Mockito.verify; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// -//public class RecruitBoardSearchApiControllerTest extends ControllerTestSupport { -// -// @Autowired -// private MockMvc mockMvc; -// -// @MockBean -// private RecruitBoardDocumentUseCase documentUseCase; -// -// @Test -// @DisplayName("모집글을 검색 조건으로 페이징 조회할 수 있다.") -// void getAllBySearch() throws Exception { -// // given -// Page page = new PageImpl<>(Collections.emptyList()); -// -// given(documentUseCase.getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class))) -// .willReturn(page); -// -// // when -// // then -// mockMvc.perform(get("/api/v1/recruit-boards/search") -// .param("keyword", "volunteer") -// .param("category", ADMINISTRATIVE_SUPPORT.name()) -// .accept(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.code").value(200)) -// .andExpect(jsonPath("$.data").exists()) -// .andExpect(jsonPath("$.message").value("봉사 활동 모집글 검색 조회 성공")); -// -// verify(documentUseCase, times(1)).getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class)); -// } -// -// @Test -// @DisplayName("위치 기반으로 근처 있는 모집글 페이징 조회할 수 있다.") -// void getNearby() throws Exception { -// // given -// Page page = new PageImpl<>(Collections.emptyList()); -// given(documentUseCase.getRecruitBoardsNearbyWithKeyword( -// any(RecruitBoardNearByCondition.class) -// )).willReturn(page); -// -// // when -// // then -// mockMvc.perform(get("/api/v1/recruit-boards/nearby") -// .param("latitude", "37.5665") -// .param("longitude", "126.9780") -// .param("radius", "10") -// .accept(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.code").value(200)) -// .andExpect(jsonPath("$.data").exists()) -// .andExpect(jsonPath("$.message").value("근처 봉사 활동 모집글 조회 성공")); -// -// verify(documentUseCase, times(1)).getRecruitBoardsNearbyWithKeyword( -// any(RecruitBoardNearByCondition.class)); -// } -//} diff --git a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java b/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java deleted file mode 100644 index 9e5f59db2..000000000 --- a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java +++ /dev/null @@ -1,128 +0,0 @@ -//package com.somemore.recruitboard.repository; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.center.domain.Center; -//import com.somemore.center.repository.center.CenterRepository; -//import com.somemore.location.domain.Location; -//import com.somemore.location.repository.LocationRepository; -//import com.somemore.recruitboard.domain.RecruitBoard; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.repository.mapper.RecruitBoardDetail; -//import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.domain.Sort; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.ArrayList; -//import java.util.List; -// -//import static com.somemore.common.fixture.CenterFixture.createCenter; -//import static com.somemore.common.fixture.LocationFixture.createLocation; -//import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; -//import static org.assertj.core.api.Assertions.assertThat; -// -//@Transactional -//class RecruitBoardDocumentRepositoryTest extends IntegrationTestSupport { -// -// @Autowired -// private RecruitBoardRepository recruitBoardRepository; -// -// @Autowired -// private LocationRepository locationRepository; -// -// @Autowired -// private CenterRepository centerRepository; -// -// private final List boards = new ArrayList<>(); -// -// @BeforeEach -// void setUp() { -// Location location = createLocation(); -// locationRepository.save(location); -// -// Center center = createCenter(); -// centerRepository.save(center); -// -// for (int i = 1; i <= 30; i++) { -// String title = "제목" + i; -// RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); -// boards.add(board); -// } -// recruitBoardRepository.saveAll(boards); -// } -// -// @DisplayName("검색 키워드가 포함된 모집글을 조회할 수 있다.") -// @Test -// void findByRecruitBoardsContaining() { -// //given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("노인") -// .pageable(pageable) -// .build(); -// -// //when -// Page findBoards = recruitBoardRepository.findByRecruitBoardsContaining(condition); -// -// //then -// assertThat(findBoards).isNotNull(); -//// assertThat(findBoards.getTotalElements()).isEqualTo(4); -//// assertThat(findBoards.getSize()).isEqualTo(5); -//// assertThat(findBoards.getTotalPages()).isEqualTo(1); -// } -// -// @DisplayName("키워드 없이 검색시 전체 모집글을 조회할 수 있다.") -// @Test -// void findByRecruitBoardsContainingWithNull() { -// //given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("") -// .pageable(pageable) -// .build(); -// -// //when -// Page findBoards = recruitBoardRepository.findByRecruitBoardsContaining(condition); -// -// //then -// assertThat(findBoards).isNotNull(); -//// assertThat(findBoards.getTotalElements()).isEqualTo(23); -//// assertThat(findBoards.getSize()).isEqualTo(5); -//// assertThat(findBoards.getTotalPages()).isEqualTo(5); -// } -// -// @DisplayName("위치 기반으로 반경 내에 모집글을 반환한다") -// @Test -// void findAllNearByLocationWithKeyword() { -// // given -// Pageable pageable = getPageable(); -// -// RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() -// .keyword(null) -// .latitude(37.5935) -// .longitude(126.9780) -// .radius(5.0) -// .pageable(pageable) -// .build(); -// -// // when -// Page result = recruitBoardRepository.findAllNearbyWithKeyword(condition); -// -// // then -// assertThat(result).isNotNull(); -//// assertThat(result.getTotalElements()).isEqualTo(23); -//// assertThat(result.getContent()).isNotEmpty(); -// } -// -// private Pageable getPageable() { -// Sort sort = Sort.by(Sort.Order.desc("created_at")); -// return PageRequest.of(0, 5, sort); -// } -//} diff --git a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java b/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java index f34a9577e..312648a31 100644 --- a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java @@ -451,38 +451,6 @@ void updateStatusToCompletedForDateRange() { assertThat(two.getRecruitStatus()).isEqualTo(COMPLETED); } -// @DisplayName("모집글을 elastic search index에 저장할 수 있다. (repository)") -// @Test -// void saveDocuments() { -// //given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("저장") -// .pageable(pageable) -// .build(); -// -// List recruitBoards = new ArrayList<>(); -// -// RecruitBoard board1 = createRecruitBoard("저장 잘 되나요?", centerId); -// RecruitBoard savedBoard1 = recruitBoardRepository.save(board1); -// RecruitBoard board2 = createRecruitBoard("저장해줘", centerId); -// RecruitBoard savedBoard2 = recruitBoardRepository.save(board2); -// recruitBoards.add(savedBoard1); -// recruitBoards.add(savedBoard2); -// -// //when -// recruitBoardRepository.saveDocuments(recruitBoards); -// -// //then -// Page findBoard = recruitBoardRepository.findByRecruitBoardsContaining(condition); -// -// assertThat(findBoard).isNotNull(); -// assertThat(findBoard.getTotalElements()).isEqualTo(2); -// -// recruitBoardRepository.deleteDocument(savedBoard1.getId()); -// recruitBoardRepository.deleteDocument(savedBoard2.getId()); -// } - private static UserCommonAttribute createUserCommonAttribute(UUID userId) { return UserCommonAttribute.createDefault(userId, UserRole.CENTER); } @@ -565,5 +533,4 @@ private Pageable getPageable() { Sort sort = Sort.by(Sort.Order.desc("created_at")); return PageRequest.of(0, 5, sort); } - } diff --git a/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateSchedulerTest.java deleted file mode 100644 index 69510db40..000000000 --- a/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardUpdateSchedulerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -//package com.somemore.recruitboard.scheduler; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.repository.RecruitBoardRepository; -//import com.somemore.recruitboard.repository.mapper.RecruitBoardWithCenter; -//import org.awaitility.Awaitility; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.domain.Sort; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.concurrent.TimeUnit; -// -//import static org.assertj.core.api.Assertions.assertThat; -// -//@Transactional -//class RecruitBoardUpdateSchedulerTest extends IntegrationTestSupport { -// -// @Autowired -// private RecruitBoardRepository recruitBoardRepository; -// -// @DisplayName("매일 자정 elastic search index에 전체 mysql 데이터를 저장한다.") -// @Test -// void updateRecruitBoardDocuments() { -// // given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("") -// .pageable(pageable) -// .build(); -// -// // when -// // then -// Page recruitBoards = recruitBoardRepository -// .findByRecruitBoardsContaining(condition); -// -// Awaitility.await() -// .atMost(3, TimeUnit.SECONDS) -// .untilAsserted(() -> { -// Page updatedBoards = recruitBoardRepository -// .findAllWithCenter(condition); -// assertThat(updatedBoards.getContent()) -// .usingRecursiveComparison() -// .ignoringFields("updatedAt") -// .isEqualTo(recruitBoards.getContent()); -// assertThat(updatedBoards.getTotalElements()).isEqualTo(recruitBoards.getTotalElements()); -// assertThat(updatedBoards.getSize()).isEqualTo(recruitBoards.getSize()); -// }); -// } -// -// private Pageable getPageable() { -// Sort sort = Sort.by(Sort.Order.desc("created_at")); -// return PageRequest.of(0, 5, sort); -// } -//} diff --git a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentServiceTest.java index 04b498bd7..e69de29bb 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentServiceTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardDocumentServiceTest.java @@ -1,157 +0,0 @@ -//package com.somemore.recruitboard.service.query; -// -//import com.somemore.support.IntegrationTestSupport; -//import com.somemore.center.domain.Center; -//import com.somemore.center.repository.center.CenterRepository; -//import com.somemore.location.domain.Location; -//import com.somemore.location.repository.LocationRepository; -//import com.somemore.recruitboard.domain.RecruitBoard; -//import com.somemore.recruitboard.dto.condition.RecruitBoardNearByCondition; -//import com.somemore.recruitboard.dto.condition.RecruitBoardSearchCondition; -//import com.somemore.recruitboard.dto.response.RecruitBoardDetailResponseDto; -//import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -//import com.somemore.recruitboard.repository.RecruitBoardRepository; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.data.domain.Page; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.data.domain.Pageable; -//import org.springframework.data.domain.Sort; -//import org.springframework.transaction.annotation.Transactional; -// -//import java.util.ArrayList; -//import java.util.List; -// -//import static com.somemore.common.fixture.CenterFixture.createCenter; -//import static com.somemore.common.fixture.LocationFixture.createLocation; -//import static com.somemore.common.fixture.RecruitBoardFixture.createRecruitBoard; -//import static org.assertj.core.api.Assertions.assertThat; -// -//@Transactional -//class RecruitBoardDocumentServiceTest extends IntegrationTestSupport { -// -// @Autowired -// private RecruitBoardDocumentService recruitBoardDocumentService; -// -// @Autowired -// private RecruitBoardRepository recruitBoardRepository; -// -// @Autowired -// private LocationRepository locationRepository; -// -// @Autowired -// private CenterRepository centerRepository; -// -// private final List boards = new ArrayList<>(); -// -// @BeforeEach -// public void setUp() { -// Location location = createLocation(); -// locationRepository.save(location); -// -// Center center = createCenter(); -// centerRepository.save(center); -// -// for (int i = 1; i <= 30; i++) { -// String title = "제목" + i; -// RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); -// boards.add(board); -// } -// recruitBoardRepository.saveAll(boards); -// } -// -// @DisplayName("검색 키워드가 포함된 모집글을 조회한다. (service)") -// @Test -// void getRecruitBoardBySearch() { -// //given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("노인") -// .pageable(pageable) -// .build(); -// -// //when -// Page dtos = recruitBoardDocumentService.getRecruitBoardBySearch(condition); -// -// //then -// assertThat(dtos).isNotNull(); -//// assertThat(dtos.getContent()).isNotNull(); -//// assertThat(dtos.getTotalElements()).isEqualTo(4); -//// assertThat(dtos.getSize()).isEqualTo(5); -//// assertThat(dtos.getTotalPages()).isEqualTo(1); -// } -// -// @DisplayName("키워드 없이 검색시 전체 모집글을 조회한다. (service)") -// @Test -// void getRecruitBoardBySearchWithNull() { -// //given -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("") -// .pageable(pageable) -// .build(); -// -// //when -// Page dtos = recruitBoardDocumentService.getRecruitBoardBySearch(condition); -// -// //then -// assertThat(dtos).isNotNull(); -//// assertThat(dtos.getContent()).isNotNull(); -//// assertThat(dtos.getTotalElements()).isEqualTo(30); -//// assertThat(dtos.getSize()).isEqualTo(5); -//// assertThat(dtos.getTotalPages()).isEqualTo(6); -// } -// -// @DisplayName("위치 기반으로 주변 모집글을 페이징하여 조회할 수 있다. (service)") -// @Test -// void getRecruitBoardsNearBy() { -// // given -// Pageable pageable = getPageable(); -// RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() -// .keyword(null) -// .latitude(37.5935) -// .longitude(126.9780) -// .radius(5.0) -// .pageable(pageable) -// .build(); -// -// // when -// Page result = recruitBoardDocumentService.getRecruitBoardsNearbyWithKeyword( -// condition); -// -// // then -// assertThat(result).isNotNull(); -//// assertThat(result.getTotalElements()).isEqualTo(23); -//// assertThat(result.getContent()).isNotEmpty(); -// } -// -// @DisplayName("위치 기반으로 반경 내에 모집글을 키워드로 검색하여 반환한다. (service)") -// @Test -// void findAllNearByLocationWithKeyword() { -// // given -// Pageable pageable = getPageable(); -// -// RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() -// .keyword("도서관") -// .latitude(37.5935) -// .longitude(126.9780) -// .radius(5.0) -// .pageable(pageable) -// .build(); -// -// // when -// Page result = recruitBoardDocumentService.getRecruitBoardsNearbyWithKeyword( -// condition); -// -// // then -// assertThat(result).isNotNull(); -//// assertThat(result.getTotalElements()).isEqualTo(1); -// } -// -// private Pageable getPageable() { -// Sort sort = Sort.by(Sort.Order.desc("created_at")); -// return PageRequest.of(0, 5, sort); -// } -//} diff --git a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java index 9d9e3d7ac..5d1931c93 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java @@ -48,9 +48,6 @@ class RecruitBoardQueryServiceTest extends IntegrationTestSupport { @Autowired private RecruitBoardQueryService recruitBoardQueryService; -// @Autowired -// private RecruitBoardDocumentService recruitBoardDocumentService; - @Autowired private RecruitBoardRepository recruitBoardRepository; @@ -251,41 +248,6 @@ void getAllByIds() { assertThat(all).hasSize(3); } -// @DisplayName("모집글을 elastic search index에 저장한다. (service)") -// @Test -// void saveRecruitBoardDocuments() { -// //given -// Center center = createCenter("특별한 기관"); -// centerRepository.save(center); -// -// Pageable pageable = getPageable(); -// RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() -// .keyword("저장") -// .pageable(pageable) -// .build(); -// -// List recruitBoards = new ArrayList<>(); -// -// RecruitBoard board1 = createRecruitBoard("저장 잘 되나요?", center.getId()); -// RecruitBoard savedBoard1 = recruitBoardRepository.save(board1); -// RecruitBoard board2 = createRecruitBoard("저장해줘", center.getId()); -// RecruitBoard savedBoard2 = recruitBoardRepository.save(board2); -// recruitBoards.add(savedBoard1); -// recruitBoards.add(savedBoard2); -// -// //when -// recruitBoardDocumentService.saveRecruitBoardDocuments(recruitBoards); -// -// //then -// Page findBoard = recruitBoardDocumentService.getRecruitBoardBySearch(condition); -// -// assertThat(findBoard).isNotNull(); -// assertThat(findBoard.getTotalElements()).isEqualTo(2); -// -// recruitBoardRepository.deleteDocument(savedBoard1.getId()); -// recruitBoardRepository.deleteDocument(savedBoard2.getId()); -// } - private static UserCommonAttribute createUserCommonAttribute(UUID userId) { return UserCommonAttribute.createDefault(userId, UserRole.CENTER); } diff --git a/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java b/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java new file mode 100644 index 000000000..b38db0022 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java @@ -0,0 +1,56 @@ +package com.somemore.domains.search.controller; + +import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; +import com.somemore.support.ControllerTestSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class CommunityBoardSearchApiControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + + @Test + @DisplayName("게시글을 검색 조건으로 페이징 조회 성공") + void getBySearch() throws Exception { + // given + Page page = new PageImpl<>(Collections.emptyList()); + + given(communityBoardDocumentUseCase.getCommunityBoardBySearch(any(), anyInt())) + .willReturn(page); + + // when + // then + mockMvc.perform(get("/api/community-boards/search") + .param("keyword", "봉사") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("커뮤니티 게시글 검색 리스트 조회 성공")); + + verify(communityBoardDocumentUseCase, times(1)).getCommunityBoardBySearch( + any(), anyInt()); + } +} diff --git a/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java b/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java new file mode 100644 index 000000000..140e12fa2 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java @@ -0,0 +1,84 @@ +package com.somemore.domains.search.controller; + +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; +import com.somemore.support.ControllerTestSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Collections; + +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class RecruitBoardSearchApiControllerTest extends ControllerTestSupport { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; + + @Test + @DisplayName("모집글을 검색 조건으로 페이징 조회 성공") + void getAllBySearch() throws Exception { + // given + Page page = new PageImpl<>(Collections.emptyList()); + + given(recruitBoardDocumentUseCase.getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class))) + .willReturn(page); + + // when + // then + mockMvc.perform(get("/api/recruit-boards/search") + .param("keyword", "volunteer") + .param("category", ADMINISTRATIVE_SUPPORT.name()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("봉사 활동 모집글 검색 조회 성공")); + + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class)); + } + + @Test + @DisplayName("위치 기반으로 근처 있는 모집글 페이징 조회할 수 있다.") + void getNearby() throws Exception { + // given + Page page = new PageImpl<>(Collections.emptyList()); + given(recruitBoardDocumentUseCase.getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class) + )).willReturn(page); + + // when + // then + mockMvc.perform(get("/api/recruit-boards/nearby") + .param("latitude", "37.5665") + .param("longitude", "126.9780") + .param("radius", "10") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("근처 봉사 활동 모집글 조회 성공")); + + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class)); + } +} diff --git a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java new file mode 100644 index 000000000..48e91cfbe --- /dev/null +++ b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java @@ -0,0 +1,277 @@ +package com.somemore.domains.search.repository; + +import com.somemore.domains.center.domain.Center; +import com.somemore.domains.center.repository.center.CenterRepository; +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.repository.board.CommunityBoardRepository; +import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.location.domain.Location; +import com.somemore.domains.location.repository.LocationRepository; +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.volunteer.domain.Volunteer; +import com.somemore.domains.volunteer.repository.VolunteerRepository; +import com.somemore.global.auth.oauth.OAuthProvider; +import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +import static com.somemore.support.fixture.CenterFixture.createCenter; +import static com.somemore.support.fixture.LocationFixture.createLocation; +import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static com.somemore.support.fixture.CommunityBoardFixture.createCommunityBoard; + +@Transactional +@EnabledIf(value = "${elastic.search.enabled}", loadContext = true) +class SearchBoardRepositoryTest extends IntegrationTestSupport { + + @Autowired + private CommunityBoardRepository communityBoardRepository; + @Autowired + private RecruitBoardRepository recruitBoardRepository; + @Autowired + private LocationRepository locationRepository; + @Autowired + private CenterRepository centerRepository; + @Autowired + private SearchBoardRepository searchBoardRepository; + @Autowired + private VolunteerRepository volunteerRepository; + + private UUID writerId, centerId; + private final List boards = new ArrayList<>(); + + @BeforeEach + void setUp() { + String oAuthId1 = "example-oauth-id1"; + Volunteer volunteer1 = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); + volunteerRepository.save(volunteer1); + writerId = volunteer1.getId(); + + for (int i = 1; i <= 20; i++) { + String title = "제목" + i; + CommunityBoard communityBoard = createCommunityBoard(title, writerId); + communityBoardRepository.save(communityBoard); + } + + Location location = createLocation(); + locationRepository.save(location); + + Center center = createCenter(); + centerRepository.save(center); + centerId = center.getId(); + + for (int i = 1; i <= 20; i++) { + String title = "제목" + i; + RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); + boards.add(board); + } + recruitBoardRepository.saveAll(boards); + } + + @DisplayName("커뮤니티 게시글을 elastic search index에 저장할 수 있다. (repository)") + @Test + void saveCommunityBoardDocuments() { + //given + Pageable pageable = getPageable(10); + + List communityBoards = new ArrayList<>(); + + CommunityBoard communityBoard1 = createCommunityBoard("저장 잘 되나요?", writerId); + CommunityBoard savedBoard1 = communityBoardRepository.save(communityBoard1); + CommunityBoard communityBoard2 = createCommunityBoard("잘 되나요?", "저장이요", writerId); + CommunityBoard savedBoard2 = communityBoardRepository.save(communityBoard2); + communityBoards.add(savedBoard1); + communityBoards.add(savedBoard2); + + //when + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); + + //then + Page findBoard = searchBoardRepository.findByCommunityBoardsContaining("저장", pageable); + + assertThat(findBoard).isNotNull(); + assertThat(findBoard.getTotalElements()).isEqualTo(2); + + searchBoardRepository.deleteCommunityBoardDocument(savedBoard1.getId()); + searchBoardRepository.deleteCommunityBoardDocument(savedBoard2.getId()); + } + + + @DisplayName("검색 키워드가 포함된 게시글을 페이지로 조회할 수 있다. (elasticsearch)") + @Test + void findByCommunityBoardsContaining() { + + //given + Pageable pageable = getPageable(10); + + String title = "봉사"; + CommunityBoard communityBoard = createCommunityBoard(title, writerId); + communityBoardRepository.save(communityBoard); + searchBoardRepository.saveCommunityBoardDocuments(List.of(communityBoard)); + + //when + Page communityBoards = searchBoardRepository.findByCommunityBoardsContaining("봉사", pageable); + + //then + assertThat(communityBoards).isNotNull(); + assertThat(communityBoards.getTotalElements()).isEqualTo(1); + assertThat(communityBoards.getSize()).isEqualTo(10); + assertThat(communityBoards.getNumber()).isZero(); + } + + @DisplayName("키워드 없이 검색 시 전체 게시글을 페이지로 조회할 수 있다. (elasticsearch)") + @Test + void findByCommunityBoardsContainingWithNull() { + + //given + Pageable pageable = getPageable(10); + + //when + Page communityBoards = searchBoardRepository.findByCommunityBoardsContaining(null, pageable); + + //then + assertThat(communityBoards).isNotNull(); + assertThat(communityBoards.getTotalElements()).isEqualTo(20); + assertThat(communityBoards.getSize()).isEqualTo(10); + } + + @DisplayName("봉사 활동 모집글을 elastic search index에 저장할 수 있다. (repository)") + @Test + void saveRecruitBoardDocuments() { + //given + Pageable pageable = getPageable(5); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("저장") + .pageable(pageable) + .build(); + + List recruitBoards = new ArrayList<>(); + + RecruitBoard board1 = createRecruitBoard("저장 잘 되나요?", centerId); + RecruitBoard savedBoard1 = recruitBoardRepository.save(board1); + RecruitBoard board2 = createRecruitBoard("저장해줘", centerId); + RecruitBoard savedBoard2 = recruitBoardRepository.save(board2); + recruitBoards.add(savedBoard1); + recruitBoards.add(savedBoard2); + + //when + searchBoardRepository.saveRecruitBoardDocuments(recruitBoards); + + //then + Page findBoard = searchBoardRepository.findByRecruitBoardsContaining(condition); + + assertThat(findBoard).isNotNull(); + assertThat(findBoard.getTotalElements()).isEqualTo(2); + + searchBoardRepository.deleteRecruitBoardDocument(savedBoard1.getId()); + searchBoardRepository.deleteRecruitBoardDocument(savedBoard2.getId()); + } + + @DisplayName("검색 키워드가 포함된 모집글을 조회할 수 있다. (elasticsearch)") + @Test + void findByRecruitBoardsContaining() { + //given + Pageable pageable = getPageable(5); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("노인") + .pageable(pageable) + .build(); + + //when + Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); + + //then + assertThat(findBoards).isNotNull(); + assertThat(findBoards.getTotalElements()).isEqualTo(4); + assertThat(findBoards.getSize()).isEqualTo(5); + assertThat(findBoards.getTotalPages()).isEqualTo(1); + } + + @DisplayName("키워드 없이 검색 시 전체 모집글을 조회할 수 있다. (elasticsearch)") + @Test + void findByRecruitBoardsContainingWithNull() { + //given + Pageable pageable = getPageable(5); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword(null) + .pageable(pageable) + .build(); + + //when + Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); + + //then + assertThat(findBoards).isNotNull(); + assertThat(findBoards.getTotalElements()).isEqualTo(20); + assertThat(findBoards.getSize()).isEqualTo(5); + assertThat(findBoards.getTotalPages()).isEqualTo(4); + } + + @DisplayName("위치 기반으로 반경 내에 검색 키워드가 포함된 모집글을 조회할 수 있다. (elasticsearch)") + @Test + void findAllNearByLocationContaining() { + // given + Pageable pageable = getPageable(5); + + RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() + .keyword("봉사") + .latitude(37.5935) + .longitude(126.9780) + .radius(5.0) + .pageable(pageable) + .build(); + + // when + Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(17); + assertThat(result.getContent()).isNotEmpty(); + } + + @DisplayName("키워드 없이 검색 시 위치 기반으로 반경 내에 모집글을 모두 조회할 수 있다. (elasticsearch)") + @Test + void findAllNearByLocationContainingWithNull() { + // given + Pageable pageable = getPageable(5); + + RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() + .keyword(null) + .latitude(37.5935) + .longitude(126.9780) + .radius(5.0) + .pageable(pageable) + .build(); + + // when + Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(20); + assertThat(result.getContent()).isNotEmpty(); + } + + private Pageable getPageable(int size) { + return PageRequest.of(0, size); + } +} diff --git a/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java new file mode 100644 index 000000000..a057c54f0 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java @@ -0,0 +1,59 @@ +package com.somemore.domains.search.scheduler; + +import com.somemore.domains.community.repository.board.CommunityBoardRepository; +import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.support.IntegrationTestSupport; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@EnabledIf(value = "${elastic.search.enabled}", loadContext = true) +class CommunityBoardUpdateSchedulerTest extends IntegrationTestSupport { + + @Autowired + private SearchBoardRepository searchBoardRepository; + + @Autowired + private CommunityBoardRepository communityBoardRepository; + + @DisplayName("매일 자정 elastic search index에 전체 mysql 데이터를 저장한다.") + @Test + void updateCommunityBoardDocuments() { + // given + Pageable pageable = getPageable(); + + // when + // then + Page communityBoards = searchBoardRepository + .findByCommunityBoardsContaining(null, pageable); + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAsserted(() -> { + Page updatedBoards = communityBoardRepository + .findCommunityBoards(null, pageable); + assertThat(updatedBoards.getContent()) + .usingRecursiveComparison() + .ignoringFields("updatedAt") + .isEqualTo(communityBoards.getContent()); + assertThat(updatedBoards.getTotalElements()).isEqualTo(communityBoards.getTotalElements()); + assertThat(updatedBoards.getSize()).isEqualTo(communityBoards.getSize()); + }); + } + + private Pageable getPageable() { + return PageRequest.of(0, 10); + } +} diff --git a/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java new file mode 100644 index 000000000..8d3cf9cd3 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java @@ -0,0 +1,66 @@ +package com.somemore.domains.search.scheduler; + +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; +import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.support.IntegrationTestSupport; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@EnabledIf(value = "${elastic.search.enabled}", loadContext = true) +class RecruitBoardUpdateSchedulerTest extends IntegrationTestSupport { + + @Autowired + private SearchBoardRepository searchBoardRepository; + + @Autowired + private RecruitBoardRepository recruitBoardRepository; + + @DisplayName("매일 자정 elastic search index에 전체 mysql 데이터를 저장한다.") + @Test + void updateRecruitBoardDocuments() { + // given + Pageable pageable = getPageable(); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("") + .pageable(pageable) + .build(); + + // when + // then + Page recruitBoards = searchBoardRepository + .findByRecruitBoardsContaining(condition); + + Awaitility.await() + .atMost(3, TimeUnit.SECONDS) + .untilAsserted(() -> { + Page updatedBoards = recruitBoardRepository + .findAllWithCenter(condition); + assertThat(updatedBoards.getContent()) + .usingRecursiveComparison() + .ignoringFields("updatedAt") + .isEqualTo(recruitBoards.getContent()); + assertThat(updatedBoards.getTotalElements()).isEqualTo(recruitBoards.getTotalElements()); + assertThat(updatedBoards.getSize()).isEqualTo(recruitBoards.getSize()); + }); + } + + private Pageable getPageable() { + Sort sort = Sort.by(Sort.Order.desc("created_at")); + return PageRequest.of(0, 5, sort); + } +} \ No newline at end of file diff --git a/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java new file mode 100644 index 000000000..dbe7fdbd5 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java @@ -0,0 +1,113 @@ +package com.somemore.domains.search.service; + +import com.somemore.domains.community.domain.CommunityBoard; +import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.community.repository.board.CommunityBoardRepository; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.domains.volunteer.domain.Volunteer; +import com.somemore.domains.volunteer.repository.VolunteerRepository; +import com.somemore.global.auth.oauth.OAuthProvider; +import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static com.somemore.support.fixture.CommunityBoardFixture.createCommunityBoard; + +@Transactional +@EnabledIf(value = "${elastic.search.enabled}", loadContext = true) +class CommunityBoardDocumentServiceTest extends IntegrationTestSupport { + + @Autowired + private CommunityBoardDocumentService communityBoardDocumentService; + + @Autowired + private CommunityBoardRepository communityBoardRepository; + + @Autowired + private SearchBoardRepository searchBoardRepository; + + @Autowired + private VolunteerRepository volunteerRepository; + + private UUID writerId; + + @BeforeEach + void setUp() { + String oAuthId1 = "example-oauth-id1"; + Volunteer volunteer1 = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); + volunteerRepository.save(volunteer1); + writerId = volunteer1.getId(); + + for (int i = 1; i <= 18; i++) { + String title = "제목" + i; + CommunityBoard communityBoard = createCommunityBoard(title, writerId); + communityBoardRepository.save(communityBoard); + } + } + + @DisplayName("검색 키워드가 포함된 게시글을 조회한다. (ealsticsearch)") + @Test + void getCommunityBoardBySearch() { + //given + //when + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("봉사", 0); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(10); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(1); + } + + @DisplayName("검색 키워드 없이 조회시 전체 게시글을 조회한다. (elasticsearch)") + @Test + void getCommunityBoardBySearchWithNull() { + //given + //when + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(18); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(2); + } + + @DisplayName("게시글을 elastic search index에 저장한다. (service)") + @Test + void saveCommunityBoardDocuments() { + //given + List communityBoards = new ArrayList<>(); + + CommunityBoard communityBoard = createCommunityBoard("저장 잘 되나요?", "안녕하세요", UUID.randomUUID()); + CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); + communityBoards.add(savedBoard); + + //when + communityBoardDocumentService.saveCommunityBoardDocuments(communityBoards); + + //then + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); + + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(18); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(2); + + searchBoardRepository.deleteCommunityBoardDocument(savedBoard.getId()); + } +} diff --git a/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java new file mode 100644 index 000000000..60068c21f --- /dev/null +++ b/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java @@ -0,0 +1,198 @@ +package com.somemore.domains.search.service; + +import com.somemore.domains.center.domain.Center; +import com.somemore.domains.center.repository.center.CenterRepository; +import com.somemore.domains.location.domain.Location; +import com.somemore.domains.location.repository.LocationRepository; +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; +import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; +import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; +import com.somemore.domains.search.repository.SearchBoardRepository; +import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.test.context.junit.jupiter.EnabledIf; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static com.somemore.support.fixture.CenterFixture.createCenter; +import static com.somemore.support.fixture.LocationFixture.createLocation; +import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; +import static org.assertj.core.api.Assertions.assertThat; + +@Transactional +@EnabledIf(value = "${elastic.search.enabled}", loadContext = true) +class RecruitBoardDocumentServiceTest extends IntegrationTestSupport { + + @Autowired + private RecruitBoardDocumentService recruitBoardDocumentService; + + @Autowired + private SearchBoardRepository searchBoardRepository; + + @Autowired + private RecruitBoardRepository recruitBoardRepository; + + @Autowired + private LocationRepository locationRepository; + + @Autowired + private CenterRepository centerRepository; + + private final List boards = new ArrayList<>(); + + @BeforeEach + public void setUp() { + Location location = createLocation(); + locationRepository.save(location); + + Center center = createCenter(); + centerRepository.save(center); + + for (int i = 1; i <= 30; i++) { + String title = "제목" + i; + RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); + boards.add(board); + } + recruitBoardRepository.saveAll(boards); + } + + @DisplayName("검색 키워드가 포함된 모집글을 조회한다. (elasticsearch)") + @Test + void getRecruitBoardBySearch() { + //given + Pageable pageable = getPageable(); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("노인") + .pageable(pageable) + .build(); + + //when + Page dtos = recruitBoardDocumentService.getRecruitBoardBySearch(condition); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(4); + assertThat(dtos.getSize()).isEqualTo(5); + assertThat(dtos.getTotalPages()).isEqualTo(1); + } + + @DisplayName("키워드 없이 검색시 전체 모집글을 조회한다. (elasticsearch)") + @Test + void getRecruitBoardBySearchWithNull() { + //given + Pageable pageable = getPageable(); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("") + .pageable(pageable) + .build(); + + //when + Page dtos = recruitBoardDocumentService.getRecruitBoardBySearch(condition); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(25); + assertThat(dtos.getSize()).isEqualTo(5); + assertThat(dtos.getTotalPages()).isEqualTo(5); + } + + @DisplayName("위치 기반으로 반경 내에 검색 키워드가 포함된 모집글을 반환한다. (elasticsearch)") + @Test + void findAllNearByLocationWithKeyword() { + // given + Pageable pageable = getPageable(); + + RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() + .keyword("도서관") + .latitude(37.5935) + .longitude(126.9780) + .radius(5.0) + .pageable(pageable) + .build(); + + // when + Page result = recruitBoardDocumentService.getRecruitBoardsNearbyWithKeyword( + condition); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(1); + } + + @DisplayName("키워드 없이 검색 시 위치 기반으로 주변 모집글을 전부 페이징하여 조회할 수 있다. (elasticsearch)") + @Test + void getRecruitBoardsNearBy() { + // given + Pageable pageable = getPageable(); + RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() + .keyword(null) + .latitude(37.5935) + .longitude(126.9780) + .radius(5.0) + .pageable(pageable) + .build(); + + // when + Page result = recruitBoardDocumentService.getRecruitBoardsNearbyWithKeyword( + condition); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(25); + assertThat(result.getContent()).isNotEmpty(); + } + + @DisplayName("모집글을 elastic search index에 저장한다. (service)") + @Test + void saveRecruitBoardDocuments() { + //given + Center center = createCenter("특별한 기관"); + centerRepository.save(center); + + Pageable pageable = getPageable(); + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword("저장") + .pageable(pageable) + .build(); + + List recruitBoards = new ArrayList<>(); + + RecruitBoard board1 = createRecruitBoard("저장 잘 되나요?", center.getId()); + RecruitBoard savedBoard1 = recruitBoardRepository.save(board1); + RecruitBoard board2 = createRecruitBoard("저장해줘", center.getId()); + RecruitBoard savedBoard2 = recruitBoardRepository.save(board2); + recruitBoards.add(savedBoard1); + recruitBoards.add(savedBoard2); + + //when + recruitBoardDocumentService.saveRecruitBoardDocuments(recruitBoards); + + //then + Page findBoard = recruitBoardDocumentService.getRecruitBoardBySearch(condition); + + assertThat(findBoard).isNotNull(); + assertThat(findBoard.getTotalElements()).isEqualTo(2); + + searchBoardRepository.deleteRecruitBoardDocument(savedBoard1.getId()); + searchBoardRepository.deleteRecruitBoardDocument(savedBoard2.getId()); + } + + private Pageable getPageable() { + Sort sort = Sort.by(Sort.Order.desc("created_at")); + return PageRequest.of(0, 5, sort); + } +} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index d18f99866..35ea4f410 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -28,10 +28,6 @@ spring: port: 6379 password: # 테스트에서는 비밀번호 없이 연결 - elasticsearch: - repositories: - enabled: true - web: locale: ko_KR locale-resolver: fixed @@ -70,11 +66,7 @@ default: elastic: search: - uri: ec2-43-201-249-131.ap-northeast-2.compute.amazonaws.com:9200 + uri: localhost:9200 username: elastic password: changeme - -management: - health: - elasticsearch: - enabled: false + enabled: false \ No newline at end of file From fbadb2c4a18a2240960c97a0d0128ead5ec5431c Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Thu, 9 Jan 2025 16:56:14 +0900 Subject: [PATCH 03/26] =?UTF-8?q?test(search):=20import=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 --- .../recruitboard/controller/RecruitBoardQueryApiController.java | 2 -- .../search/controller/RecruitBoardSearchApiController.java | 2 +- .../domains/search/scheduler/RecruitBoardUpdateScheduler.java | 2 +- .../domains/search/repository/SearchBoardRepositoryTest.java | 2 +- .../search/service/CommunityBoardDocumentServiceTest.java | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java index 2bd0adacb..37f90efbc 100644 --- a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java +++ b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java @@ -2,9 +2,7 @@ import com.somemore.domains.recruitboard.domain.RecruitStatus; import com.somemore.domains.recruitboard.domain.VolunteerCategory; -import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; -import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithLocationResponseDto; diff --git a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java index 48e5b1f4e..c748bd238 100644 --- a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java +++ b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java @@ -7,7 +7,7 @@ import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -import com.somemore.domains.recruitboard.usecase.query.RecruitBoardQueryUseCase; +import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import com.somemore.global.common.response.ApiResponse; diff --git a/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java index 5fcee4022..ee43bc38f 100644 --- a/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java +++ b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java @@ -1,7 +1,7 @@ package com.somemore.domains.search.scheduler; import com.somemore.domains.recruitboard.domain.RecruitBoard; -import com.somemore.domains.recruitboard.usecase.query.RecruitBoardQueryUseCase; +import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java index 48e91cfbe..c67a4c241 100644 --- a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java +++ b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java @@ -15,7 +15,7 @@ import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.repository.VolunteerRepository; -import com.somemore.global.auth.oauth.OAuthProvider; +import com.somemore.global.auth.oauth.domain.OAuthProvider; import com.somemore.support.IntegrationTestSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java index dbe7fdbd5..646a79d00 100644 --- a/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java +++ b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java @@ -6,7 +6,7 @@ import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.repository.VolunteerRepository; -import com.somemore.global.auth.oauth.OAuthProvider; +import com.somemore.global.auth.oauth.domain.OAuthProvider; import com.somemore.support.IntegrationTestSupport; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; From 8b64b59edd28a18de053746491d947d2ac419f86 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 10 Jan 2025 16:32:33 +0900 Subject: [PATCH 04/26] =?UTF-8?q?test(search):=20controller=20test=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - elastic search 서버 연결 여부에 따른 분기 추가 --- ...CommunityBoardSearchApiControllerTest.java | 27 +++++++++-- .../RecruitBoardSearchApiControllerTest.java | 46 +++++++++++++++---- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java b/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java index b38db0022..15a68337a 100644 --- a/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java +++ b/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java @@ -1,6 +1,8 @@ package com.somemore.domains.search.controller; import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.community.usecase.board.CommunityBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; import com.somemore.support.ControllerTestSupport; import org.junit.jupiter.api.DisplayName; @@ -10,6 +12,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.EnabledIf; import org.springframework.test.web.servlet.MockMvc; import java.util.Collections; @@ -28,17 +31,28 @@ class CommunityBoardSearchApiControllerTest extends ControllerTestSupport { @Autowired private MockMvc mockMvc; + @Autowired + private ElasticsearchHealthChecker elasticsearchHealthChecker; + @MockBean private CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + @MockBean + private CommunityBoardQueryUseCase communityBoardQueryUseCase; + @Test @DisplayName("게시글을 검색 조건으로 페이징 조회 성공") void getBySearch() throws Exception { // given Page page = new PageImpl<>(Collections.emptyList()); - given(communityBoardDocumentUseCase.getCommunityBoardBySearch(any(), anyInt())) - .willReturn(page); + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(communityBoardDocumentUseCase.getCommunityBoardBySearch(any(), anyInt())) + .willReturn(page); + } else { + given(communityBoardQueryUseCase.getCommunityBoards(any(), anyInt())) + .willReturn(page); + } // when // then @@ -50,7 +64,12 @@ void getBySearch() throws Exception { .andExpect(jsonPath("$.data").exists()) .andExpect(jsonPath("$.message").value("커뮤니티 게시글 검색 리스트 조회 성공")); - verify(communityBoardDocumentUseCase, times(1)).getCommunityBoardBySearch( - any(), anyInt()); + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + verify(communityBoardDocumentUseCase, times(1)).getCommunityBoardBySearch( + any(), anyInt()); + } else { + verify(communityBoardQueryUseCase, times(1)).getCommunityBoards( + any(), anyInt()); + } } } diff --git a/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java b/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java index 140e12fa2..d97253a47 100644 --- a/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java +++ b/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java @@ -4,6 +4,8 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import com.somemore.support.ControllerTestSupport; import org.junit.jupiter.api.DisplayName; @@ -13,6 +15,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.EnabledIf; import org.springframework.test.web.servlet.MockMvc; import java.util.Collections; @@ -31,17 +34,28 @@ class RecruitBoardSearchApiControllerTest extends ControllerTestSupport { @Autowired private MockMvc mockMvc; + @Autowired + private ElasticsearchHealthChecker elasticsearchHealthChecker; + @MockBean private RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; + @MockBean + private RecruitBoardQueryUseCase recruitBoardQueryUseCase; + @Test @DisplayName("모집글을 검색 조건으로 페이징 조회 성공") void getAllBySearch() throws Exception { // given Page page = new PageImpl<>(Collections.emptyList()); - given(recruitBoardDocumentUseCase.getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class))) - .willReturn(page); + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(recruitBoardDocumentUseCase.getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class))) + .willReturn(page); + } else { + given(recruitBoardQueryUseCase.getAllWithCenter(any(RecruitBoardSearchCondition.class))) + .willReturn(page); + } // when // then @@ -54,7 +68,11 @@ void getAllBySearch() throws Exception { .andExpect(jsonPath("$.data").exists()) .andExpect(jsonPath("$.message").value("봉사 활동 모집글 검색 조회 성공")); - verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class)); + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class)); + } else { + verify(recruitBoardQueryUseCase, times(1)).getAllWithCenter(any(RecruitBoardSearchCondition.class)); + } } @Test @@ -62,9 +80,16 @@ void getAllBySearch() throws Exception { void getNearby() throws Exception { // given Page page = new PageImpl<>(Collections.emptyList()); - given(recruitBoardDocumentUseCase.getRecruitBoardsNearbyWithKeyword( - any(RecruitBoardNearByCondition.class) - )).willReturn(page); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(recruitBoardDocumentUseCase.getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class) + )).willReturn(page); + } else { + given(recruitBoardQueryUseCase.getRecruitBoardsNearby( + any(RecruitBoardNearByCondition.class) + )).willReturn(page); + } // when // then @@ -78,7 +103,12 @@ void getNearby() throws Exception { .andExpect(jsonPath("$.data").exists()) .andExpect(jsonPath("$.message").value("근처 봉사 활동 모집글 조회 성공")); - verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardsNearbyWithKeyword( - any(RecruitBoardNearByCondition.class)); + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class)); + } else { + verify(recruitBoardQueryUseCase, times(1)).getRecruitBoardsNearby( + any(RecruitBoardNearByCondition.class)); + } } } From 9c9001e978dc1707865a5b9477bb4aad67bd46a6 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 20:17:42 +0900 Subject: [PATCH 05/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20Document=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - writerNickname, createdAt 필드 추가 - elastic search 검색 조건에 writerNickname 및 가중치 추가 --- .../search/domain/CommunityBoardDocument.java | 13 ++++++++++++- .../CommunityBoardDocumentRepository.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java index 0a22e77b3..43c3fd191 100644 --- a/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java +++ b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java @@ -7,6 +7,8 @@ import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; +import java.time.LocalDateTime; + @Getter @Document(indexName = "community_board") public class CommunityBoardDocument { @@ -20,10 +22,19 @@ public class CommunityBoardDocument { @Field(type = FieldType.Text, analyzer = "nori_analyzer") private String content; + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String writerNickname; + + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime createdAt; + @Builder - public CommunityBoardDocument(Long id, String title, String content) { + public CommunityBoardDocument(Long id, String title, String content, + String writerNickname, LocalDateTime createdAt) { this.id = id; this.title = title; this.content = content; + this.writerNickname = writerNickname; + this.createdAt = createdAt; } } diff --git a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java index 8f40913ba..c4539755a 100644 --- a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -10,6 +10,6 @@ @ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { List findAll(); - @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") + @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content^2\", \"writerNickname\"]}}") List findIdsByTitleOrContentContaining(String keyword); } From dcbbe253759a3a4762a0b74f5cba845cb3fb3e6c Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 20:22:07 +0900 Subject: [PATCH 06/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EA=B2=80=EC=83=89=20=EC=BF=BC=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ResponseDto에 fromDocument 추가 - 기존) elastic search에서 검색 결과 글 id 반환 -> id로 rdb에서 검색 후 communityBoardView 반환 수정) elastic search 검색 결과인 communityDocument 전체 반환 --- .../response/CommunityBoardResponseDto.java | 15 ++++- .../repository/SearchBoardRepository.java | 4 +- .../repository/SearchBoardRepositoryImpl.java | 57 +++++-------------- .../CommunityBoardDocumentService.java | 6 +- 4 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java b/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java index 35fb501d2..43ea2f306 100644 --- a/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java +++ b/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.search.domain.CommunityBoardDocument; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; @@ -12,9 +13,9 @@ public record CommunityBoardResponseDto( @Schema(description = "커뮤니티 게시글 ID", example = "12") Long id, - @Schema(description = "커뮤니티 게시글 ID", example = "12") + @Schema(description = "커뮤니티 게시글 제목", example = "11/29 OO도서관 봉사 같이 갈 사람 모집합니다.") String title, - @Schema(description = "작성자(봉사자) ID", example = "123e4567-e89b-12d3-a456-426614174000") + @Schema(description = "작성자(봉사자) 닉네임", example = "나는야 봉사왕") String writerNickname, @Schema(description = "커뮤니티 게시글 생성 일시", example = "2023-12-02T11:00:00") LocalDateTime createdAt @@ -27,5 +28,15 @@ public static CommunityBoardResponseDto from(CommunityBoardView board) { board.communityBoard().getCreatedAt() ); } + + public static CommunityBoardResponseDto fromDocument(CommunityBoardDocument board) { + return new CommunityBoardResponseDto( + board.getId(), + board.getTitle(), + board.getWriterNickname(), + board.getCreatedAt() + ); + } + } diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java index d1a89eadb..0b99d4774 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java @@ -1,12 +1,12 @@ package com.somemore.domains.search.repository; import com.somemore.domains.community.domain.CommunityBoard; -import com.somemore.domains.community.repository.mapper.CommunityBoardView; import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.CommunityBoardDocument; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -20,7 +20,7 @@ public interface SearchBoardRepository { void deleteRecruitBoardDocument(Long id); - Page findByCommunityBoardsContaining(String keyword, Pageable pageable); + Page findByCommunityBoardsContaining(String keyword, Pageable pageable); void saveCommunityBoardDocuments(List communityBoards); void deleteCommunityBoardDocument(Long id); } diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index 55a610b62..e15600e99 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -8,8 +8,6 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.somemore.domains.center.domain.QCenter; import com.somemore.domains.community.domain.CommunityBoard; -import com.somemore.domains.community.domain.QCommunityBoard; -import com.somemore.domains.community.repository.mapper.CommunityBoardView; import com.somemore.domains.location.domain.QLocation; import com.somemore.domains.location.utils.GeoUtils; import com.somemore.domains.recruitboard.domain.QRecruitBoard; @@ -22,7 +20,7 @@ import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.domain.RecruitBoardDocument; -import com.somemore.domains.volunteer.domain.QVolunteer; +import com.somemore.domains.volunteer.usecase.VolunteerQueryUseCase; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -44,13 +42,12 @@ public class SearchBoardRepositoryImpl implements SearchBoardRepository { private final CommunityBoardDocumentRepository communityBoardDocumentRepository; private final JPAQueryFactory queryFactory; + private final VolunteerQueryUseCase volunteerQueryUseCase; + private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; private static final QLocation location = QLocation.location; private static final QCenter center = QCenter.center; - private static final QCommunityBoard communityBoard = QCommunityBoard.communityBoard; - private static final QVolunteer volunteer = QVolunteer.volunteer; - @Override public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { @@ -135,36 +132,15 @@ public void saveRecruitBoardDocuments(List recruitBoards) { recruitBoardDocumentRepository.saveAll(recruitBoardDocuments); } - @Override public void deleteRecruitBoardDocument(Long id) { recruitBoardDocumentRepository.deleteById(id); } - @Override - public Page findByCommunityBoardsContaining(String keyword, Pageable pageable) { + public Page findByCommunityBoardsContaining(String keyword, Pageable pageable) { List boardDocuments = getCommunityBoardDocuments(keyword); - - List boardIds = boardDocuments.stream() - .map(CommunityBoardDocument::getId) - .toList(); - - List content = getCommunityBoardsQuery() - .where(communityBoard.id.in(boardIds) - .and(isNotDeletedCommunityBoard())) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch(); - - JPAQuery countQuery = queryFactory - .select(communityBoard.count()) - .from(communityBoard) - .join(volunteer).on(communityBoard.writerId.eq(volunteer.id)) - .where(communityBoard.id.in(boardIds) - .and(isNotDeletedCommunityBoard())); - - return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + return PageableExecutionUtils.getPage(boardDocuments, pageable, boardDocuments::size); } @Override @@ -184,8 +160,13 @@ private List convertRecruitBoardToDocuments(List convertCommunityBoardToDocuments(List communityBoardDocuments = new ArrayList<>(); for (CommunityBoard communityboard : communityBoards) { + String nickname = volunteerQueryUseCase.getNicknameById(communityboard.getWriterId()); + CommunityBoardDocument document = CommunityBoardDocument.builder() .id(communityboard.getId()) .title(communityboard.getTitle()) .content(communityboard.getContent()) + .writerNickname(nickname) + .createdAt(communityboard.getCreatedAt()) .build(); communityBoardDocuments.add(document); } return communityBoardDocuments; } - private JPAQuery getCommunityBoardsQuery() { - return queryFactory - .select(Projections.constructor(CommunityBoardView.class, - communityBoard, - volunteer.nickname)) - .from(communityBoard) - .join(volunteer).on(communityBoard.writerId.eq(volunteer.id)) - .orderBy(communityBoard.createdAt.desc()); - } - private BooleanExpression isNotDeletedRecruitBoard() { return recruitBoard.deleted.eq(false); } - private BooleanExpression isNotDeletedCommunityBoard() { - return communityBoard.deleted.eq(false); - } - private BooleanExpression volunteerCategoryEq(VolunteerCategory category) { return category != null ? recruitBoard.recruitmentInfo.volunteerCategory.eq(category) : null; diff --git a/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java index 4434c34a3..54e104bc7 100644 --- a/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java +++ b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java @@ -2,7 +2,7 @@ import com.somemore.domains.community.domain.CommunityBoard; import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; -import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; import lombok.RequiredArgsConstructor; @@ -27,8 +27,8 @@ public class CommunityBoardDocumentService implements CommunityBoardDocumentUseC @Override public Page getCommunityBoardBySearch(String keyword, int page) { Pageable pageable = PageRequest.of(page, PAGE_SIZE); - Page boards = searchBoardRepository.findByCommunityBoardsContaining(keyword, pageable); - return boards.map(CommunityBoardResponseDto::from); + Page boards = searchBoardRepository.findByCommunityBoardsContaining(keyword, pageable); + return boards.map(CommunityBoardResponseDto::fromDocument); } @Transactional From a9626c605dc4dc3fb918acbfe8286805c3055e1f Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 23:42:25 +0900 Subject: [PATCH 07/26] =?UTF-8?q?test(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EA=B2=80=EC=83=89=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=EC=97=90=20=EB=94=B0=EB=A5=B8=20test=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SearchBoardRepositoryTest.java | 80 +++++++++---------- .../CommunityBoardUpdateSchedulerTest.java | 3 +- .../CommunityBoardDocumentServiceTest.java | 61 ++++++++------ 3 files changed, 75 insertions(+), 69 deletions(-) diff --git a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java index c67a4c241..4ab828c70 100644 --- a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java +++ b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java @@ -4,19 +4,18 @@ import com.somemore.domains.center.repository.center.CenterRepository; import com.somemore.domains.community.domain.CommunityBoard; import com.somemore.domains.community.repository.board.CommunityBoardRepository; -import com.somemore.domains.community.repository.mapper.CommunityBoardView; -import com.somemore.domains.location.domain.Location; -import com.somemore.domains.location.repository.LocationRepository; import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.repository.VolunteerRepository; import com.somemore.global.auth.oauth.domain.OAuthProvider; import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,7 +27,6 @@ import org.springframework.transaction.annotation.Transactional; import static com.somemore.support.fixture.CenterFixture.createCenter; -import static com.somemore.support.fixture.LocationFixture.createLocation; import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; import static org.assertj.core.api.Assertions.assertThat; @@ -47,8 +45,6 @@ class SearchBoardRepositoryTest extends IntegrationTestSupport { @Autowired private RecruitBoardRepository recruitBoardRepository; @Autowired - private LocationRepository locationRepository; - @Autowired private CenterRepository centerRepository; @Autowired private SearchBoardRepository searchBoardRepository; @@ -56,7 +52,6 @@ class SearchBoardRepositoryTest extends IntegrationTestSupport { private VolunteerRepository volunteerRepository; private UUID writerId, centerId; - private final List boards = new ArrayList<>(); @BeforeEach void setUp() { @@ -65,25 +60,14 @@ void setUp() { volunteerRepository.save(volunteer1); writerId = volunteer1.getId(); - for (int i = 1; i <= 20; i++) { - String title = "제목" + i; - CommunityBoard communityBoard = createCommunityBoard(title, writerId); - communityBoardRepository.save(communityBoard); - } - - Location location = createLocation(); - locationRepository.save(location); - Center center = createCenter(); centerRepository.save(center); centerId = center.getId(); + } - for (int i = 1; i <= 20; i++) { - String title = "제목" + i; - RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); - boards.add(board); - } - recruitBoardRepository.saveAll(boards); + @AfterEach + void tearDown() { + searchBoardRepository.deleteAllCommunityBoardDocument(); } @DisplayName("커뮤니티 게시글을 elastic search index에 저장할 수 있다. (repository)") @@ -105,52 +89,64 @@ void saveCommunityBoardDocuments() { searchBoardRepository.saveCommunityBoardDocuments(communityBoards); //then - Page findBoard = searchBoardRepository.findByCommunityBoardsContaining("저장", pageable); + Page findBoard = searchBoardRepository.findByCommunityBoardsContaining("저장", pageable); assertThat(findBoard).isNotNull(); assertThat(findBoard.getTotalElements()).isEqualTo(2); - - searchBoardRepository.deleteCommunityBoardDocument(savedBoard1.getId()); - searchBoardRepository.deleteCommunityBoardDocument(savedBoard2.getId()); } - @DisplayName("검색 키워드가 포함된 게시글을 페이지로 조회할 수 있다. (elasticsearch)") @Test void findByCommunityBoardsContaining() { - //given Pageable pageable = getPageable(10); - String title = "봉사"; - CommunityBoard communityBoard = createCommunityBoard(title, writerId); - communityBoardRepository.save(communityBoard); - searchBoardRepository.saveCommunityBoardDocuments(List.of(communityBoard)); + List communityBoards = new ArrayList<>(); + + CommunityBoard communityBoard1 = createCommunityBoard("검색", writerId); + CommunityBoard savedBoard1 = communityBoardRepository.save(communityBoard1); + CommunityBoard communityBoard2 = createCommunityBoard("제목", writerId); + CommunityBoard savedBoard2 = communityBoardRepository.save(communityBoard2); + communityBoards.add(savedBoard1); + communityBoards.add(savedBoard2); + + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); //when - Page communityBoards = searchBoardRepository.findByCommunityBoardsContaining("봉사", pageable); + Page findBoards = searchBoardRepository.findByCommunityBoardsContaining("검색", pageable); //then - assertThat(communityBoards).isNotNull(); - assertThat(communityBoards.getTotalElements()).isEqualTo(1); - assertThat(communityBoards.getSize()).isEqualTo(10); - assertThat(communityBoards.getNumber()).isZero(); + assertThat(findBoards).isNotNull(); + assertThat(findBoards.getTotalElements()).isEqualTo(1); + assertThat(findBoards.getSize()).isEqualTo(10); + assertThat(findBoards.getNumber()).isZero(); } @DisplayName("키워드 없이 검색 시 전체 게시글을 페이지로 조회할 수 있다. (elasticsearch)") @Test void findByCommunityBoardsContainingWithNull() { - //given Pageable pageable = getPageable(10); + List communityBoards = new ArrayList<>(); + + for (int i = 1; i <= 21; i++) { + String title = "제목" + i; + CommunityBoard communityBoard = createCommunityBoard(title, writerId); + CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); + communityBoards.add(savedBoard); + } + + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); + //when - Page communityBoards = searchBoardRepository.findByCommunityBoardsContaining(null, pageable); + Page findBoards = searchBoardRepository.findByCommunityBoardsContaining(null, pageable); //then - assertThat(communityBoards).isNotNull(); - assertThat(communityBoards.getTotalElements()).isEqualTo(20); - assertThat(communityBoards.getSize()).isEqualTo(10); + assertThat(findBoards).isNotNull(); + assertThat(findBoards.getTotalElements()).isEqualTo(21); + assertThat(findBoards.getTotalPages()).isEqualTo(3); + assertThat(findBoards.getSize()).isEqualTo(10); } @DisplayName("봉사 활동 모집글을 elastic search index에 저장할 수 있다. (repository)") diff --git a/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java index a057c54f0..5f95ece38 100644 --- a/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java +++ b/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java @@ -2,6 +2,7 @@ import com.somemore.domains.community.repository.board.CommunityBoardRepository; import com.somemore.domains.community.repository.mapper.CommunityBoardView; +import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.support.IntegrationTestSupport; import org.awaitility.Awaitility; @@ -36,7 +37,7 @@ void updateCommunityBoardDocuments() { // when // then - Page communityBoards = searchBoardRepository + Page communityBoards = searchBoardRepository .findByCommunityBoardsContaining(null, pageable); Awaitility.await() diff --git a/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java index 646a79d00..0bf054407 100644 --- a/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java +++ b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java @@ -8,6 +8,7 @@ import com.somemore.domains.volunteer.repository.VolunteerRepository; import com.somemore.global.auth.oauth.domain.OAuthProvider; import com.somemore.support.IntegrationTestSupport; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -30,32 +31,45 @@ class CommunityBoardDocumentServiceTest extends IntegrationTestSupport { @Autowired private CommunityBoardDocumentService communityBoardDocumentService; - - @Autowired - private CommunityBoardRepository communityBoardRepository; - @Autowired private SearchBoardRepository searchBoardRepository; - + @Autowired + private CommunityBoardRepository communityBoardRepository; @Autowired private VolunteerRepository volunteerRepository; - private UUID writerId; + private UUID volunteerId; @BeforeEach void setUp() { + List communityBoards = new ArrayList<>(); + String oAuthId1 = "example-oauth-id1"; - Volunteer volunteer1 = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); - volunteerRepository.save(volunteer1); - writerId = volunteer1.getId(); + Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); + volunteerRepository.save(volunteer); + volunteerId = volunteer.getId(); - for (int i = 1; i <= 18; i++) { + for (int i = 1; i <= 11; i++) { String title = "제목" + i; - CommunityBoard communityBoard = createCommunityBoard(title, writerId); - communityBoardRepository.save(communityBoard); + CommunityBoard communityBoard = createCommunityBoard(title, volunteerId); + CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); + communityBoards.add(savedBoard); } + + for (int i = 1; i <= 11; i++) { + String title = "봉사" + i; + CommunityBoard communityBoard = createCommunityBoard(title, volunteerId); + CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); + communityBoards.add(savedBoard); + } + + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); + } + @AfterEach + void tearDown() { searchBoardRepository.deleteAllCommunityBoardDocument(); } + @DisplayName("검색 키워드가 포함된 게시글을 조회한다. (ealsticsearch)") @Test void getCommunityBoardBySearch() { @@ -66,9 +80,9 @@ void getCommunityBoardBySearch() { //then assertThat(dtos).isNotNull(); assertThat(dtos.getContent()).isNotNull(); - assertThat(dtos.getTotalElements()).isEqualTo(10); + assertThat(dtos.getTotalElements()).isEqualTo(11); assertThat(dtos.getSize()).isEqualTo(10); - assertThat(dtos.getTotalPages()).isEqualTo(1); + assertThat(dtos.getTotalPages()).isEqualTo(2); } @DisplayName("검색 키워드 없이 조회시 전체 게시글을 조회한다. (elasticsearch)") @@ -81,33 +95,28 @@ void getCommunityBoardBySearchWithNull() { //then assertThat(dtos).isNotNull(); assertThat(dtos.getContent()).isNotNull(); - assertThat(dtos.getTotalElements()).isEqualTo(18); + assertThat(dtos.getTotalElements()).isEqualTo(22); assertThat(dtos.getSize()).isEqualTo(10); - assertThat(dtos.getTotalPages()).isEqualTo(2); + assertThat(dtos.getTotalPages()).isEqualTo(3); } @DisplayName("게시글을 elastic search index에 저장한다. (service)") @Test void saveCommunityBoardDocuments() { //given - List communityBoards = new ArrayList<>(); - - CommunityBoard communityBoard = createCommunityBoard("저장 잘 되나요?", "안녕하세요", UUID.randomUUID()); + CommunityBoard communityBoard = createCommunityBoard("저장 잘 되나요?", "안녕하세요", volunteerId); CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); - communityBoards.add(savedBoard); //when - communityBoardDocumentService.saveCommunityBoardDocuments(communityBoards); + communityBoardDocumentService.saveCommunityBoardDocuments(List.of(savedBoard)); //then - Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("저장", 0); assertThat(dtos).isNotNull(); assertThat(dtos.getContent()).isNotNull(); - assertThat(dtos.getTotalElements()).isEqualTo(18); + assertThat(dtos.getTotalElements()).isEqualTo(1); assertThat(dtos.getSize()).isEqualTo(10); - assertThat(dtos.getTotalPages()).isEqualTo(2); - - searchBoardRepository.deleteCommunityBoardDocument(savedBoard.getId()); + assertThat(dtos.getTotalPages()).isEqualTo(1); } } From 1c036f7680336416f087dff98b246a977ec267b3 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 23:48:44 +0900 Subject: [PATCH 08/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20Document=20updatedAt=20=ED=95=84=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/search/domain/CommunityBoardDocument.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java index 43c3fd191..011e4005e 100644 --- a/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java +++ b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java @@ -28,13 +28,17 @@ public class CommunityBoardDocument { @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") private LocalDateTime createdAt; + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime updatedAt; + @Builder - public CommunityBoardDocument(Long id, String title, String content, - String writerNickname, LocalDateTime createdAt) { + public CommunityBoardDocument(Long id, String title, String content, String writerNickname, + LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.title = title; this.content = content; this.writerNickname = writerNickname; this.createdAt = createdAt; + this.updatedAt = updatedAt; } } From f6d7b9f0bf4f1bdf833304ce39c3c677b60f9bc6 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 23:50:29 +0900 Subject: [PATCH 09/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20findAll()=20?= =?UTF-8?q?->=20findAllByDeletedFalse()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - elasticsearch 저장을 위한 전체 게시글 조회 메서드 변경 --- .../repository/board/CommunityBoardJpaRepository.java | 3 +++ .../community/repository/board/CommunityBoardRepository.java | 2 +- .../repository/board/CommunityBoardRepositoryImpl.java | 4 ++-- .../community/service/board/CommunityBoardQueryService.java | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardJpaRepository.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardJpaRepository.java index eda62438c..7d6ec8ac8 100644 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardJpaRepository.java +++ b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardJpaRepository.java @@ -3,6 +3,9 @@ import com.somemore.domains.community.domain.CommunityBoard; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface CommunityBoardJpaRepository extends JpaRepository { boolean existsByIdAndDeletedFalse(Long id); + List findAllByDeletedFalse(); } diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java index 8e488f467..c1fcaaf8c 100644 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java +++ b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepository.java @@ -19,5 +19,5 @@ default boolean doesNotExistById(Long id) { return !existsById(id); } void deleteAllInBatch(); - List findAll(); + List findAllByDeletedFalse(); } diff --git a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java index d7c471462..c0aa1a43a 100644 --- a/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/community/repository/board/CommunityBoardRepositoryImpl.java @@ -102,8 +102,8 @@ private JPAQuery getCommunityBoardsQuery() { } @Override - public List findAll() { - return communityBoardJpaRepository.findAll(); + public List findAllByDeletedFalse() { + return communityBoardJpaRepository.findAllByDeletedFalse(); } private BooleanExpression isNotDeleted() { diff --git a/src/main/java/com/somemore/domains/community/service/board/CommunityBoardQueryService.java b/src/main/java/com/somemore/domains/community/service/board/CommunityBoardQueryService.java index eafa22319..0ea3f5178 100644 --- a/src/main/java/com/somemore/domains/community/service/board/CommunityBoardQueryService.java +++ b/src/main/java/com/somemore/domains/community/service/board/CommunityBoardQueryService.java @@ -50,6 +50,6 @@ public CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id) { @Override public List getAllCommunityBoards() { - return communityBoardRepository.findAll(); + return communityBoardRepository.findAllByDeletedFalse(); } } From f3d4c03693089caa9ae58e7fdb1c75ec7ad4cee8 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 23:51:07 +0900 Subject: [PATCH 10/26] =?UTF-8?q?refactor(search):=20=EA=B0=9C=ED=96=89=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/somemore/domains/community/domain/CommunityComment.java | 1 - .../community/dto/response/CommunityBoardResponseDto.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/somemore/domains/community/domain/CommunityComment.java b/src/main/java/com/somemore/domains/community/domain/CommunityComment.java index bf9280b17..4e23886db 100644 --- a/src/main/java/com/somemore/domains/community/domain/CommunityComment.java +++ b/src/main/java/com/somemore/domains/community/domain/CommunityComment.java @@ -17,7 +17,6 @@ import static lombok.AccessLevel.PROTECTED; - @Getter @NoArgsConstructor(access = PROTECTED) @Entity diff --git a/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java b/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java index 43ea2f306..3af194e53 100644 --- a/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java +++ b/src/main/java/com/somemore/domains/community/dto/response/CommunityBoardResponseDto.java @@ -37,6 +37,4 @@ public static CommunityBoardResponseDto fromDocument(CommunityBoardDocument boar board.getCreatedAt() ); } - } - From 8856018a28f8c602d5b26edacb696938210299f8 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Fri, 17 Jan 2025 23:51:43 +0900 Subject: [PATCH 11/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EA=B2=80=EC=83=89=20test=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20delete=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/search/repository/SearchBoardRepository.java | 2 +- .../domains/search/repository/SearchBoardRepositoryImpl.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java index 0b99d4774..796438631 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java @@ -22,5 +22,5 @@ public interface SearchBoardRepository { Page findByCommunityBoardsContaining(String keyword, Pageable pageable); void saveCommunityBoardDocuments(List communityBoards); - void deleteCommunityBoardDocument(Long id); + void deleteAllCommunityBoardDocument(); } diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index e15600e99..fad374b34 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -150,8 +150,8 @@ public void saveCommunityBoardDocuments(List communityBoards) { } @Override - public void deleteCommunityBoardDocument(Long id) { - communityBoardDocumentRepository.deleteById(id); + public void deleteAllCommunityBoardDocument() { + communityBoardDocumentRepository.deleteAll(); } private List convertRecruitBoardToDocuments(List recruitBoards) { @@ -185,6 +185,7 @@ private List convertCommunityBoardToDocuments(List Date: Tue, 21 Jan 2025 17:15:10 +0900 Subject: [PATCH 12/26] =?UTF-8?q?feat(center):=20center=20id=EB=A1=9C=20?= =?UTF-8?q?=EA=B8=B0=EA=B4=80=EB=AA=85=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/center/CenterRepository.java | 2 ++ .../center/CenterRepositoryImpl.java | 21 +++++++++++++++++++ .../service/query/CenterQueryService.java | 11 ++++++++++ .../usecase/query/CenterQueryUseCase.java | 1 + 4 files changed, 35 insertions(+) diff --git a/src/main/java/com/somemore/domains/center/repository/center/CenterRepository.java b/src/main/java/com/somemore/domains/center/repository/center/CenterRepository.java index 820d2a5e4..0858ea507 100644 --- a/src/main/java/com/somemore/domains/center/repository/center/CenterRepository.java +++ b/src/main/java/com/somemore/domains/center/repository/center/CenterRepository.java @@ -21,4 +21,6 @@ default boolean doesNotExistById(UUID id) { List findCenterOverviewsByIds(List ids); void deleteAllInBatch(); + + String findNameById(UUID id); } diff --git a/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java b/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java index 99698f066..2e57bccbf 100644 --- a/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java @@ -1,5 +1,6 @@ package com.somemore.domains.center.repository.center; +import com.querydsl.core.types.Path; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -66,7 +67,27 @@ public void deleteAllInBatch() { centerJpaRepository.deleteAllInBatch(); } + @Override + public String findNameById(UUID id) { + return findDynamicField(id, center.name) + .orElse(null); + } + private static BooleanExpression isNotDeleted() { return center.deleted.isFalse(); } + + private Optional findDynamicField(UUID id, Path field) { + + return Optional.ofNullable( + queryFactory + .select(field) + .from(center) + .where( + center.id.eq(id), + isNotDeleted() + ) + .fetchOne() + ); + } } diff --git a/src/main/java/com/somemore/domains/center/service/query/CenterQueryService.java b/src/main/java/com/somemore/domains/center/service/query/CenterQueryService.java index 5fc206dcb..357d83c7b 100644 --- a/src/main/java/com/somemore/domains/center/service/query/CenterQueryService.java +++ b/src/main/java/com/somemore/domains/center/service/query/CenterQueryService.java @@ -46,6 +46,17 @@ public void validateCenterExists(UUID id) { } } + @Override + public String getNameById(UUID id) { + String name = centerRepository.findNameById(id); + + if (name == null || name.isBlank()) { + throw new BadRequestException(NOT_EXISTS_CENTER); + } + + return name; + } + private Center getCenterById(UUID centerId) { return centerRepository.findCenterById(centerId) .orElseThrow(() -> new BadRequestException(NOT_EXISTS_CENTER.getMessage())); diff --git a/src/main/java/com/somemore/domains/center/usecase/query/CenterQueryUseCase.java b/src/main/java/com/somemore/domains/center/usecase/query/CenterQueryUseCase.java index 5176dc02c..0b19eeb84 100644 --- a/src/main/java/com/somemore/domains/center/usecase/query/CenterQueryUseCase.java +++ b/src/main/java/com/somemore/domains/center/usecase/query/CenterQueryUseCase.java @@ -12,4 +12,5 @@ public interface CenterQueryUseCase { List getCenterOverviewsByIds(List centerIds); void validateCenterExists(UUID centerId); + String getNameById(UUID id); } From 85b73d60a1398e37c4818156f6168c091f809ea7 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Tue, 21 Jan 2025 17:15:41 +0900 Subject: [PATCH 13/26] =?UTF-8?q?test(center):=20center=20id=EB=A1=9C=20?= =?UTF-8?q?=EA=B8=B0=EA=B4=80=EB=AA=85=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/CenterRepositoryTest.java | 26 ++++++++++++++++++ .../service/query/CenterQueryServiceTest.java | 27 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/test/java/com/somemore/domains/center/repository/CenterRepositoryTest.java b/src/test/java/com/somemore/domains/center/repository/CenterRepositoryTest.java index 5bc879a2b..cf252c28e 100644 --- a/src/test/java/com/somemore/domains/center/repository/CenterRepositoryTest.java +++ b/src/test/java/com/somemore/domains/center/repository/CenterRepositoryTest.java @@ -65,6 +65,32 @@ void notExistsById() { assertThat(isExist).isFalse(); } + @DisplayName("기관 Id로 기관명을 조회할 수 있다.") + @Test + void findNameById() { + //given + Center center = createCenter(); + centerRepository.save(center); + + //when + String foundName = centerRepository.findNameById(center.getId()); + + //then + assertThat(foundName).isNotNull(); + assertThat(foundName).isEqualTo("기본 기관 이름"); + } + + @DisplayName("존재하지 않는 기관 id로 기관명 조회 시 null을 반환한다.") + @Test + void findNameByNonExistentId() { + //given + //when + String foundName = centerRepository.findNameById(UUID.randomUUID()); + + //then + assertThat(foundName).isNull(); + } + private static Center createCenter() { return Center.create( "기본 기관 이름", diff --git a/src/test/java/com/somemore/domains/center/service/query/CenterQueryServiceTest.java b/src/test/java/com/somemore/domains/center/service/query/CenterQueryServiceTest.java index 058077268..06de9ade7 100644 --- a/src/test/java/com/somemore/domains/center/service/query/CenterQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/center/service/query/CenterQueryServiceTest.java @@ -136,6 +136,33 @@ void validateExistingCenter() { assertThatCode(callable).doesNotThrowAnyException(); } + @DisplayName("기관 Id로 기관명을 조회할 수 있다. (service)") + @Test + void getNameById() { + // given + Center center = createCenter(); + Center foundCenter = centerRepository.save(center); + + // when + String foundName = centerQueryService.getNameById(foundCenter.getId()); + + // then + assertThat(foundName).isEqualTo("기본 기관 이름"); + } + + @DisplayName("존재하지 않는 기관 id로 기관명 조회 시 예외가 발생한다. (service)") + @Test + void getNameByNonExistentId() { + // given + // when + ThrowableAssert.ThrowingCallable callable = () -> centerQueryService.getNameById(UUID.randomUUID()); + + // then + assertThatExceptionOfType(BadRequestException.class) + .isThrownBy(callable) + .withMessage(ExceptionMessage.NOT_EXISTS_CENTER.getMessage()); + } + private Center createCenter() { return Center.create( "기본 기관 이름", From 74f49723fe15c917a7e424af4abe2fba684c508d Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Tue, 21 Jan 2025 23:20:47 +0900 Subject: [PATCH 14/26] =?UTF-8?q?refactor(search):=20community=20elastic?= =?UTF-8?q?=20search=20=EA=B2=80=EC=83=89=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/repository/CommunityBoardDocumentRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java index c4539755a..0e8923715 100644 --- a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -11,5 +11,5 @@ public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { List findAll(); @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content^2\", \"writerNickname\"]}}") - List findIdsByTitleOrContentContaining(String keyword); + List findDocumentsByTitleOrContentOrNicknameContaining(String keyword); } From c8be8474309892e5a22cd44c83092a7bd795657b Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 00:54:57 +0900 Subject: [PATCH 15/26] =?UTF-8?q?refactor(search):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=BF=BC=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 유사어 검색 쿼리 추가 --- .../search/repository/CommunityBoardDocumentRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java index 0e8923715..805b2a232 100644 --- a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -10,6 +10,6 @@ @ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { List findAll(); - @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content^2\", \"writerNickname\"]}}") + @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content^2\", \"writerNickname\"], \"fuzziness\": \"AUTO\"}}") List findDocumentsByTitleOrContentOrNicknameContaining(String keyword); } From 6b5b3556e4c2062bd1e40bac12c3374ce73e0491 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 01:11:48 +0900 Subject: [PATCH 16/26] =?UTF-8?q?refactor(search):=20=EB=B4=89=EC=82=AC?= =?UTF-8?q?=ED=99=9C=EB=8F=99=20=EB=AA=A8=EC=A7=91=EA=B8=80=20document=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20responseD?= =?UTF-8?q?to=EC=97=90=20fromDocument=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RecruitBoardWithCenterResponseDto, RecruitBoardDetailResponseDto에 포함되는 필드 모두 추가 - RecruitBoardWithCenterResponseDto, RecruitBoardDetailResponseDto에 fromDocument 추가 --- .../RecruitBoardDetailResponseDto.java | 25 ++++++ .../RecruitBoardWithCenterResponseDto.java | 22 ++++++ .../search/domain/RecruitBoardDocument.java | 76 ++++++++++++++++++- 3 files changed, 121 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardDetailResponseDto.java b/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardDetailResponseDto.java index bc5206aac..72ed94318 100644 --- a/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardDetailResponseDto.java +++ b/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardDetailResponseDto.java @@ -9,9 +9,11 @@ import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; +import com.somemore.domains.search.domain.RecruitBoardDocument; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; +import java.math.BigDecimal; import java.time.LocalDateTime; @@ -79,4 +81,27 @@ public static RecruitBoardDetailResponseDto from(RecruitBoardDetail recruitBoard .build(); } + public static RecruitBoardDetailResponseDto fromDocument(RecruitBoardDocument document) { + return RecruitBoardDetailResponseDto.builder() + .id(document.getId()) + .createdAt(document.getCreatedAt()) + .updatedAt(document.getUpdatedAt()) + .title(document.getTitle()) + .content(document.getContent()) + .region(document.getRegion()) + .recruitStatus(document.getRecruitStatus()) + .recruitmentCount(document.getRecruitmentCount()) + .volunteerStartDateTime(document.getVolunteerStartDateTime()) + .volunteerEndDateTime(document.getVolunteerEndDateTime()) + .volunteerCategory(document.getVolunteerCategory()) + .volunteerHours(document.getVolunteerHours()) + .admitted(document.getAdmitted()) + .location(LocationResponseDto.of(document.getAddress(), + BigDecimal.valueOf(document.getLocation()[1]), + BigDecimal.valueOf(document.getLocation()[0]))) + .center(CenterSimpleInfoResponseDto.of(document.getCenterId(), + document.getCenterName())) + .build(); + } + } diff --git a/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardWithCenterResponseDto.java b/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardWithCenterResponseDto.java index 97c788813..091ac6782 100644 --- a/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardWithCenterResponseDto.java +++ b/src/main/java/com/somemore/domains/recruitboard/dto/response/RecruitBoardWithCenterResponseDto.java @@ -8,6 +8,7 @@ import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.RecruitBoardDocument; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -73,5 +74,26 @@ public static RecruitBoardWithCenterResponseDto from( recruitBoardWithCenter.centerName())) .build(); } + + public static RecruitBoardWithCenterResponseDto fromDocument(RecruitBoardDocument document) { + return RecruitBoardWithCenterResponseDto.builder() + .id(document.getId()) + .locationId(document.getLocationId()) + .createdAt(document.getCreatedAt()) + .updatedAt(document.getUpdatedAt()) + .title(document.getTitle()) + .content(document.getContent()) + .region(document.getRegion()) + .recruitStatus(document.getRecruitStatus()) + .recruitmentCount(document.getRecruitmentCount()) + .volunteerStartDateTime(document.getVolunteerStartDateTime()) + .volunteerEndDateTime(document.getVolunteerEndDateTime()) + .volunteerCategory(document.getVolunteerCategory()) + .volunteerHours(document.getVolunteerHours()) + .admitted(document.getAdmitted()) + .center(CenterSimpleInfoResponseDto.of(document.getCenterId(), + document.getCenterName())) + .build(); + } } diff --git a/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java b/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java index 74a6db9cd..64b7cd5b4 100644 --- a/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java +++ b/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java @@ -1,11 +1,17 @@ package com.somemore.domains.search.domain; -import jakarta.persistence.Id; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.VolunteerCategory; import lombok.Builder; import lombok.Getter; +import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; +import org.springframework.data.elasticsearch.annotations.GeoPointField; + +import java.time.LocalDateTime; +import java.util.UUID; @Getter @Document(indexName = "recruit_board") @@ -20,10 +26,76 @@ public class RecruitBoardDocument { @Field(type = FieldType.Text, analyzer = "nori_analyzer") private String content; + @Field(type = FieldType.Text, analyzer = "nori_analyzer") + private String centerName; + + @Field(type = FieldType.Long) + private Long locationId; + + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime createdAt; + + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime updatedAt; + + @Field(type = FieldType.Text) + private String region; + + @Field(type = FieldType.Keyword) + private RecruitStatus recruitStatus; + + @Field(type = FieldType.Integer) + private Integer recruitmentCount; + + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime volunteerStartDateTime; + + @Field(type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS") + private LocalDateTime volunteerEndDateTime; + + @Field(type = FieldType.Keyword) + private VolunteerCategory volunteerCategory; + + @Field(type = FieldType.Integer) + private Integer volunteerHours; + + @Field(type = FieldType.Boolean) + private Boolean admitted; + + @Field(type = FieldType.Keyword) + private UUID centerId; + + @Field(type = FieldType.Text) + private String address; + + @GeoPointField + private double[] location; + @Builder - public RecruitBoardDocument(Long id, String title, String content) { + public RecruitBoardDocument(Long id, String title, String content, String centerName, + Long locationId, LocalDateTime createdAt, LocalDateTime updatedAt, + String region, RecruitStatus recruitStatus, Integer recruitmentCount, + LocalDateTime volunteerStartDateTime, LocalDateTime volunteerEndDateTime, + VolunteerCategory volunteerCategory, Integer volunteerHours, + Boolean admitted, UUID centerId, String address, + double[] location) { this.id = id; this.title = title; this.content = content; + this.centerName = centerName; + this.locationId = locationId; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.region = region; + this.recruitStatus = recruitStatus; + this.recruitmentCount = recruitmentCount; + this.volunteerStartDateTime = volunteerStartDateTime; + this.volunteerEndDateTime = volunteerEndDateTime; + this.volunteerCategory = volunteerCategory; + this.volunteerHours = volunteerHours; + this.admitted = admitted; + this.centerId = centerId; + this.address = address; + this.location = location; } } From 13f7f8cbfa2e0a36e5f5bd3488eff8f3a9b52985 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 01:13:48 +0900 Subject: [PATCH 17/26] =?UTF-8?q?refactor(recruit=5Fboard):=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20findAll()=20->=20findAllByDeletedFalse()=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - elasticsearch index 저장을 위한 전체 데이터 조회 시 deleted 조건 추가 --- .../repository/RecruitBoardJpaRepository.java | 4 +++- .../recruitboard/repository/RecruitBoardRepository.java | 2 +- .../repository/RecruitBoardRepositoryImpl.java | 8 ++++++-- .../recruitboard/service/RecruitBoardQueryService.java | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardJpaRepository.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardJpaRepository.java index 0a5395328..a853eef50 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardJpaRepository.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardJpaRepository.java @@ -3,6 +3,8 @@ import com.somemore.domains.recruitboard.domain.RecruitBoard; import org.springframework.data.jpa.repository.JpaRepository; -public interface RecruitBoardJpaRepository extends JpaRepository { +import java.util.List; +public interface RecruitBoardJpaRepository extends JpaRepository { + List findAllByDeletedFalse(); } diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java index a596d76ab..58516d7e3 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java @@ -32,7 +32,7 @@ public interface RecruitBoardRepository { List findAllByIds(List ids); - List findAll(); + List findAllByDeletedFalse(); long updateStatusToClosedForDateRange(LocalDateTime startTime, LocalDateTime endTime); diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java index ee9898d2c..97d8e368f 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -1,5 +1,9 @@ package com.somemore.domains.recruitboard.repository; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; + import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; @@ -205,8 +209,8 @@ public List findAllByIds(List ids) { } @Override - public List findAll() { - return recruitBoardJpaRepository.findAll(); + public List findAllByDeletedFalse() { + return recruitBoardJpaRepository.findAllByDeletedFalse(); } @Override diff --git a/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryService.java b/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryService.java index 0890ace27..3557893c6 100644 --- a/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryService.java +++ b/src/main/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryService.java @@ -84,6 +84,6 @@ public List getAllByIds(List ids) { @Override public List getAllRecruitBoards() { - return recruitBoardRepository.findAll(); + return recruitBoardRepository.findAllByDeletedFalse(); } } From 3c25ae9be510f5ea3dd9c593de2f296650d35158 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 01:32:37 +0900 Subject: [PATCH 18/26] =?UTF-8?q?refactor(search):=20=EB=B4=89=EC=82=AC?= =?UTF-8?q?=ED=99=9C=EB=8F=99=20=EB=AA=A8=EC=A7=91=EA=B8=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=BF=BC=EB=A6=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존) elastic search에서 검색 결과 글 id 반환 -> id로 rdb에서 검색 후 반환 수정) elastic search 검색 결과인 recruitBoardDocument 전체 반환 - 근처 모집글 검색의 경우 elastic search의 geo_point로 매핑하여 검색하도록 리팩토링 - 유사어 검색 쿼리 추가 --- .../RecruitBoardDocumentRepository.java | 6 - .../repository/SearchBoardRepository.java | 8 +- .../repository/SearchBoardRepositoryImpl.java | 326 +++++++++--------- .../service/RecruitBoardDocumentService.java | 11 +- 4 files changed, 170 insertions(+), 181 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java index 6de3e3131..3e121204e 100644 --- a/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java @@ -2,14 +2,8 @@ import com.somemore.domains.search.domain.RecruitBoardDocument; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import java.util.List; - @ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") public interface RecruitBoardDocumentRepository extends ElasticsearchRepository { - List findAll(); - @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}") - List findIdsByTitleOrContentContaining(String keyword); } diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java index 796438631..9df92e97c 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java @@ -4,9 +4,8 @@ import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.search.domain.CommunityBoardDocument; +import com.somemore.domains.search.domain.RecruitBoardDocument; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -14,12 +13,11 @@ public interface SearchBoardRepository { - Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); - Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); + Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); + Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); void saveRecruitBoardDocuments(List recruitBoards); void deleteRecruitBoardDocument(Long id); - Page findByCommunityBoardsContaining(String keyword, Pageable pageable); void saveCommunityBoardDocuments(List communityBoards); void deleteAllCommunityBoardDocument(); diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index fad374b34..dcbe6ca56 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -1,129 +1,73 @@ package com.somemore.domains.search.repository; -import com.querydsl.core.types.ConstructorExpression; -import com.querydsl.core.types.OrderSpecifier; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; -import com.somemore.domains.center.domain.QCenter; +import co.elastic.clients.elasticsearch._types.GeoLocation; +import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders; +import com.somemore.domains.center.usecase.query.CenterQueryUseCase; import com.somemore.domains.community.domain.CommunityBoard; -import com.somemore.domains.location.domain.QLocation; -import com.somemore.domains.location.utils.GeoUtils; -import com.somemore.domains.recruitboard.domain.QRecruitBoard; +import com.somemore.domains.location.domain.Location; +import com.somemore.domains.location.usecase.query.LocationQueryUseCase; import com.somemore.domains.recruitboard.domain.RecruitBoard; -import com.somemore.domains.recruitboard.domain.RecruitStatus; -import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.volunteer.usecase.VolunteerQueryUseCase; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; +import org.springframework.data.elasticsearch.client.elc.NativeQuery; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery; +import org.springframework.data.elasticsearch.core.ElasticsearchOperations; +import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; import java.util.ArrayList; import java.util.List; +import java.util.UUID; @RequiredArgsConstructor @Repository @ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) public class SearchBoardRepositoryImpl implements SearchBoardRepository { + private final ElasticsearchOperations elasticsearchOperations; + private final RecruitBoardDocumentRepository recruitBoardDocumentRepository; private final CommunityBoardDocumentRepository communityBoardDocumentRepository; - private final JPAQueryFactory queryFactory; private final VolunteerQueryUseCase volunteerQueryUseCase; - - private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; - private static final QLocation location = QLocation.location; - private static final QCenter center = QCenter.center; + private final CenterQueryUseCase centerQueryUseCase; + private final LocationQueryUseCase locationQueryUseCase; @Override - public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { - - List boardDocuments = getRecruitBoardDocuments(condition.keyword()); - - List boardIds = boardDocuments.stream() - .map(RecruitBoardDocument::getId) - .toList(); - - Pageable pageable = condition.pageable(); - BooleanExpression predicate = isNotDeletedRecruitBoard() - .and(volunteerCategoryEq(condition.category())) - .and(regionEq(condition.region())) - .and(admittedEq(condition.admitted())) - .and(statusEq(condition.status())); - - List content = queryFactory - .select(getRecruitBoardWithCenterConstructorExpression()) - .from(recruitBoard) - .where(recruitBoard.id.in(boardIds) - .and(predicate)) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); - - JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .where(recruitBoard.id.in(boardIds) - .and(predicate)); - - return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { + NativeQuery searchQuery = getRecruitBoardWithSearchCondition(condition); + + List boardDocuments = + elasticsearchOperations.search(searchQuery, RecruitBoardDocument.class) + .stream() + .map(SearchHit::getContent) + .toList(); + + return PageableExecutionUtils.getPage(boardDocuments, condition.pageable(), boardDocuments::size); } @Override - public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { - QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; - QLocation location = QLocation.location; - QCenter center = QCenter.center; - - List boardDocuments = getRecruitBoardDocuments(condition.keyword()); - - List boardIds = boardDocuments.stream() - .map(RecruitBoardDocument::getId) - .toList(); - - Pageable pageable = condition.pageable(); - - BooleanExpression predicate = locationBetween(condition) - .and(statusEq(condition.status())) - .and(isNotDeletedRecruitBoard()); - - List content = queryFactory - .select(getRecruitBoardDetailConstructorExpression()) - .from(recruitBoard) - .join(location).on(recruitBoard.locationId.eq(location.id)) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .where(recruitBoard.id.in(boardIds) - .and(predicate)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); - - JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .join(location).on(recruitBoard.locationId.eq(location.id)) - .join(center).on(recruitBoard.centerId.eq(center.id)) - .where(recruitBoard.id.in(boardIds) - .and(predicate)); - - return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { + NativeQuery searchQuery = getRecruitBoardWithNearByCondition(condition); + + System.out.println("Native Query : " + searchQuery.getQuery()); + + List boardDocuments = + elasticsearchOperations.search(searchQuery, RecruitBoardDocument.class) + .stream() + .map(SearchHit::getContent) + .toList(); + + return PageableExecutionUtils.getPage(boardDocuments, condition.pageable(), boardDocuments::size); } @Override @@ -158,15 +102,32 @@ private List convertRecruitBoardToDocuments(List communityBoardDocuments = new ArrayList<>(); for (RecruitBoard recruitBoard : recruitBoards) { + UUID centerId = recruitBoard.getCenterId(); + String centerName = centerQueryUseCase.getNameById(centerId); + + Long locationId = recruitBoard.getLocationId(); + Location location = locationQueryUseCase.getById(locationId); + RecruitBoardDocument document = RecruitBoardDocument.builder() .id(recruitBoard.getId()) - .centerId(recruitBoard.getCenterId()) - .locationId(recruitBoard.getLocationId()) .title(recruitBoard.getTitle()) .content(recruitBoard.getContent()) + .centerName(centerName) + .locationId(locationId) .createdAt(recruitBoard.getCreatedAt()) .updatedAt(recruitBoard.getUpdatedAt()) - .deleted(recruitBoard.getDeleted()) + .region(recruitBoard.getRecruitmentInfo().getRegion()) + .recruitStatus(recruitBoard.getRecruitStatus()) + .recruitmentCount(recruitBoard.getRecruitmentInfo().getRecruitmentCount()) + .volunteerStartDateTime(recruitBoard.getRecruitmentInfo().getVolunteerStartDateTime()) + .volunteerEndDateTime(recruitBoard.getRecruitmentInfo().getVolunteerEndDateTime()) + .volunteerCategory(recruitBoard.getRecruitmentInfo().getVolunteerCategory()) + .volunteerHours(recruitBoard.getRecruitmentInfo().getVolunteerHours()) + .admitted(recruitBoard.getRecruitmentInfo().getAdmitted()) + .centerId(centerId) + .address(location.getAddress()) + .location(new double[]{location.getLongitude().doubleValue(), + location.getLatitude().doubleValue()}) .build(); communityBoardDocuments.add(document); } @@ -192,79 +153,116 @@ private List convertCommunityBoardToDocuments(List mustQueries = new ArrayList<>(); + List shouldQueries = new ArrayList<>(); + + if (condition.category() != null) { + mustQueries.add(QueryBuilders.term() + .field("volunteerCategory") + .value(condition.category().toString()) + .build() + ._toQuery()); + } + if (condition.region() != null && !condition.region().isEmpty()) { + mustQueries.add(QueryBuilders.term() + .field("region") + .value(condition.region()) + .build() + ._toQuery()); + } + if (condition.admitted() != null) { + mustQueries.add(QueryBuilders.term() + .field("admitted") + .value(condition.admitted()) + .build() + ._toQuery()); + } + if (condition.status() != null) { + mustQueries.add(QueryBuilders.term() + .field("recruitStatus") + .value(condition.status().toString()) + .build() + ._toQuery()); + } + if (condition.keyword() != null && !condition.keyword().isEmpty()) { + shouldQueries.add( + QueryBuilders.multiMatch() + .fields("title^3", "content^2", "centerName") + .query(condition.keyword()) + .fuzziness("AUTO") + .build() + ._toQuery() + ); + } - private OrderSpecifier[] toOrderSpecifiers(Sort sort) { - return sort.stream() - .map(order -> { - String property = order.getProperty(); - if ("created_at".equals(property)) { - return order.isAscending() - ? recruitBoard.createdAt.asc() - : recruitBoard.createdAt.desc(); - } - if ("volunteer_start_date_time".equals(property)) { - return order.isAscending() - ? recruitBoard.recruitmentInfo.volunteerStartDateTime.asc() - : recruitBoard.recruitmentInfo.volunteerStartDateTime.desc(); - } - throw new IllegalStateException("Invalid sort property: " + property); - }) - .toArray(OrderSpecifier[]::new); - } + if (mustQueries.isEmpty() && shouldQueries.isEmpty()) { + query = QueryBuilders.matchAll().build()._toQuery(); + } else { + query = new BoolQuery.Builder() + .must(mustQueries) + .should(shouldQueries) + .build() + ._toQuery(); + } - private static ConstructorExpression getRecruitBoardWithCenterConstructorExpression() { - return Projections.constructor(RecruitBoardWithCenter.class, - recruitBoard, center.name); + return NativeQuery.builder() + .withQuery(query) + .build(); } - private static ConstructorExpression getRecruitBoardDetailConstructorExpression() { - return Projections.constructor(RecruitBoardDetail.class, - recruitBoard, location.address, location.latitude, location.longitude, center.name); - } - private List getRecruitBoardDocuments(String keyword) { + private NativeQuery getRecruitBoardWithNearByCondition(RecruitBoardNearByCondition condition) { + Query query; + List mustQueries = new ArrayList<>(); + List shouldQueries = new ArrayList<>(); + + if (condition.latitude() != null && condition.longitude() != null && condition.radius() != null) { + String distance = condition.radius() + "km"; + + mustQueries.add(QueryBuilders.geoDistance() + .field("location") + .distance(distance) + .location(GeoLocation.of(builder -> builder + .latlon(latlon -> latlon + .lat(condition.latitude()) + .lon(condition.longitude()) + ) + )) + .build() + ._toQuery()); + } + if (condition.status() != null) { + mustQueries.add(QueryBuilders.term() + .field("recruitStatus") + .value(condition.status().toString()) + .build() + ._toQuery()); + } + if (condition.keyword() != null && !condition.keyword().isEmpty()) { + shouldQueries.add( + QueryBuilders.multiMatch() + .fields("title^3", "content^2", "centerName") + .query(condition.keyword()) + .fuzziness("AUTO") + .build() + ._toQuery() + ); + } - if (keyword == null || keyword.isEmpty()) { - return recruitBoardDocumentRepository.findAll(); + if (mustQueries.isEmpty() && shouldQueries.isEmpty()) { + query = QueryBuilders.matchAll().build()._toQuery(); + } else { + query = new BoolQuery.Builder() + .must(mustQueries) + .should(shouldQueries) + .build() + ._toQuery(); } - return recruitBoardDocumentRepository.findIdsByTitleOrContentContaining(keyword); + + return NativeQuery.builder() + .withQuery(query) + .build(); } private List getCommunityBoardDocuments(String keyword) { @@ -272,6 +270,6 @@ private List getCommunityBoardDocuments(String keyword) if (keyword == null || keyword.isEmpty()) { return communityBoardDocumentRepository.findAll(); } - return communityBoardDocumentRepository.findIdsByTitleOrContentContaining(keyword); + return communityBoardDocumentRepository.findDocumentsByTitleOrContentOrNicknameContaining(keyword); } } diff --git a/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java index 6cd7feea8..b39196d52 100644 --- a/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java +++ b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java @@ -5,8 +5,7 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import lombok.RequiredArgsConstructor; @@ -27,16 +26,16 @@ public class RecruitBoardDocumentService implements RecruitBoardDocumentUseCase @Transactional(readOnly = true) @Override public Page getRecruitBoardBySearch(RecruitBoardSearchCondition condition) { - Page boards = searchBoardRepository.findByRecruitBoardsContaining(condition); - return boards.map(RecruitBoardWithCenterResponseDto::from); + Page boards = searchBoardRepository.findByRecruitBoardsContaining(condition); + return boards.map(RecruitBoardWithCenterResponseDto::fromDocument); } @Transactional(readOnly = true) @Override public Page getRecruitBoardsNearbyWithKeyword( RecruitBoardNearByCondition condition) { - Page boards = searchBoardRepository.findAllNearbyWithKeyword(condition); - return boards.map(RecruitBoardDetailResponseDto::from); + Page boards = searchBoardRepository.findAllNearbyWithKeyword(condition); + return boards.map(RecruitBoardDetailResponseDto::fromDocument); } @Transactional From 0f0861f6fda6001a8b6cdcf97a579daeb2721f0a Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 01:33:33 +0900 Subject: [PATCH 19/26] =?UTF-8?q?test(search):=20scheduler=20test=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../search/scheduler/RecruitBoardUpdateSchedulerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java index 8d3cf9cd3..566b4cd9e 100644 --- a/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java +++ b/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java @@ -3,6 +3,7 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; +import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.support.IntegrationTestSupport; import org.awaitility.Awaitility; @@ -42,7 +43,7 @@ void updateRecruitBoardDocuments() { // when // then - Page recruitBoards = searchBoardRepository + Page recruitBoards = searchBoardRepository .findByRecruitBoardsContaining(condition); Awaitility.await() From 25c637e06666d709734bdba69f3f2e8614678699 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 14:43:40 +0900 Subject: [PATCH 20/26] =?UTF-8?q?refactor(search):=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SearchBoardRepositoryImpl.java | 161 ++++++++---------- 1 file changed, 70 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index dcbe6ca56..b3b8b4e29 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.function.Consumer; @RequiredArgsConstructor @Repository @@ -98,6 +99,14 @@ public void deleteAllCommunityBoardDocument() { communityBoardDocumentRepository.deleteAll(); } + private List getCommunityBoardDocuments(String keyword) { + + if (keyword == null || keyword.isEmpty()) { + return communityBoardDocumentRepository.findAll(); + } + return communityBoardDocumentRepository.findDocumentsByTitleOrContentOrNicknameContaining(keyword); + } + private List convertRecruitBoardToDocuments(List recruitBoards) { List communityBoardDocuments = new ArrayList<>(); @@ -154,122 +163,92 @@ private List convertCommunityBoardToDocuments(List mustQueries = new ArrayList<>(); - List shouldQueries = new ArrayList<>(); + return buildNativeQuery(builder -> { + if (condition.category() != null) { + builder.addMustQuery("volunteerCategory", condition.category().toString()); + } + if (condition.region() != null && !condition.region().isEmpty()) { + builder.addMustQuery("region", condition.region()); + } + if (condition.admitted() != null) { + builder.addMustQuery("admitted", String.valueOf(condition.admitted())); + } + if (condition.status() != null) { + builder.addMustQuery("recruitStatus", condition.status().toString()); + } + if (condition.keyword() != null && !condition.keyword().isEmpty()) { + builder.addShouldQuery(condition.keyword()); + } + }); + } + private NativeQuery getRecruitBoardWithNearByCondition(RecruitBoardNearByCondition condition) { + return buildNativeQuery(builder -> { + if (condition.latitude() != null && condition.longitude() != null && condition.radius() != null) { + builder.addGeoDistanceQuery(condition.latitude(), condition.longitude(), condition.radius()); + } + if (condition.status() != null) { + builder.addMustQuery("recruitStatus", condition.status().toString()); + } + if (condition.keyword() != null && !condition.keyword().isEmpty()) { + builder.addShouldQuery(condition.keyword()); + } + }); + } + private NativeQuery buildNativeQuery(Consumer builderConsumer) { + QueryBuilder builder = new QueryBuilder(); + builderConsumer.accept(builder); + return NativeQuery.builder() + .withQuery(builder.build()) + .build(); + } + private static class QueryBuilder { + private final List mustQueries = new ArrayList<>(); + private final List shouldQueries = new ArrayList<>(); - if (condition.category() != null) { + void addMustQuery(String field, String value) { mustQueries.add(QueryBuilders.term() - .field("volunteerCategory") - .value(condition.category().toString()) + .field(field) + .value(value) .build() ._toQuery()); } - if (condition.region() != null && !condition.region().isEmpty()) { - mustQueries.add(QueryBuilders.term() - .field("region") - .value(condition.region()) - .build() - ._toQuery()); - } - if (condition.admitted() != null) { - mustQueries.add(QueryBuilders.term() - .field("admitted") - .value(condition.admitted()) - .build() - ._toQuery()); - } - if (condition.status() != null) { - mustQueries.add(QueryBuilders.term() - .field("recruitStatus") - .value(condition.status().toString()) - .build() - ._toQuery()); - } - if (condition.keyword() != null && !condition.keyword().isEmpty()) { + + void addShouldQuery(String keyword) { shouldQueries.add( QueryBuilders.multiMatch() .fields("title^3", "content^2", "centerName") - .query(condition.keyword()) + .query(keyword) .fuzziness("AUTO") .build() ._toQuery() ); } - if (mustQueries.isEmpty() && shouldQueries.isEmpty()) { - query = QueryBuilders.matchAll().build()._toQuery(); - } else { - query = new BoolQuery.Builder() - .must(mustQueries) - .should(shouldQueries) - .build() - ._toQuery(); - } - - return NativeQuery.builder() - .withQuery(query) - .build(); - } - - private NativeQuery getRecruitBoardWithNearByCondition(RecruitBoardNearByCondition condition) { - Query query; - List mustQueries = new ArrayList<>(); - List shouldQueries = new ArrayList<>(); - - if (condition.latitude() != null && condition.longitude() != null && condition.radius() != null) { - String distance = condition.radius() + "km"; - + void addGeoDistanceQuery(double latitude, double longitude, double radius) { + String distance = radius + "km"; mustQueries.add(QueryBuilders.geoDistance() .field("location") .distance(distance) .location(GeoLocation.of(builder -> builder .latlon(latlon -> latlon - .lat(condition.latitude()) - .lon(condition.longitude()) + .lat(latitude) + .lon(longitude) ) )) .build() ._toQuery()); } - if (condition.status() != null) { - mustQueries.add(QueryBuilders.term() - .field("recruitStatus") - .value(condition.status().toString()) - .build() - ._toQuery()); - } - if (condition.keyword() != null && !condition.keyword().isEmpty()) { - shouldQueries.add( - QueryBuilders.multiMatch() - .fields("title^3", "content^2", "centerName") - .query(condition.keyword()) - .fuzziness("AUTO") - .build() - ._toQuery() - ); - } - if (mustQueries.isEmpty() && shouldQueries.isEmpty()) { - query = QueryBuilders.matchAll().build()._toQuery(); - } else { - query = new BoolQuery.Builder() - .must(mustQueries) - .should(shouldQueries) - .build() - ._toQuery(); + Query build() { + if (mustQueries.isEmpty() && shouldQueries.isEmpty()) { + return QueryBuilders.matchAll().build()._toQuery(); + } else { + return new BoolQuery.Builder() + .must(mustQueries) + .should(shouldQueries) + .build() + ._toQuery(); + } } - - return NativeQuery.builder() - .withQuery(query) - .build(); - } - - private List getCommunityBoardDocuments(String keyword) { - - if (keyword == null || keyword.isEmpty()) { - return communityBoardDocumentRepository.findAll(); - } - return communityBoardDocumentRepository.findDocumentsByTitleOrContentOrNicknameContaining(keyword); } } From 568805baf754e5b2cd352ae3fabc9a6900d5fc5b Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 15:25:31 +0900 Subject: [PATCH 21/26] =?UTF-8?q?refactor(search):=20=EB=AF=B8=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=BD=94=EB=93=9C=20recruitboard=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 기관 모집글 조회 RecruitBoardQueryApiController로 이동 (추후 개발 후 다시 이동) --- .../RecruitBoardQueryApiController.java | 27 +++++++++++++++++ .../RecruitBoardSearchApiController.java | 29 +------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java index 37f90efbc..f16b257d3 100644 --- a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java +++ b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardQueryApiController.java @@ -63,6 +63,33 @@ public ApiResponse> getAll( ); } + @GetMapping("/recruit-boards/center/{centerId}") + @Operation(summary = "특정 기관 모집글 조회", description = "특정 기관의 봉사 모집글을 조회합니다.") + public ApiResponse> getRecruitBoardsByCenterId( + @PathVariable UUID centerId, + @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) VolunteerCategory category, + @RequestParam(required = false) String region, + @RequestParam(required = false) Boolean admitted, + @RequestParam(required = false) RecruitStatus status + ) { + RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() + .keyword(keyword) + .category(category) + .region(region) + .admitted(admitted) + .status(status) + .pageable(pageable) + .build(); + + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getRecruitBoardsByCenterId(centerId, condition), + "특정 기관 봉사 활동 모집글 조회 성공" + ); + } + @Secured("ROLE_CENTER") @GetMapping("/recruit-boards/me") @Operation(summary = "기관이 작성한 모집글 조회", description = "기관의 봉사 모집글을 조회합니다.") diff --git a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java index c748bd238..3aa991272 100644 --- a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java +++ b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java @@ -5,7 +5,6 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; -import com.somemore.domains.recruitboard.dto.response.RecruitBoardResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; import com.somemore.domains.search.config.ElasticsearchHealthChecker; @@ -20,7 +19,6 @@ import org.springframework.web.bind.annotation.*; import java.util.Optional; -import java.util.UUID; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -102,30 +100,5 @@ public ApiResponse> getNearbyBySearch( } } - @GetMapping("/recruit-boards/center/{centerId}") - @Operation(summary = "특정 기관 모집글 조회", description = "특정 기관의 봉사 모집글을 조회합니다.") - public ApiResponse> getRecruitBoardsByCenterId( - @PathVariable UUID centerId, - @PageableDefault(sort = "created_at", direction = DESC) Pageable pageable, - @RequestParam(required = false) String keyword, - @RequestParam(required = false) VolunteerCategory category, - @RequestParam(required = false) String region, - @RequestParam(required = false) Boolean admitted, - @RequestParam(required = false) RecruitStatus status - ) { - RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .keyword(keyword) - .category(category) - .region(region) - .admitted(admitted) - .status(status) - .pageable(pageable) - .build(); - - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getRecruitBoardsByCenterId(centerId, condition), - "기관 봉사 활동 모집글 조회 성공" - ); - } + //TODO: 특정 기관 모집글 조회, 기관이 작성한 모집글 조회 추가 } From bdb390ed075769f8d51e1e186b5c555576eb96c9 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 21:22:59 +0900 Subject: [PATCH 22/26] =?UTF-8?q?test(search):=20=EB=B4=89=EC=82=AC?= =?UTF-8?q?=ED=99=9C=EB=8F=99=20=EB=AA=A8=EC=A7=91=EA=B8=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=BF=BC=EB=A6=AC=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81=EC=97=90=20=EB=94=B0=EB=A5=B8=20test=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 --- .../repository/SearchBoardRepositoryTest.java | 17 ++++++++--------- .../RecruitBoardDocumentServiceTest.java | 12 ++++++------ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java index 4ab828c70..020ae4756 100644 --- a/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java +++ b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java @@ -8,9 +8,8 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; -import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.search.domain.CommunityBoardDocument; +import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.volunteer.domain.Volunteer; import com.somemore.domains.volunteer.repository.VolunteerRepository; import com.somemore.global.auth.oauth.domain.OAuthProvider; @@ -172,7 +171,7 @@ void saveRecruitBoardDocuments() { searchBoardRepository.saveRecruitBoardDocuments(recruitBoards); //then - Page findBoard = searchBoardRepository.findByRecruitBoardsContaining(condition); + Page findBoard = searchBoardRepository.findByRecruitBoardsContaining(condition); assertThat(findBoard).isNotNull(); assertThat(findBoard.getTotalElements()).isEqualTo(2); @@ -192,7 +191,7 @@ void findByRecruitBoardsContaining() { .build(); //when - Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); + Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); //then assertThat(findBoards).isNotNull(); @@ -212,7 +211,7 @@ void findByRecruitBoardsContainingWithNull() { .build(); //when - Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); + Page findBoards = searchBoardRepository.findByRecruitBoardsContaining(condition); //then assertThat(findBoards).isNotNull(); @@ -236,7 +235,7 @@ void findAllNearByLocationContaining() { .build(); // when - Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); + Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); // then assertThat(result).isNotNull(); @@ -252,14 +251,14 @@ void findAllNearByLocationContainingWithNull() { RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() .keyword(null) - .latitude(37.5935) - .longitude(126.9780) + .latitude(37.64598908) + .longitude(127.00640578) .radius(5.0) .pageable(pageable) .build(); // when - Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); + Page result = searchBoardRepository.findAllNearbyWithKeyword(condition); // then assertThat(result).isNotNull(); diff --git a/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java b/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java index 60068c21f..af523e978 100644 --- a/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java +++ b/src/test/java/com/somemore/domains/search/service/RecruitBoardDocumentServiceTest.java @@ -74,7 +74,7 @@ void getRecruitBoardBySearch() { //given Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .keyword("노인") + .keyword("강북") .pageable(pageable) .build(); @@ -105,9 +105,9 @@ void getRecruitBoardBySearchWithNull() { //then assertThat(dtos).isNotNull(); assertThat(dtos.getContent()).isNotNull(); - assertThat(dtos.getTotalElements()).isEqualTo(25); + assertThat(dtos.getTotalElements()).isEqualTo(5); assertThat(dtos.getSize()).isEqualTo(5); - assertThat(dtos.getTotalPages()).isEqualTo(5); + assertThat(dtos.getTotalPages()).isEqualTo(1); } @DisplayName("위치 기반으로 반경 내에 검색 키워드가 포함된 모집글을 반환한다. (elasticsearch)") @@ -140,8 +140,8 @@ void getRecruitBoardsNearBy() { Pageable pageable = getPageable(); RecruitBoardNearByCondition condition = RecruitBoardNearByCondition.builder() .keyword(null) - .latitude(37.5935) - .longitude(126.9780) + .latitude(37.64598908) + .longitude(127.00640578) .radius(5.0) .pageable(pageable) .build(); @@ -152,7 +152,7 @@ void getRecruitBoardsNearBy() { // then assertThat(result).isNotNull(); - assertThat(result.getTotalElements()).isEqualTo(25); + assertThat(result.getTotalElements()).isEqualTo(5); assertThat(result.getContent()).isNotEmpty(); } From 5166b77db775edbdee8f8db078c20538dce831df Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 22:10:36 +0900 Subject: [PATCH 23/26] =?UTF-8?q?fix(search):=20search=20=EB=94=94?= =?UTF-8?q?=EB=A0=89=ED=86=A0=EB=A6=AC=20=EC=BB=A4=EB=B2=84=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3f0cfaaff..a776ba54a 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,8 @@ def jacocoExcludePatterns = [ '**/event/**', '**/*Aspect*', '**/Aspect/*', - '**/utils/*' + '**/utils/*', + '**/search/**' ] def jacocoExcludePatternsForVerify = [ @@ -161,7 +162,8 @@ def jacocoExcludePatternsForVerify = [ '*.*event*.*', '*.*Aspect*', '*.Aspect.*', - '*.utils.*' + '*.utils.*', + '*.search.*' ] jacocoTestReport { From 6fa72c5207ef2954cac916f4dce179ffb31c7870 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Mon, 27 Jan 2025 22:17:44 +0900 Subject: [PATCH 24/26] =?UTF-8?q?refactor(search):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domains/search/repository/SearchBoardRepositoryImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index b3b8b4e29..dadadc097 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -60,8 +60,6 @@ public Page findByRecruitBoardsContaining(RecruitBoardSear public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { NativeQuery searchQuery = getRecruitBoardWithNearByCondition(condition); - System.out.println("Native Query : " + searchQuery.getQuery()); - List boardDocuments = elasticsearchOperations.search(searchQuery, RecruitBoardDocument.class) .stream() From 290f2462689bceea93dbddb6e042b692a50bb1e0 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Tue, 4 Feb 2025 23:45:11 +0900 Subject: [PATCH 25/26] =?UTF-8?q?refactor(search):=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EC=82=AC=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - findDynamicField -> findDynamicFieldByCenterId 네이밍 수정 - recruitboard 조회 쿼리문 형식 수정 - elastic search 활성화 확인 어노테이션 생성 및 적용 - 검색 컨트롤러 else 제거 - elastic search 저장 scheduler 로직 순서 수정 (활성화 체크 우선) - --- .../center/CenterRepositoryImpl.java | 4 ++-- .../ConditionalOnElasticSearchEnabled.java | 18 ++++++++++++++ .../OnElasticSearchEnabledCondition.java | 20 ++++++++++++++++ .../search/config/ElasticsearchConfig.java | 4 ++-- .../CommunityBoardSearchApiController.java | 6 ++--- .../RecruitBoardSearchApiController.java | 24 +++++++++---------- .../CommunityBoardDocumentRepository.java | 14 ++++++++--- .../RecruitBoardDocumentRepository.java | 4 ++-- .../repository/SearchBoardRepositoryImpl.java | 4 ++-- .../CommunityBoardUpdateScheduler.java | 9 ++++--- .../RecruitBoardUpdateScheduler.java | 7 +++--- .../CommunityBoardDocumentService.java | 4 ++-- .../service/RecruitBoardDocumentService.java | 4 ++-- 13 files changed, 83 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/somemore/domains/search/annotation/ConditionalOnElasticSearchEnabled.java create mode 100644 src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java diff --git a/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java b/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java index 2e57bccbf..9e6f9a7d3 100644 --- a/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/center/repository/center/CenterRepositoryImpl.java @@ -69,7 +69,7 @@ public void deleteAllInBatch() { @Override public String findNameById(UUID id) { - return findDynamicField(id, center.name) + return findDynamicFieldByCenterId(id, center.name) .orElse(null); } @@ -77,7 +77,7 @@ private static BooleanExpression isNotDeleted() { return center.deleted.isFalse(); } - private Optional findDynamicField(UUID id, Path field) { + private Optional findDynamicFieldByCenterId(UUID id, Path field) { return Optional.ofNullable( queryFactory diff --git a/src/main/java/com/somemore/domains/search/annotation/ConditionalOnElasticSearchEnabled.java b/src/main/java/com/somemore/domains/search/annotation/ConditionalOnElasticSearchEnabled.java new file mode 100644 index 000000000..84c703b03 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/annotation/ConditionalOnElasticSearchEnabled.java @@ -0,0 +1,18 @@ +package com.somemore.domains.search.annotation; + +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Documented +@Conditional(OnElasticSearchEnabledCondition.class) +public @interface ConditionalOnElasticSearchEnabled { + String propertyName() default "elastic.search.enabled"; + String havingValue() default "true"; +} \ No newline at end of file diff --git a/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java b/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java new file mode 100644 index 000000000..8efa2a21a --- /dev/null +++ b/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java @@ -0,0 +1,20 @@ +package com.somemore.domains.search.annotation; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.util.MultiValueMap; + +public class OnElasticSearchEnabledCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + MultiValueMap attrs = metadata.getAllAnnotationAttributes( + ConditionalOnElasticSearchEnabled.class.getName()); + String propertyName = (String) attrs.getFirst("propertyName"); + String havingValue = (String) attrs.getFirst("havingValue"); + + String propertyValue = context.getEnvironment().getProperty(propertyName); + return havingValue.equals(propertyValue); + } +} \ No newline at end of file diff --git a/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java b/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java index b80f34c2a..0a1df62a3 100644 --- a/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java +++ b/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java @@ -1,13 +1,13 @@ package com.somemore.domains.search.config; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.client.ClientConfiguration; import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration; @Configuration -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class ElasticsearchConfig extends ElasticsearchConfiguration { @Value("${elastic.search.uri}") private String uri; diff --git a/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java index e080b4c4e..1560cceb5 100644 --- a/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java +++ b/src/main/java/com/somemore/domains/search/controller/CommunityBoardSearchApiController.java @@ -39,12 +39,12 @@ public ApiResponse> getCommunityBoardsBySearch( communityBoardDocumentUseCase.get().getCommunityBoardBySearch(keyword, pageable.getPageNumber()), "커뮤니티 게시글 검색 리스트 조회 성공" ); - } else { - return ApiResponse.ok( + } + + return ApiResponse.ok( 200, communityBoardQueryUseCase.getCommunityBoards(keyword, pageable.getPageNumber()), "커뮤니티 게시글 검색 리스트 조회 성공" ); - } } } diff --git a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java index 3aa991272..15c74728c 100644 --- a/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java +++ b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java @@ -57,13 +57,13 @@ public ApiResponse> getAllBySearch( recruitBoardDocumentUseCase.get().getRecruitBoardBySearch(condition), "봉사 활동 모집글 검색 조회 성공" ); - } else { - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getAllWithCenter(condition), - "봉사 활동 모집글 검색 조회 성공" - ); } + + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getAllWithCenter(condition), + "봉사 활동 모집글 검색 조회 성공" + ); } @GetMapping("/recruit-boards/nearby") @@ -91,13 +91,13 @@ public ApiResponse> getNearbyBySearch( recruitBoardDocumentUseCase.get().getRecruitBoardsNearbyWithKeyword(condition), "근처 봉사 활동 모집글 조회 성공" ); - } else { - return ApiResponse.ok( - 200, - recruitBoardQueryUseCase.getRecruitBoardsNearby(condition), - "근처 봉사 활동 모집글 조회 성공" - ); } + + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getRecruitBoardsNearby(condition), + "근처 봉사 활동 모집글 조회 성공" + ); } //TODO: 특정 기관 모집글 조회, 기관이 작성한 모집글 조회 추가 diff --git a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java index 805b2a232..a593608f2 100644 --- a/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -1,15 +1,23 @@ package com.somemore.domains.search.repository; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.domain.CommunityBoardDocument; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import java.util.List; -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") +@ConditionalOnElasticSearchEnabled public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { List findAll(); - @Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content^2\", \"writerNickname\"], \"fuzziness\": \"AUTO\"}}") + @Query(""" + { + "multi_match": { + "query": "?0", + "fields": ["title^3", "content^2", "writerNickname"], + "fuzziness": "AUTO" + } + } + """) List findDocumentsByTitleOrContentOrNicknameContaining(String keyword); } diff --git a/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java index 3e121204e..82a34da91 100644 --- a/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java +++ b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java @@ -1,9 +1,9 @@ package com.somemore.domains.search.repository; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.domain.RecruitBoardDocument; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true") +@ConditionalOnElasticSearchEnabled public interface RecruitBoardDocumentRepository extends ElasticsearchRepository { } diff --git a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java index dadadc097..4c7a129d8 100644 --- a/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -9,12 +9,12 @@ import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.volunteer.usecase.VolunteerQueryUseCase; import lombok.RequiredArgsConstructor; import org.springframework.data.elasticsearch.client.elc.NativeQuery; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import co.elastic.clients.elasticsearch._types.query_dsl.Query; @@ -31,7 +31,7 @@ @RequiredArgsConstructor @Repository -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class SearchBoardRepositoryImpl implements SearchBoardRepository { private final ElasticsearchOperations elasticsearchOperations; diff --git a/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java index a29e0e329..dd629b6be 100644 --- a/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java +++ b/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java @@ -2,10 +2,10 @@ import com.somemore.domains.community.domain.CommunityBoard; import com.somemore.domains.community.usecase.board.CommunityBoardQueryUseCase; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -13,7 +13,7 @@ @Component @RequiredArgsConstructor -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class CommunityBoardUpdateScheduler { private final CommunityBoardQueryUseCase communityBoardQueryUseCase; @@ -22,10 +22,9 @@ public class CommunityBoardUpdateScheduler { @Scheduled(cron = "${spring.schedules.cron.updateCommunityBoardDocuments}") public void updateCommunityBoardDocuments() { - List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); - if (elasticsearchHealthChecker.isElasticsearchRunning()) { + List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java index ee43bc38f..92133f9ac 100644 --- a/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java +++ b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java @@ -2,10 +2,10 @@ import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.config.ElasticsearchHealthChecker; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @@ -13,7 +13,7 @@ @Component @RequiredArgsConstructor -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class RecruitBoardUpdateScheduler { private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; @@ -22,9 +22,8 @@ public class RecruitBoardUpdateScheduler { @Scheduled(cron = "${spring.schedules.cron.updateRecruitBoardDocuments}") public void updateRecruitBoardDocuments() { - List recruitBoards = recruitBoardQueryUseCase.getAllRecruitBoards(); - if (elasticsearchHealthChecker.isElasticsearchRunning()) { + List recruitBoards = recruitBoardQueryUseCase.getAllRecruitBoards(); recruitBoardDocumentUseCase.saveRecruitBoardDocuments(recruitBoards); } } diff --git a/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java index 54e104bc7..23c4029f8 100644 --- a/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java +++ b/src/main/java/com/somemore/domains/search/service/CommunityBoardDocumentService.java @@ -2,11 +2,11 @@ import com.somemore.domains.community.domain.CommunityBoard; import com.somemore.domains.community.dto.response.CommunityBoardResponseDto; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.domain.CommunityBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -17,7 +17,7 @@ @RequiredArgsConstructor @Service -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class CommunityBoardDocumentService implements CommunityBoardDocumentUseCase { private final SearchBoardRepository searchBoardRepository; diff --git a/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java index b39196d52..d4065abd4 100644 --- a/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java +++ b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java @@ -5,11 +5,11 @@ import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.dto.response.RecruitBoardDetailResponseDto; import com.somemore.domains.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; import com.somemore.domains.search.domain.RecruitBoardDocument; import com.somemore.domains.search.repository.SearchBoardRepository; import com.somemore.domains.search.usecase.RecruitBoardDocumentUseCase; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,7 +18,7 @@ @RequiredArgsConstructor @Service -@ConditionalOnProperty(name = "elastic.search.enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnElasticSearchEnabled public class RecruitBoardDocumentService implements RecruitBoardDocumentUseCase { private final SearchBoardRepository searchBoardRepository; From df8c95e98ebb758db8496d25fa677b3d5471a495 Mon Sep 17 00:00:00 2001 From: ayoung-dev Date: Wed, 5 Feb 2025 00:06:22 +0900 Subject: [PATCH 26/26] =?UTF-8?q?refactor(search):=20=EC=86=8C=EB=82=98?= =?UTF-8?q?=ED=81=90=EB=B8=8C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 불필요한 import 제거 - null 처리 추가 --- .../CommunityBoardQueryApiController.java | 1 - .../OnElasticSearchEnabledCondition.java | 16 +++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java b/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java index 2c4ce1437..753d3df7f 100644 --- a/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java +++ b/src/main/java/com/somemore/domains/community/controller/CommunityBoardQueryApiController.java @@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; diff --git a/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java b/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java index 8efa2a21a..27b84a90e 100644 --- a/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java +++ b/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java @@ -3,18 +3,20 @@ import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.util.MultiValueMap; + +import java.util.Objects; public class OnElasticSearchEnabledCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - MultiValueMap attrs = metadata.getAllAnnotationAttributes( - ConditionalOnElasticSearchEnabled.class.getName()); - String propertyName = (String) attrs.getFirst("propertyName"); - String havingValue = (String) attrs.getFirst("havingValue"); + String propertyName = (String) Objects.requireNonNull(metadata.getAnnotationAttributes( + ConditionalOnElasticSearchEnabled.class.getName())).get("propertyName"); + String havingValue = (String) Objects.requireNonNull(metadata.getAnnotationAttributes( + ConditionalOnElasticSearchEnabled.class.getName())).get("havingValue"); String propertyValue = context.getEnvironment().getProperty(propertyName); - return havingValue.equals(propertyValue); + + return havingValue.equals(propertyValue != null ? propertyValue : ""); } -} \ No newline at end of file +}