diff --git a/build.gradle b/build.gradle index c82330d96..eade7e24a 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,8 @@ dependencies { implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3' + //elastic-search + implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch' //test testImplementation 'org.springframework.boot:spring-boot-starter-test' diff --git a/src/main/java/com/somemore/SomemoreApplication.java b/src/main/java/com/somemore/SomemoreApplication.java index dec2413b5..076b1cca3 100644 --- a/src/main/java/com/somemore/SomemoreApplication.java +++ b/src/main/java/com/somemore/SomemoreApplication.java @@ -3,13 +3,14 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableJpaAuditing +@EnableScheduling public class SomemoreApplication { public static void main(String[] args) { SpringApplication.run(SomemoreApplication.class, args); } - } diff --git a/src/main/java/com/somemore/community/controller/CommunityBoardQueryApiController.java b/src/main/java/com/somemore/community/controller/CommunityBoardQueryApiController.java index 18f112412..4af56163f 100644 --- a/src/main/java/com/somemore/community/controller/CommunityBoardQueryApiController.java +++ b/src/main/java/com/somemore/community/controller/CommunityBoardQueryApiController.java @@ -2,6 +2,7 @@ import com.somemore.community.dto.response.CommunityBoardDetailResponseDto; import com.somemore.community.dto.response.CommunityBoardResponseDto; +import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase; import com.somemore.community.usecase.board.CommunityBoardQueryUseCase; import com.somemore.global.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; @@ -20,6 +21,7 @@ public class CommunityBoardQueryApiController { private final CommunityBoardQueryUseCase communityBoardQueryUseCase; + private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; @GetMapping("/community-boards") @Operation(summary = "전체 커뮤니티 게시글 조회", description = "전체 커뮤니티 게시글 목록을 조회합니다.") @@ -46,6 +48,19 @@ 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-board/{id}") @Operation(summary = "커뮤니티 게시글 상세 조회", description = "커뮤니티 게시글의 상세 정보를 조회합니다.") public ApiResponse getById( diff --git a/src/main/java/com/somemore/community/domain/CommunityBoardDocument.java b/src/main/java/com/somemore/community/domain/CommunityBoardDocument.java new file mode 100644 index 000000000..c031e0e58 --- /dev/null +++ b/src/main/java/com/somemore/community/domain/CommunityBoardDocument.java @@ -0,0 +1,29 @@ +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/community/repository/board/CommunityBoardDocumentRepository.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardDocumentRepository.java new file mode 100644 index 000000000..422a79697 --- /dev/null +++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardDocumentRepository.java @@ -0,0 +1,13 @@ +package com.somemore.community.repository.board; + +import com.somemore.community.domain.CommunityBoardDocument; +import org.springframework.data.elasticsearch.annotations.Query; +import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; + +import java.util.List; + +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/community/repository/board/CommunityBoardRepository.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java index e767e837c..3e37727b7 100644 --- a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java +++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -18,4 +19,9 @@ default boolean doesNotExistById(Long id) { return !existsById(id); } void deleteAllInBatch(); + + Page findByCommunityBoardsContaining(String keyword, Pageable pageable); + void saveDocuments(List communityBoards); + List findAll(); + void deleteDocument(Long id); } diff --git a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java index 0450c7630..cbd96e99d 100644 --- a/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/community/repository/board/CommunityBoardRepositoryImpl.java @@ -5,6 +5,7 @@ import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import com.somemore.community.domain.CommunityBoard; +import com.somemore.community.domain.CommunityBoardDocument; import com.somemore.community.repository.mapper.CommunityBoardView; import com.somemore.community.domain.QCommunityBoard; import com.somemore.volunteer.domain.QVolunteer; @@ -14,6 +15,7 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -24,6 +26,7 @@ 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; @@ -83,6 +86,52 @@ 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(); + } + private JPAQuery getCommunityBoardsQuery() { return queryFactory .select(Projections.constructor(CommunityBoardView.class, @@ -93,10 +142,18 @@ private JPAQuery getCommunityBoardsQuery() { .orderBy(communityBoard.createdAt.desc()); } - - @Override - public void deleteAllInBatch() { - communityBoardJpaRepository.deleteAllInBatch(); + 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; } private BooleanExpression isNotDeleted() { @@ -104,4 +161,12 @@ private BooleanExpression isNotDeleted() { } private BooleanExpression isWriter(UUID writerId) {return communityBoard.writerId.eq(writerId); } + + 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/community/scheduler/CommunityScheduler.java b/src/main/java/com/somemore/community/scheduler/CommunityScheduler.java new file mode 100644 index 000000000..8bfcc3aac --- /dev/null +++ b/src/main/java/com/somemore/community/scheduler/CommunityScheduler.java @@ -0,0 +1,25 @@ +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 CommunityScheduler { + + private final CommunityBoardQueryUseCase communityBoardQueryUseCase; + private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + + + @Scheduled(cron = "0 0 0 * * *") + public void updateCommunityBoardDocuments() { + List communityBoards = communityBoardQueryUseCase.getAllCommunityBoards(); + communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards); + } +} diff --git a/src/main/java/com/somemore/community/service/board/CommunityBoardDocumentService.java b/src/main/java/com/somemore/community/service/board/CommunityBoardDocumentService.java new file mode 100644 index 000000000..a53935563 --- /dev/null +++ b/src/main/java/com/somemore/community/service/board/CommunityBoardDocumentService.java @@ -0,0 +1,37 @@ +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/community/service/board/CommunityBoardQueryService.java b/src/main/java/com/somemore/community/service/board/CommunityBoardQueryService.java index 0e516630a..b91d50f22 100644 --- a/src/main/java/com/somemore/community/service/board/CommunityBoardQueryService.java +++ b/src/main/java/com/somemore/community/service/board/CommunityBoardQueryService.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.UUID; import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD; @@ -46,4 +47,9 @@ public CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id) { .orElseThrow(() -> new BadRequestException(NOT_EXISTS_COMMUNITY_BOARD.getMessage())); return CommunityBoardDetailResponseDto.from(board); } + + @Override + public List getAllCommunityBoards() { + return communityBoardRepository.findAll(); + } } diff --git a/src/main/java/com/somemore/community/usecase/board/CommunityBoardDocumentUseCase.java b/src/main/java/com/somemore/community/usecase/board/CommunityBoardDocumentUseCase.java new file mode 100644 index 000000000..86f7ca902 --- /dev/null +++ b/src/main/java/com/somemore/community/usecase/board/CommunityBoardDocumentUseCase.java @@ -0,0 +1,12 @@ +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/community/usecase/board/CommunityBoardQueryUseCase.java b/src/main/java/com/somemore/community/usecase/board/CommunityBoardQueryUseCase.java index 6e1d4c5c9..5a4011f85 100644 --- a/src/main/java/com/somemore/community/usecase/board/CommunityBoardQueryUseCase.java +++ b/src/main/java/com/somemore/community/usecase/board/CommunityBoardQueryUseCase.java @@ -1,13 +1,16 @@ package com.somemore.community.usecase.board; +import com.somemore.community.domain.CommunityBoard; import com.somemore.community.dto.response.CommunityBoardDetailResponseDto; import com.somemore.community.dto.response.CommunityBoardResponseDto; import org.springframework.data.domain.Page; +import java.util.List; import java.util.UUID; public interface CommunityBoardQueryUseCase { Page getCommunityBoards(int page); Page getCommunityBoardsByWriterId(UUID writerId, int page); CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id); + List getAllCommunityBoards(); } diff --git a/src/main/java/com/somemore/global/configure/ElasticsearchConfig.java b/src/main/java/com/somemore/global/configure/ElasticsearchConfig.java new file mode 100644 index 000000000..0a47d6c36 --- /dev/null +++ b/src/main/java/com/somemore/global/configure/ElasticsearchConfig.java @@ -0,0 +1,24 @@ +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/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiController.java b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiController.java new file mode 100644 index 000000000..69902dfa6 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiController.java @@ -0,0 +1,85 @@ +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 RecruitBoardQueryUseCase recruitBoardQueryUseCase; + 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/recruitboard/domain/RecruitBoardDocument.java b/src/main/java/com/somemore/recruitboard/domain/RecruitBoardDocument.java new file mode 100644 index 000000000..0af2d3bb4 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/domain/RecruitBoardDocument.java @@ -0,0 +1,30 @@ +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/recruitboard/repository/RecruitBoardDocumentRepository.java b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardDocumentRepository.java new file mode 100644 index 000000000..25017b8b9 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardDocumentRepository.java @@ -0,0 +1,13 @@ +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/recruitboard/repository/RecruitBoardRepository.java b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java index 1f25c824d..282d926a5 100644 --- a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java +++ b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepository.java @@ -25,9 +25,17 @@ public interface RecruitBoardRepository { Page findAllNearby(RecruitBoardNearByCondition condition); + Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); + + Page findAllByCenterId(UUID centerId, RecruitBoardSearchCondition condition); List findNotCompletedIdsByCenterId(UUID centerId); List findAllByIds(List ids); + + Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); + void saveDocuments(List recruitBoards); + List findAll(); + void deleteDocument(Long id); } diff --git a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java index a2d68ede6..14609a3e4 100644 --- a/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -12,15 +12,14 @@ import com.somemore.center.domain.QCenter; import com.somemore.location.domain.QLocation; import com.somemore.location.utils.GeoUtils; -import com.somemore.recruitboard.domain.QRecruitBoard; -import com.somemore.recruitboard.domain.RecruitBoard; -import com.somemore.recruitboard.domain.RecruitStatus; -import com.somemore.recruitboard.domain.VolunteerCategory; +import com.somemore.recruitboard.domain.*; 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 com.somemore.recruitboard.repository.mapper.RecruitBoardWithLocation; + +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -37,6 +36,7 @@ public class RecruitBoardRepositoryImpl implements RecruitBoardRepository { private final RecruitBoardJpaRepository recruitBoardJpaRepository; + private final RecruitBoardDocumentRepository documentRepository; private final JPAQueryFactory queryFactory; @Override @@ -166,6 +166,47 @@ public Page findAllNearby(RecruitBoardNearByCondition condit return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } + @Override + public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { + QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; + QLocation location = QLocation.location; + QCenter center = QCenter.center; + + List boardDocuments = 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 findAllByCenterId(UUID centerId, RecruitBoardSearchCondition condition) { @@ -196,6 +237,62 @@ public Page findAllByCenterId(UUID centerId, 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 List findAll() { + return recruitBoardJpaRepository.findAll(); + } + + @Override + public void deleteDocument(Long id) { + documentRepository.deleteById(id); + } + private static BooleanExpression idEq(Long id) { return recruitBoard.id.eq(id); } @@ -294,4 +391,25 @@ private static ConstructorExpression getRecruitBoardDetailCo recruitBoard, location.address, location.latitude, location.longitude, center.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/recruitboard/scheduler/RecruitBoardScheduler.java b/src/main/java/com/somemore/recruitboard/scheduler/RecruitBoardScheduler.java new file mode 100644 index 000000000..8f55bac71 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/scheduler/RecruitBoardScheduler.java @@ -0,0 +1,25 @@ +package com.somemore.recruitboard.scheduler; + +import com.somemore.recruitboard.domain.RecruitBoard; +import com.somemore.recruitboard.repository.RecruitBoardRepository; +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 RecruitBoardScheduler { + + private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; + private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase; + + @Scheduled(cron = "0 0 0 * * *") + public void updateRecruitBoardDocuments() { + List recruitBoards = recruitBoardQueryUseCase.getAllRecruitBoards(); + recruitBoardDocumentUseCase.saveRecruitBoardDocuments(recruitBoards); + } +} diff --git a/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardDocumentService.java b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardDocumentService.java new file mode 100644 index 000000000..275320129 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardDocumentService.java @@ -0,0 +1,45 @@ +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/recruitboard/service/query/RecruitBoardQueryService.java b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java index c93420b09..f9f768395 100644 --- a/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java +++ b/src/main/java/com/somemore/recruitboard/service/query/RecruitBoardQueryService.java @@ -86,4 +86,8 @@ public List getAllByIds(List ids) { return recruitBoardRepository.findAllByIds(ids); } + @Override + public List getAllRecruitBoards() { + return recruitBoardRepository.findAll(); + } } diff --git a/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardDocumentUseCase.java b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardDocumentUseCase.java new file mode 100644 index 000000000..477a6da80 --- /dev/null +++ b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardDocumentUseCase.java @@ -0,0 +1,17 @@ +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/recruitboard/usecase/query/RecruitBoardQueryUseCase.java b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java index f940244b0..e355920db 100644 --- a/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java +++ b/src/main/java/com/somemore/recruitboard/usecase/query/RecruitBoardQueryUseCase.java @@ -30,4 +30,7 @@ Page getRecruitBoardsByCenterId(UUID centerId, List getNotCompletedIdsByCenterIds(UUID centerId); List getAllByIds(List ids); + + List getAllRecruitBoards(); + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b350d50cd..8e0844759 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -41,6 +41,10 @@ spring: port: ${REDIS_PORT} password: ${REDIS_PASSWORD} + elasticsearch: + repositories: + enabled: true + security: oauth2: client: @@ -105,3 +109,14 @@ server: default: image: url: ${DEFAULT_IMG_URL} + +elastic: + search: + uri: ${ELASTIC_URI} + username: ${ELASTIC_USERNAME} + password: ${ELASTIC_PASSWORD} + +management: + health: + elasticsearch: + enabled: false diff --git a/src/test/java/com/somemore/common/fixture/CommunityBoardFixture.java b/src/test/java/com/somemore/common/fixture/CommunityBoardFixture.java index a55264dc7..4856e49a9 100644 --- a/src/test/java/com/somemore/common/fixture/CommunityBoardFixture.java +++ b/src/test/java/com/somemore/common/fixture/CommunityBoardFixture.java @@ -39,4 +39,12 @@ public static CommunityBoard createCommunityBoard(String title, UUID writerId) { .writerId(writerId) .build(); } + public static CommunityBoard createCommunityBoard(String title, String content, UUID writerId) { + return CommunityBoard.builder() + .title(title) + .content(content) + .imgUrl(IMG_URL) + .writerId(writerId) + .build(); + } } diff --git a/src/test/java/com/somemore/community/controller/CommunityBoardQueryApiControllerTest.java b/src/test/java/com/somemore/community/controller/CommunityBoardQueryApiControllerTest.java index 74e05b775..cf45a35a7 100644 --- a/src/test/java/com/somemore/community/controller/CommunityBoardQueryApiControllerTest.java +++ b/src/test/java/com/somemore/community/controller/CommunityBoardQueryApiControllerTest.java @@ -11,6 +11,7 @@ import com.somemore.ControllerTestSupport; import com.somemore.community.dto.response.CommunityBoardDetailResponseDto; import com.somemore.community.dto.response.CommunityBoardResponseDto; +import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase; import com.somemore.community.usecase.board.CommunityBoardQueryUseCase; import java.util.Collections; import java.util.UUID; @@ -32,6 +33,9 @@ public class CommunityBoardQueryApiControllerTest extends ControllerTestSupport @MockBean private CommunityBoardQueryUseCase communityBoardQueryUseCase; + @MockBean + private CommunityBoardDocumentUseCase communityBoardDocumentUseCase; + @Test @DisplayName("커뮤니티 게시글 전체 조회 성공") void getAll() throws Exception { @@ -101,4 +105,27 @@ void getById() throws Exception { verify(communityBoardQueryUseCase, times(1)) .getCommunityBoardDetail(any()); } + + @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/community/repository/CommunityBoardDocumentRepositoryTest.java b/src/test/java/com/somemore/community/repository/CommunityBoardDocumentRepositoryTest.java new file mode 100644 index 000000000..a18d78206 --- /dev/null +++ b/src/test/java/com/somemore/community/repository/CommunityBoardDocumentRepositoryTest.java @@ -0,0 +1,79 @@ +package com.somemore.community.repository; + +import com.somemore.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 +public 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/community/repository/CommunityBoardRepositoryTest.java b/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java index 1d7202844..a4949fcd8 100644 --- a/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java +++ b/src/test/java/com/somemore/community/repository/CommunityBoardRepositoryTest.java @@ -18,6 +18,8 @@ import static com.somemore.common.fixture.CommunityBoardFixture.createCommunityBoard; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -138,6 +140,34 @@ void existsById() { assertThat(isExist).isTrue(); } + @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/community/service/board/CommunityBoardDocumentServiceTest.java b/src/test/java/com/somemore/community/service/board/CommunityBoardDocumentServiceTest.java new file mode 100644 index 000000000..e7fb72e41 --- /dev/null +++ b/src/test/java/com/somemore/community/service/board/CommunityBoardDocumentServiceTest.java @@ -0,0 +1,83 @@ +package com.somemore.community.service.board; + +import com.somemore.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; + +public 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/community/service/board/CommunityBoardQueryServiceTest.java b/src/test/java/com/somemore/community/service/board/CommunityBoardQueryServiceTest.java index 44c05fe98..61a9bf4b6 100644 --- a/src/test/java/com/somemore/community/service/board/CommunityBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/community/service/board/CommunityBoardQueryServiceTest.java @@ -21,6 +21,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -43,6 +45,8 @@ class CommunityBoardQueryServiceTest extends IntegrationTestSupport { DeleteCommunityBoardUseCase deleteCommunityBoardUseCase; @Autowired CommunityBoardQueryService communityBoardQueryService; + @Autowired + private CommunityBoardDocumentService communityBoardDocumentService; private UUID writerId1; private Long communityId1; @@ -146,5 +150,30 @@ void getCommunityBoardDetailWithDeletedId() { .isThrownBy(callable) .withMessage(ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD.getMessage()); } + + @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/recruitboard/controller/RecruitBoardQueryApiControllerTest.java b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java index 5a48b9775..a637acf7e 100644 --- a/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java +++ b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardQueryApiControllerTest.java @@ -48,11 +48,11 @@ void getById() throws Exception { // when // then mockMvc.perform(get("/api/recruit-board/{id}", recruitBoardId) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message").value("봉사 활동 모집 상세 조회 성공")); + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("봉사 활동 모집 상세 조회 성공")); verify(recruitBoardQueryUseCase, times(1)).getWithLocationById(recruitBoardId); } @@ -64,19 +64,19 @@ void getAll() throws Exception { Page page = new PageImpl<>(Collections.emptyList()); given(recruitBoardQueryUseCase.getAllWithCenter(any(RecruitBoardSearchCondition.class))) - .willReturn(page); + .willReturn(page); // when // then mockMvc.perform(get("/api/recruit-boards") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(200)) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message").value("봉사 활동 모집글 리스트 조회 성공")); + .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)); + any(RecruitBoardSearchCondition.class)); } @Test @@ -86,21 +86,21 @@ void getAllBySearch() throws Exception { Page page = new PageImpl<>(Collections.emptyList()); given(recruitBoardQueryUseCase.getAllWithCenter(any(RecruitBoardSearchCondition.class))) - .willReturn(page); + .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("봉사 활동 모집글 검색 조회 성공")); + .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)); + any(RecruitBoardSearchCondition.class)); } @Test @@ -109,23 +109,23 @@ void getNearby() throws Exception { // given Page page = new PageImpl<>(Collections.emptyList()); given(recruitBoardQueryUseCase.getRecruitBoardsNearby( - any(RecruitBoardNearByCondition.class) + 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("근처 봉사 활동 모집글 조회 성공")); + .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)); + any(RecruitBoardNearByCondition.class)); } @Test @@ -136,20 +136,20 @@ void getRecruitBoardsByCenterId() throws Exception { Page page = new PageImpl<>(Collections.emptyList()); given(recruitBoardQueryUseCase.getRecruitBoardsByCenterId(eq(centerId), - any(RecruitBoardSearchCondition.class))) - .willReturn(page); + any(RecruitBoardSearchCondition.class))) + .willReturn(page); // when // then mockMvc.perform(get("/api/recruit-boards/center/{centerId}", centerId) - .param("keyword", "volunteer") - .param("category", ADMINISTRATIVE_SUPPORT.name()) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data").exists()) - .andExpect(jsonPath("$.message").value("기관 봉사 활동 모집글 조회 성공")); + .param("keyword", "volunteer") + .param("category", ADMINISTRATIVE_SUPPORT.name()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()) + .andExpect(jsonPath("$.message").value("기관 봉사 활동 모집글 조회 성공")); verify(recruitBoardQueryUseCase, times(1)).getRecruitBoardsByCenterId(eq(centerId), - any(RecruitBoardSearchCondition.class)); + any(RecruitBoardSearchCondition.class)); } -} +} \ No newline at end of file diff --git a/src/test/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiControllerTest.java b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiControllerTest.java new file mode 100644 index 000000000..9f670c1e9 --- /dev/null +++ b/src/test/java/com/somemore/recruitboard/controller/RecruitBoardSearchApiControllerTest.java @@ -0,0 +1,84 @@ +package com.somemore.recruitboard.controller; + +import com.somemore.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/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java new file mode 100644 index 000000000..dec972e8c --- /dev/null +++ b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardDocumentRepositoryTest.java @@ -0,0 +1,126 @@ +package com.somemore.recruitboard.repository; + +import com.somemore.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.RecruitBoardSearchCondition; +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 +public 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/recruitboard/repository/RecruitBoardRepositoryImplTest.java b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java index 1b0268c59..986e91e83 100644 --- a/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java +++ b/src/test/java/com/somemore/recruitboard/repository/RecruitBoardRepositoryImplTest.java @@ -51,6 +51,8 @@ class RecruitBoardRepositoryImplTest extends IntegrationTestSupport { private final List boards = new ArrayList<>(); + private UUID centerId; + @BeforeEach void setUp() { Location location = createLocation(); @@ -58,6 +60,7 @@ void setUp() { Center center = createCenter(); centerRepository.save(center); + centerId = center.getId(); for (int i = 1; i <= 100; i++) { String title = "제목" + i; @@ -426,6 +429,38 @@ void findAllByIds() { assertThat(all).hasSize(3); } + @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 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/recruitboard/service/query/RecruitBoardDocumentServiceTest.java b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardDocumentServiceTest.java new file mode 100644 index 000000000..ffab94dbe --- /dev/null +++ b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardDocumentServiceTest.java @@ -0,0 +1,155 @@ +package com.somemore.recruitboard.service.query; + +import com.somemore.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.RecruitBoardSearchCondition; +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 +public 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/recruitboard/service/query/RecruitBoardQueryServiceTest.java b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java index 7cdcc6b2c..2abc28610 100644 --- a/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/recruitboard/service/query/RecruitBoardQueryServiceTest.java @@ -25,6 +25,8 @@ import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto; import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto; import com.somemore.recruitboard.repository.RecruitBoardRepository; + +import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.BeforeEach; @@ -43,6 +45,9 @@ class RecruitBoardQueryServiceTest extends IntegrationTestSupport { @Autowired private RecruitBoardQueryService recruitBoardQueryService; + @Autowired + private RecruitBoardDocumentService recruitBoardDocumentService; + @Autowired private RecruitBoardRepository recruitBoardRepository; @@ -280,6 +285,41 @@ 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 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 537ef65eb..9e79bdba9 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -33,6 +33,10 @@ spring: port: 6379 password: # 테스트에서는 비밀번호 없이 연결 + elasticsearch: + repositories: + enabled: true + web: locale: ko_KR locale-resolver: fixed @@ -63,3 +67,14 @@ cloud: default: image: url: "" + +elastic: + search: + uri: ec2-43-201-249-131.ap-northeast-2.compute.amazonaws.com:9200 + username: elastic + password: changeme + +management: + health: + elasticsearch: + enabled: false