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 { 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..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 @@ -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 findDynamicFieldByCenterId(id, center.name) + .orElse(null); + } + private static BooleanExpression isNotDeleted() { return center.deleted.isFalse(); } + + private Optional findDynamicFieldByCenterId(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); } 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..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; @@ -24,7 +23,6 @@ public class CommunityBoardQueryApiController { private final CommunityBoardQueryUseCase communityBoardQueryUseCase; -// private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; @GetMapping("/community-boards") @Operation(summary = "전체 커뮤니티 게시글 조회", description = "전체 커뮤니티 게시글 목록을 조회합니다.") @@ -51,32 +49,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/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 35fb501d2..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 @@ -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,13 @@ 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/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/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 d0e5bc988..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 @@ -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); + 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 94a211706..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 @@ -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 findAllByDeletedFalse() { + return communityBoardJpaRepository.findAllByDeletedFalse(); + } 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/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(); } } 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..f16b257d3 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; @@ -65,58 +63,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( 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/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/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/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 abc0078cf..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,14 +32,9 @@ public interface RecruitBoardRepository { List findAllByIds(List ids); - List findAll(); + List findAllByDeletedFalse(); 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..97d8e368f 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -41,7 +41,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; @@ -210,13 +209,13 @@ public List findAllByIds(List ids) { } @Override - public List findAll() { - return recruitBoardJpaRepository.findAll(); + public List findAllByDeletedFalse() { + return recruitBoardJpaRepository.findAllByDeletedFalse(); } @Override public long updateStatusToClosedForDateRange(LocalDateTime startTime, - LocalDateTime endTime) { + LocalDateTime endTime) { return queryFactory.update(recruitBoard) .set(recruitBoard.recruitStatus, CLOSED) .where( @@ -229,7 +228,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 +239,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 +340,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/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(); } } 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/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..27b84a90e --- /dev/null +++ b/src/main/java/com/somemore/domains/search/annotation/OnElasticSearchEnabledCondition.java @@ -0,0 +1,22 @@ +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 java.util.Objects; + +public class OnElasticSearchEnabledCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + 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 != null ? propertyValue : ""); + } +} 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..0a1df62a3 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/config/ElasticsearchConfig.java @@ -0,0 +1,26 @@ +package com.somemore.domains.search.config; + +import com.somemore.domains.search.annotation.ConditionalOnElasticSearchEnabled; +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 +@ConditionalOnElasticSearchEnabled +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..1560cceb5 --- /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()), + "커뮤니티 게시글 검색 리스트 조회 성공" + ); + } + + 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..15c74728c --- /dev/null +++ b/src/main/java/com/somemore/domains/search/controller/RecruitBoardSearchApiController.java @@ -0,0 +1,104 @@ +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.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.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 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), + "봉사 활동 모집글 검색 조회 성공" + ); + } + + 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), + "근처 봉사 활동 모집글 조회 성공" + ); + } + + return ApiResponse.ok( + 200, + recruitBoardQueryUseCase.getRecruitBoardsNearby(condition), + "근처 봉사 활동 모집글 조회 성공" + ); + } + + //TODO: 특정 기관 모집글 조회, 기관이 작성한 모집글 조회 추가 +} 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..011e4005e --- /dev/null +++ b/src/main/java/com/somemore/domains/search/domain/CommunityBoardDocument.java @@ -0,0 +1,44 @@ +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; + +import java.time.LocalDateTime; + +@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; + + @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; + + @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, LocalDateTime updatedAt) { + this.id = id; + this.title = title; + this.content = content; + this.writerNickname = writerNickname; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } +} 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..64b7cd5b4 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/domain/RecruitBoardDocument.java @@ -0,0 +1,101 @@ +package com.somemore.domains.search.domain; + +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") +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; + + @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, 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; + } +} 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..a593608f2 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/CommunityBoardDocumentRepository.java @@ -0,0 +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.data.elasticsearch.annotations.Query; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +@ConditionalOnElasticSearchEnabled +public interface CommunityBoardDocumentRepository extends ElasticsearchRepository { + List findAll(); + @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 new file mode 100644 index 000000000..82a34da91 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/RecruitBoardDocumentRepository.java @@ -0,0 +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.data.elasticsearch.repository.ElasticsearchRepository; + +@ConditionalOnElasticSearchEnabled +public interface RecruitBoardDocumentRepository extends ElasticsearchRepository { +} 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..9df92e97c --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepository.java @@ -0,0 +1,24 @@ +package com.somemore.domains.search.repository; + +import com.somemore.domains.community.domain.CommunityBoard; +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.domain.CommunityBoardDocument; +import com.somemore.domains.search.domain.RecruitBoardDocument; +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 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 new file mode 100644 index 000000000..4c7a129d8 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/repository/SearchBoardRepositoryImpl.java @@ -0,0 +1,252 @@ +package com.somemore.domains.search.repository; + +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.Location; +import com.somemore.domains.location.usecase.query.LocationQueryUseCase; +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.data.domain.Page; +import org.springframework.data.domain.Pageable; +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; +import java.util.function.Consumer; + +@RequiredArgsConstructor +@Repository +@ConditionalOnElasticSearchEnabled +public class SearchBoardRepositoryImpl implements SearchBoardRepository { + + private final ElasticsearchOperations elasticsearchOperations; + + private final RecruitBoardDocumentRepository recruitBoardDocumentRepository; + private final CommunityBoardDocumentRepository communityBoardDocumentRepository; + + private final VolunteerQueryUseCase volunteerQueryUseCase; + private final CenterQueryUseCase centerQueryUseCase; + private final LocationQueryUseCase locationQueryUseCase; + + @Override + 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) { + NativeQuery searchQuery = getRecruitBoardWithNearByCondition(condition); + + List boardDocuments = + elasticsearchOperations.search(searchQuery, RecruitBoardDocument.class) + .stream() + .map(SearchHit::getContent) + .toList(); + + return PageableExecutionUtils.getPage(boardDocuments, condition.pageable(), boardDocuments::size); + } + + @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); + return PageableExecutionUtils.getPage(boardDocuments, pageable, boardDocuments::size); + } + + @Override + public void saveCommunityBoardDocuments(List communityBoards) { + List communityBoardDocuments = convertCommunityBoardToDocuments(communityBoards); + communityBoardDocumentRepository.saveAll(communityBoardDocuments); + } + + @Override + 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<>(); + + 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()) + .title(recruitBoard.getTitle()) + .content(recruitBoard.getContent()) + .centerName(centerName) + .locationId(locationId) + .createdAt(recruitBoard.getCreatedAt()) + .updatedAt(recruitBoard.getUpdatedAt()) + .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); + } + return communityBoardDocuments; + } + + private List convertCommunityBoardToDocuments(List communityBoards) { + 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()) + .updatedAt(communityboard.getUpdatedAt()) + .build(); + communityBoardDocuments.add(document); + } + return communityBoardDocuments; + } + + private NativeQuery getRecruitBoardWithSearchCondition(RecruitBoardSearchCondition condition) { + 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<>(); + + void addMustQuery(String field, String value) { + mustQueries.add(QueryBuilders.term() + .field(field) + .value(value) + .build() + ._toQuery()); + } + + void addShouldQuery(String keyword) { + shouldQueries.add( + QueryBuilders.multiMatch() + .fields("title^3", "content^2", "centerName") + .query(keyword) + .fuzziness("AUTO") + .build() + ._toQuery() + ); + } + + 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(latitude) + .lon(longitude) + ) + )) + .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(); + } + } + } +} 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..dd629b6be --- /dev/null +++ b/src/main/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateScheduler.java @@ -0,0 +1,30 @@ +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.annotation.ConditionalOnElasticSearchEnabled; +import com.somemore.domains.search.config.ElasticsearchHealthChecker; +import com.somemore.domains.search.usecase.CommunityBoardDocumentUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@ConditionalOnElasticSearchEnabled +public class CommunityBoardUpdateScheduler { + + private final CommunityBoardQueryUseCase communityBoardQueryUseCase; + private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @Scheduled(cron = "${spring.schedules.cron.updateCommunityBoardDocuments}") + public void updateCommunityBoardDocuments() { + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); + communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards); + } + } +} 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..92133f9ac --- /dev/null +++ b/src/main/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateScheduler.java @@ -0,0 +1,30 @@ +package com.somemore.domains.search.scheduler; + +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.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +@ConditionalOnElasticSearchEnabled +public class RecruitBoardUpdateScheduler { + + private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; + private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; + private final ElasticsearchHealthChecker elasticsearchHealthChecker; + + @Scheduled(cron = "${spring.schedules.cron.updateRecruitBoardDocuments}") + public void updateRecruitBoardDocuments() { + 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 new file mode 100644 index 000000000..23c4029f8 --- /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.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.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 +@ConditionalOnElasticSearchEnabled +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::fromDocument); + } + + @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..d4065abd4 --- /dev/null +++ b/src/main/java/com/somemore/domains/search/service/RecruitBoardDocumentService.java @@ -0,0 +1,46 @@ +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.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.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Service +@ConditionalOnElasticSearchEnabled +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::fromDocument); + } + + @Transactional(readOnly = true) + @Override + public Page getRecruitBoardsNearbyWithKeyword( + RecruitBoardNearByCondition condition) { + Page boards = searchBoardRepository.findAllNearbyWithKeyword(condition); + return boards.map(RecruitBoardDetailResponseDto::fromDocument); + } + + @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: 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( "기본 기관 이름", 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..15a68337a --- /dev/null +++ b/src/test/java/com/somemore/domains/search/controller/CommunityBoardSearchApiControllerTest.java @@ -0,0 +1,75 @@ +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; +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.context.junit.jupiter.EnabledIf; +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; + + @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()); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(communityBoardDocumentUseCase.getCommunityBoardBySearch(any(), anyInt())) + .willReturn(page); + } else { + 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("커뮤니티 게시글 검색 리스트 조회 성공")); + + 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 new file mode 100644 index 000000000..d97253a47 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/controller/RecruitBoardSearchApiControllerTest.java @@ -0,0 +1,114 @@ +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.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; +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.context.junit.jupiter.EnabledIf; +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; + + @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()); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(recruitBoardDocumentUseCase.getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class))) + .willReturn(page); + } else { + 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("봉사 활동 모집글 검색 조회 성공")); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardBySearch(any(RecruitBoardSearchCondition.class)); + } else { + verify(recruitBoardQueryUseCase, times(1)).getAllWithCenter(any(RecruitBoardSearchCondition.class)); + } + } + + @Test + @DisplayName("위치 기반으로 근처 있는 모집글 페이징 조회할 수 있다.") + void getNearby() throws Exception { + // given + Page page = new PageImpl<>(Collections.emptyList()); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + given(recruitBoardDocumentUseCase.getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class) + )).willReturn(page); + } else { + 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("근처 봉사 활동 모집글 조회 성공")); + + if (elasticsearchHealthChecker.isElasticsearchRunning()) { + verify(recruitBoardDocumentUseCase, times(1)).getRecruitBoardsNearbyWithKeyword( + any(RecruitBoardNearByCondition.class)); + } else { + verify(recruitBoardQueryUseCase, times(1)).getRecruitBoardsNearby( + 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..020ae4756 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/repository/SearchBoardRepositoryTest.java @@ -0,0 +1,272 @@ +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.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.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; +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; +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.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 CenterRepository centerRepository; + @Autowired + private SearchBoardRepository searchBoardRepository; + @Autowired + private VolunteerRepository volunteerRepository; + + private UUID writerId, centerId; + + @BeforeEach + void setUp() { + String oAuthId1 = "example-oauth-id1"; + Volunteer volunteer1 = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); + volunteerRepository.save(volunteer1); + writerId = volunteer1.getId(); + + Center center = createCenter(); + centerRepository.save(center); + centerId = center.getId(); + } + + @AfterEach + void tearDown() { + searchBoardRepository.deleteAllCommunityBoardDocument(); + } + + @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); + } + + @DisplayName("검색 키워드가 포함된 게시글을 페이지로 조회할 수 있다. (elasticsearch)") + @Test + void findByCommunityBoardsContaining() { + //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); + + searchBoardRepository.saveCommunityBoardDocuments(communityBoards); + + //when + Page findBoards = searchBoardRepository.findByCommunityBoardsContaining("검색", pageable); + + //then + 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 findBoards = searchBoardRepository.findByCommunityBoardsContaining(null, pageable); + + //then + assertThat(findBoards).isNotNull(); + assertThat(findBoards.getTotalElements()).isEqualTo(21); + assertThat(findBoards.getTotalPages()).isEqualTo(3); + assertThat(findBoards.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.64598908) + .longitude(127.00640578) + .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..5f95ece38 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/scheduler/CommunityBoardUpdateSchedulerTest.java @@ -0,0 +1,60 @@ +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.domain.CommunityBoardDocument; +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..566b4cd9e --- /dev/null +++ b/src/test/java/com/somemore/domains/search/scheduler/RecruitBoardUpdateSchedulerTest.java @@ -0,0 +1,67 @@ +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.domain.RecruitBoardDocument; +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..0bf054407 --- /dev/null +++ b/src/test/java/com/somemore/domains/search/service/CommunityBoardDocumentServiceTest.java @@ -0,0 +1,122 @@ +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.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; +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 SearchBoardRepository searchBoardRepository; + @Autowired + private CommunityBoardRepository communityBoardRepository; + @Autowired + private VolunteerRepository volunteerRepository; + + private UUID volunteerId; + + @BeforeEach + void setUp() { + List communityBoards = new ArrayList<>(); + + String oAuthId1 = "example-oauth-id1"; + Volunteer volunteer = Volunteer.createDefault(OAuthProvider.NAVER, oAuthId1); + volunteerRepository.save(volunteer); + volunteerId = volunteer.getId(); + + for (int i = 1; i <= 11; i++) { + String title = "제목" + i; + 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() { + //given + //when + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("봉사", 0); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(11); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(2); + } + + @DisplayName("검색 키워드 없이 조회시 전체 게시글을 조회한다. (elasticsearch)") + @Test + void getCommunityBoardBySearchWithNull() { + //given + //when + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("", 0); + + //then + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(22); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(3); + } + + @DisplayName("게시글을 elastic search index에 저장한다. (service)") + @Test + void saveCommunityBoardDocuments() { + //given + CommunityBoard communityBoard = createCommunityBoard("저장 잘 되나요?", "안녕하세요", volunteerId); + CommunityBoard savedBoard = communityBoardRepository.save(communityBoard); + + //when + communityBoardDocumentService.saveCommunityBoardDocuments(List.of(savedBoard)); + + //then + Page dtos = communityBoardDocumentService.getCommunityBoardBySearch("저장", 0); + + assertThat(dtos).isNotNull(); + assertThat(dtos.getContent()).isNotNull(); + assertThat(dtos.getTotalElements()).isEqualTo(1); + assertThat(dtos.getSize()).isEqualTo(10); + assertThat(dtos.getTotalPages()).isEqualTo(1); + } +} 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..af523e978 --- /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(5); + assertThat(dtos.getSize()).isEqualTo(5); + assertThat(dtos.getTotalPages()).isEqualTo(1); + } + + @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.64598908) + .longitude(127.00640578) + .radius(5.0) + .pageable(pageable) + .build(); + + // when + Page result = recruitBoardDocumentService.getRecruitBoardsNearbyWithKeyword( + condition); + + // then + assertThat(result).isNotNull(); + assertThat(result.getTotalElements()).isEqualTo(5); + 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