From df9fdee97cb4461ea42fb2325e1f5d01962eeb5e Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Fri, 11 Oct 2024 15:56:38 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[feat]=20Querydsl=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index 5ae542b7..9a9f417e 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,12 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5') // jackson으로 jwt 파싱 + + // querydsl 관련 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { From fe81866f30f275f58e7f8c5b1673239ebdf17eba Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Fri, 11 Oct 2024 15:57:12 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[feat]=20=EB=8F=99=EC=A0=81=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EB=8B=B4=EB=8B=B9=ED=95=98=EB=8A=94=20Faq?= =?UTF-8?q?SearchRepository=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/repository/FaqRepository.java | 2 +- .../faq/repository/FaqSearchRepository.java | 10 ++++ .../repository/FaqSearchRepositoryImpl.java | 59 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepository.java create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepository.java index 4ab1c63e..d9c6cf4f 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepository.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepository.java @@ -3,7 +3,7 @@ import com.bbteam.budgetbuddies.domain.faq.entity.Faq; import org.springframework.data.jpa.repository.JpaRepository; -public interface FaqRepository extends JpaRepository { +public interface FaqRepository extends JpaRepository, FaqSearchRepository { } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepository.java new file mode 100644 index 00000000..b54ab5be --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepository.java @@ -0,0 +1,10 @@ +package com.bbteam.budgetbuddies.domain.faq.repository; + +import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface FaqSearchRepository { + + Page searchFaq(Pageable pageable, String searchCondition); +} diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java new file mode 100644 index 00000000..81205dbf --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.bbteam.budgetbuddies.domain.faq.repository; + +import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static com.bbteam.budgetbuddies.domain.faq.entity.QFaq.*; +import static com.bbteam.budgetbuddies.domain.faqkeyword.domain.QFaqKeyword.*; + +public class FaqSearchRepositoryImpl implements FaqSearchRepository{ + + private final JPAQueryFactory queryFactory; + + public FaqSearchRepositoryImpl(EntityManager em) { + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public Page searchFaq(Pageable pageable, String searchCondition) { + List result = queryFactory.select(faq) + .from(faq) + .where(faq.in( + JPAExpressions + .select(faqKeyword.faq) + .from(faqKeyword) + .where(keywordMatch(searchCondition)) + )) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long total = queryFactory.select(faq.count()) + .from(faq) + .where(faq.in( + JPAExpressions + .select(faqKeyword.faq) + .from(faqKeyword) + .where(keywordMatch(searchCondition)) + )) + .fetchOne(); + + return new PageImpl<>(result, pageable, total); + + } + + private BooleanExpression keywordMatch(String searchCondition) { + return searchCondition != null ? faqKeyword.searchKeyword.keyword.eq(searchCondition) : null; + } + +} + From fccb9ffb407b63114abc767461805cb935dc1110 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Fri, 11 Oct 2024 16:01:09 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[feat]=20FaqService=20searchFaq=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bbteam/budgetbuddies/domain/faq/service/FaqService.java | 2 ++ .../budgetbuddies/domain/faq/service/FaqServiceImpl.java | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java index eb6a3718..2f276470 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java @@ -11,6 +11,8 @@ public interface FaqService { Page findAllWithPaging(Pageable pageable); + Page searchFaq(Pageable pageable, String searchCondition); + FaqResponseDto.FaqPostResponse postFaq(FaqRequestDto.FaqPostRequest dto, Long userId); FaqResponseDto.FaqModifyResponse modifyFaq(FaqRequestDto.FaqModifyRequest dto, Long faqId); diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java index a28ccba8..70c11286 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java @@ -62,4 +62,9 @@ public String deleteFaq(Long faqId) { private Faq findFaq(Long faqId) { return faqRepository.findById(faqId).orElseThrow(() -> new GeneralException(ErrorStatus._FAQ_NOT_FOUND)); } + + @Override + public Page searchFaq(Pageable pageable, String searchCondition) { + return faqRepository.searchFaq(pageable, searchCondition).map(FaqConverter::entityToFind); + } } From f24d9635ba108b4ee0b10a627e46dce3be8467c8 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Fri, 11 Oct 2024 16:01:59 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[feat]=20FaqApi=20findByPaging=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B0=8F=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bbteam/budgetbuddies/domain/faq/controller/FaqApi.java | 3 ++- .../budgetbuddies/domain/faq/controller/FaqController.java | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java index f3e0b134..06bc6e16 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java @@ -17,6 +17,7 @@ import jakarta.validation.Valid; import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; +import org.springframework.lang.Nullable; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; @@ -70,7 +71,7 @@ ApiResponse postFaq(@ExistUser @RequestParam Long userId, } ) - ApiResponse findByPaging(Pageable pageable); + ApiResponse findByPaging(Pageable pageable, String SearchCondition); @Operation(summary = "[User] FAQ 수정 API", description = "FAQ를 수정하는 API입니다.", requestBody = @RequestBody( diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java index c8da6681..74a6e41f 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java @@ -7,10 +7,12 @@ import com.bbteam.budgetbuddies.domain.faq.validation.ExistFaq; import com.bbteam.budgetbuddies.domain.user.validation.ExistUser; import jakarta.validation.Valid; +import jakarta.validation.constraints.Null; import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.lang.Nullable; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -37,8 +39,9 @@ public ApiResponse findFaq(@PathVariable @ExistF @Override @GetMapping("/all") - public ApiResponse> findByPaging(@ParameterObject Pageable pageable) { - return ApiResponse.onSuccess(faqService.findAllWithPaging(pageable)); + public ApiResponse> findByPaging(@ParameterObject Pageable pageable, + @RequestParam @Nullable String searchCondition) { + return ApiResponse.onSuccess(faqService.searchFaq(pageable, searchCondition)); } @Override From 16a3ee7b13151a7b216c2f6478add5ce9838e9b4 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:17:34 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[feat]=20ErrorStatus=20Faq=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20status=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../budgetbuddies/apiPayload/code/status/ErrorStatus.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/bbteam/budgetbuddies/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/bbteam/budgetbuddies/apiPayload/code/status/ErrorStatus.java index 8c9515b8..e3d44bb8 100644 --- a/src/main/java/com/bbteam/budgetbuddies/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/bbteam/budgetbuddies/apiPayload/code/status/ErrorStatus.java @@ -27,7 +27,9 @@ public enum ErrorStatus implements BaseErrorCode { _OTP_NOT_VALID(HttpStatus.BAD_REQUEST, "OTP4001", "인증번호가 유효하지 않습니다."), _PHONE_NUMBER_NOT_VALID(HttpStatus.BAD_REQUEST, "AUTH4001", "전화번호 형식이 유효하지 않습니다. (예: 01012341234)"), - _FAQ_NOT_FOUND(HttpStatus.NOT_FOUND, "FAQ4004", "해당 FAQ가 존재하지 않습니다."); + _FAQ_NOT_FOUND(HttpStatus.NOT_FOUND, "FAQ4004", "해당 FAQ가 존재하지 않습니다."), + _SEARCH_KEYWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "SEARCH_KEYWORD4004", "해당 SearchKeyword가 존재하지 않습니다."), + _FAQ_KEYWORD_NOT_FOUND(HttpStatus.NOT_FOUND, "FAQ_KEYWORD4004", "해당 FaqKeyword가 존재하지 않습니다."); private HttpStatus httpStatus; From 9772e0f8146e4e4f3bb19fa3b0dacf75d276a1a7 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:18:09 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[feat]=20FAQ,=20SearchKeyword=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../budgetbuddies/domain/faq/controller/FaqApi.java | 5 +++++ .../domain/faq/controller/FaqController.java | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java index 06bc6e16..8485960f 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqApi.java @@ -113,4 +113,9 @@ ApiResponse modifyFaq(@PathVariable @ExistFaq Long faqId, } ) ApiResponse deleteFaq(@ExistFaq Long faqId); + + ApiResponse addKeyword(@ExistFaq Long faqId, Long searchKeywordId); + + ApiResponse deleteKeyword(@ExistFaq Long faqId, Long searchKeywordId); + } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java index 74a6e41f..c15fe5d2 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/controller/FaqController.java @@ -57,4 +57,16 @@ public ApiResponse deleteFaq(@PathVariable @ExistFaq Long faqId) { faqService.deleteFaq(faqId); return ApiResponse.onSuccess("Delete Success"); } + + @Override + @PostMapping("/{faqId}/keyword") + public ApiResponse addKeyword(@PathVariable @ExistFaq Long faqId, @RequestParam Long searchKeywordId) { + return ApiResponse.onSuccess(faqService.addKeyword(faqId, searchKeywordId)); + } + + @Override + @DeleteMapping("/{faqId}/keyword") + public ApiResponse deleteKeyword(@PathVariable @ExistFaq Long faqId, @RequestParam Long searchKeywordId) { + return ApiResponse.onSuccess(faqService.removeKeyword(faqId, searchKeywordId)); + } } From 64e64e362200449bb19906ab09cd9f8518eef33e Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:19:04 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[feat]=20Faq=EC=99=80=20SearchKeyword?= =?UTF-8?q?=EC=9D=98=20=EC=A4=91=EA=B0=84=20mapping=20=EA=B0=9D=EC=B2=B4,?= =?UTF-8?q?=20FaqKeyword=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faqkeyword/domain/FaqKeyword.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/domain/FaqKeyword.java diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/domain/FaqKeyword.java b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/domain/FaqKeyword.java new file mode 100644 index 00000000..6f5e9f14 --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/domain/FaqKeyword.java @@ -0,0 +1,32 @@ +package com.bbteam.budgetbuddies.domain.faqkeyword.domain; + +import com.bbteam.budgetbuddies.common.BaseEntity; +import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.*; +import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@SuperBuilder +public class FaqKeyword extends BaseEntity { + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "faq_id") + @NotFound(action = NotFoundAction.IGNORE) + private Faq faq; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "search_keyword_id") + @NotFound(action = NotFoundAction.IGNORE) + private SearchKeyword searchKeyword; + +} From bd62f553458109e0530ed844940fb4f5859b13bd Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:20:02 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[feat]=20FaqKeyword=20repository,=20dto,?= =?UTF-8?q?=20test=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../faqkeyword/dto/FaqKeywordResponseDto.java | 21 ++++ .../repository/FaqKeywordRepository.java | 12 +++ .../faq/repository/FaqRepositoryTest.java | 101 ++++++++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/dto/FaqKeywordResponseDto.java create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/repository/FaqKeywordRepository.java create mode 100644 src/test/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepositoryTest.java diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/dto/FaqKeywordResponseDto.java b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/dto/FaqKeywordResponseDto.java new file mode 100644 index 00000000..da6ef042 --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/dto/FaqKeywordResponseDto.java @@ -0,0 +1,21 @@ +package com.bbteam.budgetbuddies.domain.faqkeyword.dto; + +import com.bbteam.budgetbuddies.domain.faqkeyword.domain.FaqKeyword; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class FaqKeywordResponseDto { + + private Long faqId; + private Long searchKeywordId; + + private String faqTitle; + private String keyword; + + public static FaqKeywordResponseDto toDto(FaqKeyword faqKeyword) { + return new FaqKeywordResponseDto(faqKeyword.getFaq().getId(), faqKeyword.getSearchKeyword().getId(), + faqKeyword.getFaq().getTitle(), faqKeyword.getSearchKeyword().getKeyword()); + } +} diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/repository/FaqKeywordRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/repository/FaqKeywordRepository.java new file mode 100644 index 00000000..c0606ba0 --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faqkeyword/repository/FaqKeywordRepository.java @@ -0,0 +1,12 @@ +package com.bbteam.budgetbuddies.domain.faqkeyword.repository; + +import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import com.bbteam.budgetbuddies.domain.faqkeyword.domain.FaqKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FaqKeywordRepository extends JpaRepository { + Optional findByFaqAndSearchKeyword(Faq faq, SearchKeyword searchKeyword); +} diff --git a/src/test/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepositoryTest.java b/src/test/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepositoryTest.java new file mode 100644 index 00000000..6263fd38 --- /dev/null +++ b/src/test/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqRepositoryTest.java @@ -0,0 +1,101 @@ +package com.bbteam.budgetbuddies.domain.faq.repository; + +import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import com.bbteam.budgetbuddies.domain.faqkeyword.domain.FaqKeyword; +import com.bbteam.budgetbuddies.domain.faqkeyword.repository.FaqKeywordRepository; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.repository.SearchKeywordRepository; +import com.bbteam.budgetbuddies.domain.user.entity.User; +import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +class FaqRepositoryTest { + @Autowired + FaqRepository faqRepository; + @Autowired + FaqKeywordRepository faqKeywordRepository; + @Autowired + SearchKeywordRepository searchKeywordRepository; + @Autowired + UserRepository userRepository; + + + @Test + void searchTest() { + + User user1 = User.builder() + .name("tester1") + .email("1234") + .age(5) + .phoneNumber("123456") + .mobileCarrier("skt") + .build(); + userRepository.save(user1); + + Faq faq1 = Faq.builder() + .user(user1) + .title("test1") + .body("test1") + .build(); + faqRepository.save(faq1); + + Faq faq2 = Faq.builder() + .user(user1) + .title("test2") + .body("test2") + .build(); + faqRepository.save(faq2); + + SearchKeyword keyword1 = SearchKeyword.builder() + .keyword("러닝") + .build(); + searchKeywordRepository.save(keyword1); + + FaqKeyword faqKeyword = FaqKeyword.builder() + .searchKeyword(keyword1) + .faq(faq1) + .build(); + faqKeywordRepository.save(faqKeyword); + + SearchKeyword keyword2 = SearchKeyword.builder() + .keyword("헬스") + .build(); + searchKeywordRepository.save(keyword2); + + FaqKeyword faqKeyword2 = FaqKeyword.builder() + .searchKeyword(keyword2) + .faq(faq1) + .build(); + faqKeywordRepository.save(faqKeyword2); + + FaqKeyword faqKeyword3 = FaqKeyword.builder() + .searchKeyword(keyword1) + .faq(faq2) + .build(); + faqKeywordRepository.save(faqKeyword3); + + PageRequest pageRequest = PageRequest.of(0, 5); + + Page result1 = faqRepository.searchFaq(pageRequest, "러닝"); + + Page result2 = faqRepository.searchFaq(pageRequest, "헬스"); + + assertThat(result1.getNumberOfElements()).isEqualTo(2); + assertThat(result2.getNumberOfElements()).isEqualTo(1); + + } + + +} \ No newline at end of file From f6166e6dd77ed8f60778de33dd805456f712bab1 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:20:30 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[feat]=20FaqService=20keyword=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/faq/service/FaqService.java | 5 +++ .../domain/faq/service/FaqServiceImpl.java | 34 ++++++++++++++++ .../domain/faq/service/FaqServiceTest.java | 40 +++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java index 2f276470..f334dac5 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqService.java @@ -2,6 +2,7 @@ import com.bbteam.budgetbuddies.domain.faq.dto.FaqRequestDto; import com.bbteam.budgetbuddies.domain.faq.dto.FaqResponseDto; +import com.bbteam.budgetbuddies.domain.faqkeyword.dto.FaqKeywordResponseDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -18,4 +19,8 @@ public interface FaqService { FaqResponseDto.FaqModifyResponse modifyFaq(FaqRequestDto.FaqModifyRequest dto, Long faqId); String deleteFaq(Long faqId); + + FaqKeywordResponseDto addKeyword(Long faqId, Long searchKeywordId); + + String removeKeyword(Long faqId, Long searchKeywordId); } diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java index 70c11286..6b732095 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceImpl.java @@ -7,6 +7,11 @@ import com.bbteam.budgetbuddies.domain.faq.dto.FaqResponseDto; import com.bbteam.budgetbuddies.domain.faq.entity.Faq; import com.bbteam.budgetbuddies.domain.faq.repository.FaqRepository; +import com.bbteam.budgetbuddies.domain.faqkeyword.domain.FaqKeyword; +import com.bbteam.budgetbuddies.domain.faqkeyword.dto.FaqKeywordResponseDto; +import com.bbteam.budgetbuddies.domain.faqkeyword.repository.FaqKeywordRepository; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.repository.SearchKeywordRepository; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -22,6 +27,8 @@ public class FaqServiceImpl implements FaqService{ private final FaqRepository faqRepository; private final UserRepository userRepository; + private final FaqKeywordRepository faqKeywordRepository; + private final SearchKeywordRepository searchKeywordRepository; @Override public FaqResponseDto.FaqFindResponse findOneFaq(Long faqId) { @@ -67,4 +74,31 @@ private Faq findFaq(Long faqId) { public Page searchFaq(Pageable pageable, String searchCondition) { return faqRepository.searchFaq(pageable, searchCondition).map(FaqConverter::entityToFind); } + + @Override + @Transactional + public FaqKeywordResponseDto addKeyword(Long faqId, Long searchKeywordId) { + Faq faq = findFaq(faqId); + SearchKeyword searchKeyword = searchKeywordRepository.findById(searchKeywordId).orElseThrow(() -> new GeneralException(ErrorStatus._SEARCH_KEYWORD_NOT_FOUND)); + + FaqKeyword faqKeyword = FaqKeyword.builder() + .searchKeyword(searchKeyword) + .faq(faq) + .build(); + + faqKeywordRepository.save(faqKeyword); + return FaqKeywordResponseDto.toDto(faqKeyword); + } + + @Override + @Transactional + public String removeKeyword(Long faqId, Long searchKeywordId) { + Faq faq = findFaq(faqId); + SearchKeyword searchKeyword = searchKeywordRepository.findById(searchKeywordId).orElseThrow(() -> new GeneralException(ErrorStatus._SEARCH_KEYWORD_NOT_FOUND)); + + FaqKeyword faqKeyword = faqKeywordRepository.findByFaqAndSearchKeyword(faq, searchKeyword).orElseThrow(() -> new GeneralException(ErrorStatus._FAQ_KEYWORD_NOT_FOUND)); + faqKeywordRepository.delete(faqKeyword); + + return "ok"; + } } diff --git a/src/test/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceTest.java b/src/test/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceTest.java index 4e895c18..67d5e7b1 100644 --- a/src/test/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceTest.java +++ b/src/test/java/com/bbteam/budgetbuddies/domain/faq/service/FaqServiceTest.java @@ -4,6 +4,10 @@ import com.bbteam.budgetbuddies.domain.faq.dto.FaqResponseDto; import com.bbteam.budgetbuddies.domain.faq.entity.Faq; import com.bbteam.budgetbuddies.domain.faq.repository.FaqRepository; +import com.bbteam.budgetbuddies.domain.faqkeyword.dto.FaqKeywordResponseDto; +import com.bbteam.budgetbuddies.domain.faqkeyword.repository.FaqKeywordRepository; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.repository.SearchKeywordRepository; import com.bbteam.budgetbuddies.domain.user.entity.User; import com.bbteam.budgetbuddies.domain.user.repository.UserRepository; import org.assertj.core.api.Assertions; @@ -14,6 +18,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -31,6 +36,10 @@ class FaqServiceTest { FaqRepository faqRepository; @Autowired UserRepository userRepository; + @Autowired + FaqKeywordRepository faqKeywordRepository; + @Autowired + SearchKeywordRepository searchKeywordRepository; static Long userId; static Long faqId; @@ -149,4 +158,35 @@ void deleteFaq() { assertThat(faq.isEmpty()).isTrue(); } + + @Test + void addKeywordAndRemoveKeyword() { + User user = userRepository.findById(userId).get(); + + Faq faq1 = Faq.builder() + .title("test1") + .body("test1") + .user(user) + .build(); + faqRepository.save(faq1); + + SearchKeyword searchKeyword = SearchKeyword.builder() + .keyword("testKeyword") + .build(); + searchKeywordRepository.save(searchKeyword); + faqService.addKeyword(faq1.getId(), searchKeyword.getId()); + PageRequest pageRequest = PageRequest.of(0, 1); + + Page result1 = faqService.searchFaq(pageRequest, "test"); + Assertions.assertThat(result1.getNumberOfElements()).isEqualTo(1); + + Page result2 = faqService.searchFaq(pageRequest, "no"); + Assertions.assertThat(result2.getNumberOfElements()).isEqualTo(0); + + faqService.removeKeyword(faq1.getId(), searchKeyword.getId()); + Page result3 = faqService.searchFaq(pageRequest, "test"); + Assertions.assertThat(result3.getNumberOfElements()).isEqualTo(0); + + + } } \ No newline at end of file From f00df26c03493636d385d24a95e236a62644cb4c Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:20:55 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[feat]=20FaqSearchRepository=EC=9D=98=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=EC=BF=BC=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(QueryDSL)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../faq/repository/FaqSearchRepositoryImpl.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java index 81205dbf..1e3116ab 100644 --- a/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java +++ b/src/main/java/com/bbteam/budgetbuddies/domain/faq/repository/FaqSearchRepositoryImpl.java @@ -1,6 +1,7 @@ package com.bbteam.budgetbuddies.domain.faq.repository; import com.bbteam.budgetbuddies.domain.faq.entity.Faq; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.QSearchKeyword; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQueryFactory; @@ -14,6 +15,7 @@ import static com.bbteam.budgetbuddies.domain.faq.entity.QFaq.*; import static com.bbteam.budgetbuddies.domain.faqkeyword.domain.QFaqKeyword.*; +import static com.bbteam.budgetbuddies.domain.searchkeyword.domain.QSearchKeyword.*; public class FaqSearchRepositoryImpl implements FaqSearchRepository{ @@ -27,11 +29,11 @@ public FaqSearchRepositoryImpl(EntityManager em) { public Page searchFaq(Pageable pageable, String searchCondition) { List result = queryFactory.select(faq) .from(faq) - .where(faq.in( + .where(faq.id.in( JPAExpressions - .select(faqKeyword.faq) + .select(faqKeyword.faq.id) .from(faqKeyword) - .where(keywordMatch(searchCondition)) + .join(searchKeyword).on(keywordMatch(searchCondition)) )) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) @@ -39,11 +41,11 @@ public Page searchFaq(Pageable pageable, String searchCondition) { Long total = queryFactory.select(faq.count()) .from(faq) - .where(faq.in( + .where(faq.id.in( JPAExpressions - .select(faqKeyword.faq) + .select(faqKeyword.faq.id) .from(faqKeyword) - .where(keywordMatch(searchCondition)) + .join(searchKeyword).on(keywordMatch(searchCondition)) )) .fetchOne(); @@ -52,7 +54,7 @@ public Page searchFaq(Pageable pageable, String searchCondition) { } private BooleanExpression keywordMatch(String searchCondition) { - return searchCondition != null ? faqKeyword.searchKeyword.keyword.eq(searchCondition) : null; + return searchCondition != null ? searchKeyword.keyword.contains(searchCondition) : null; } } From d78df08091e6478a4af08a805f714663901cee86 Mon Sep 17 00:00:00 2001 From: wnd01jun Date: Wed, 13 Nov 2024 16:21:35 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[feat]=20SearchKeyword=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SearchKeywordController.java | 48 +++++++++++++++++++ .../searchkeyword/domain/SearchKeyword.java | 23 +++++++++ .../repository/SearchKeywordRepository.java | 7 +++ .../service/SearchKeywordService.java | 44 +++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/controller/SearchKeywordController.java create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/domain/SearchKeyword.java create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/repository/SearchKeywordRepository.java create mode 100644 src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/service/SearchKeywordService.java diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/controller/SearchKeywordController.java b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/controller/SearchKeywordController.java new file mode 100644 index 00000000..45786bbe --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/controller/SearchKeywordController.java @@ -0,0 +1,48 @@ +package com.bbteam.budgetbuddies.domain.searchkeyword.controller; + +import com.bbteam.budgetbuddies.apiPayload.ApiResponse; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.service.SearchKeywordService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/search-keyword") +public class SearchKeywordController { + + private final SearchKeywordService searchKeywordService; + + @PostMapping("") + public ApiResponse saveKeyword(String keyword) { + + return ApiResponse.onSuccess(searchKeywordService.saveKeyword(keyword)); + } + + @GetMapping("") + public ApiResponse findOne(Long searchKeywordId) { + return ApiResponse.onSuccess(searchKeywordService.findOne(searchKeywordId)); + } + + @GetMapping("/all") + public ApiResponse> findAll(Pageable pageable) { + return ApiResponse.onSuccess(searchKeywordService.findAll(pageable)); + } + + @PutMapping("") + public ApiResponse modifyOne(Long searchKeywordId, String newKeyword) { + return ApiResponse.onSuccess((searchKeywordService.modifyOne(searchKeywordId, newKeyword))); + } + + @DeleteMapping("") + public ApiResponse deleteOne(Long searchKeywordId) { + searchKeywordService.deleteOne(searchKeywordId); + return ApiResponse.onSuccess("OK"); + } + + + + +} diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/domain/SearchKeyword.java b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/domain/SearchKeyword.java new file mode 100644 index 00000000..970d72f4 --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/domain/SearchKeyword.java @@ -0,0 +1,23 @@ +package com.bbteam.budgetbuddies.domain.searchkeyword.domain; + +import com.bbteam.budgetbuddies.common.BaseEntity; +import jakarta.persistence.Entity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SearchKeyword extends BaseEntity { + + private String keyword; + + public void changeKeyword(String newKeyword) { + this.keyword = newKeyword; + } + +} diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/repository/SearchKeywordRepository.java b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/repository/SearchKeywordRepository.java new file mode 100644 index 00000000..1fcea8b7 --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/repository/SearchKeywordRepository.java @@ -0,0 +1,7 @@ +package com.bbteam.budgetbuddies.domain.searchkeyword.repository; + +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SearchKeywordRepository extends JpaRepository { +} diff --git a/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/service/SearchKeywordService.java b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/service/SearchKeywordService.java new file mode 100644 index 00000000..b960855d --- /dev/null +++ b/src/main/java/com/bbteam/budgetbuddies/domain/searchkeyword/service/SearchKeywordService.java @@ -0,0 +1,44 @@ +package com.bbteam.budgetbuddies.domain.searchkeyword.service; + +import com.bbteam.budgetbuddies.apiPayload.code.status.ErrorStatus; +import com.bbteam.budgetbuddies.apiPayload.exception.GeneralException; +import com.bbteam.budgetbuddies.domain.searchkeyword.domain.SearchKeyword; +import com.bbteam.budgetbuddies.domain.searchkeyword.repository.SearchKeywordRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class SearchKeywordService { + + private final SearchKeywordRepository searchKeywordRepository; + + public SearchKeyword saveKeyword(String keyword) { + SearchKeyword searchKeyword = SearchKeyword.builder().keyword(keyword).build(); + searchKeywordRepository.save(searchKeyword); + return searchKeyword; + } + + public SearchKeyword findOne(Long searchKeywordId) { + return searchKeywordRepository.findById(searchKeywordId).orElseThrow(() -> new GeneralException(ErrorStatus._SEARCH_KEYWORD_NOT_FOUND)); + } + + public Page findAll(Pageable pageable) { + return searchKeywordRepository.findAll(pageable); + } + + public SearchKeyword modifyOne(Long searchKeywordId, String keyword) { + SearchKeyword searchKeyword = searchKeywordRepository.findById(searchKeywordId).orElseThrow(() -> new GeneralException(ErrorStatus._SEARCH_KEYWORD_NOT_FOUND)); + searchKeyword.changeKeyword(keyword); + return searchKeyword; + } + + public void deleteOne(Long searchKeywordId) { + SearchKeyword searchKeyword = searchKeywordRepository.findById(searchKeywordId).orElseThrow(() -> new GeneralException(ErrorStatus._SEARCH_KEYWORD_NOT_FOUND)); + searchKeywordRepository.delete(searchKeyword); + } +}