From 1ecd413e83663938407b34fa86aefbc93a252a2e Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:58:24 +0900 Subject: [PATCH 01/18] =?UTF-8?q?fix:=20Schedule=20Repository=EC=9D=98=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=8B=A4?= =?UTF-8?q?=EC=A4=91=20=EA=B5=AC=ED=98=84=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/component/CareManageService.java | 2 -- .../kcy/fitapet/domain/memo/domain/Memo.java | 4 +-- .../domain/memo/domain/MemoCategory.java | 26 ++++++++++++++++--- .../kcy/fitapet/domain/pet/domain/Pet.java | 3 ++- .../service/component/PetManageService.java | 2 ++ .../schedule/dao/ScheduleRepository.java | 2 +- .../service/module/ScheduleSearchService.java | 9 +++---- 7 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/kcy/fitapet/domain/care/service/component/CareManageService.java b/src/main/java/com/kcy/fitapet/domain/care/service/component/CareManageService.java index 8ca2a284..f3d7115e 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/service/component/CareManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/care/service/component/CareManageService.java @@ -58,11 +58,9 @@ public CareInfoRes findCaresByPetId(Long petId) { List careDtos = new ArrayList<>(); for (Care care : cares) { WeekType todayWeek = WeekType.fromLegacyType(LocalDateTime.now().getDayOfWeek().toString()); - log.info("todayWeek: {}", todayWeek); List careDates = careSearchService.findCareDatesCareIdAndWeek(care.getId(), todayWeek); for (CareDate careDate : careDates) { LocalDateTime today = LocalDateTime.now(); - log.info("today: {}", today); boolean isClear = careLogSearchService.existsByCareDateIdOnLogDate(careDate.getId(), today); log.info("isClear: {}", isClear); diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java index 4dc2275a..dff4ed81 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java @@ -20,11 +20,9 @@ public class Memo extends AuthorAuditable { private String title; private String content; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "pet_id") - private Pet pet; @OneToMany(mappedBy = "memo", cascade = CascadeType.ALL) private List memoImages; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id") private MemoCategory memoCategory; diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java index 0cac1684..3c647ec1 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java @@ -1,16 +1,15 @@ package com.kcy.fitapet.domain.memo.domain; import com.kcy.fitapet.domain.model.DateAuditable; +import com.kcy.fitapet.domain.pet.domain.Pet; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.NoArgsConstructor; -import lombok.ToString; +import lombok.*; import java.util.ArrayList; import java.util.List; @Entity +@Getter @Table(name = "MEMO_CATEGORY") @NoArgsConstructor(access = AccessLevel.PROTECTED) @ToString(of = {"categoryName"}) @@ -20,6 +19,9 @@ public class MemoCategory extends DateAuditable { @Column(name = "category_name") private String categoryName; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "pet_id") + private Pet pet; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") private MemoCategory parent; @@ -39,4 +41,20 @@ public static MemoCategory of(String categoryName, MemoCategory parent) { .parent(parent) .build(); } + + public void updatePet(Pet pet) { + if (this.pet != null) + this.pet.getMemoCategories().remove(this); + + this.pet = pet; + pet.getMemoCategories().add(this); + } + + public void updateParent(MemoCategory parent) { + if (this.parent != null) + this.parent.getChildren().remove(this); + + this.parent = parent; + parent.getChildren().add(this); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/pet/domain/Pet.java b/src/main/java/com/kcy/fitapet/domain/pet/domain/Pet.java index 262c3f04..247509bc 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/domain/Pet.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/domain/Pet.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.care.domain.CareCategory; import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.memo.domain.Memo; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; import com.kcy.fitapet.domain.model.DateAuditable; import com.kcy.fitapet.domain.pet.type.GenderType; import com.kcy.fitapet.domain.pet.type.GenderTypeConverter; @@ -45,7 +46,7 @@ public class Pet extends DateAuditable { private List careCategories = new ArrayList<>(); @OneToMany(mappedBy = "pet", cascade = CascadeType.ALL) - private List memos = new ArrayList<>(); + private List memoCategories = new ArrayList<>(); @Builder private Pet(String petName, GenderType gender, String petProfileImg, boolean neutered, LocalDate birthdate, diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java index 246dcdef..002d99b1 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java @@ -31,6 +31,8 @@ public class PetManageService { public void savePet(Pet pet, Long memberId) { pet = petSaveService.savePet(pet); + // TODO : 반려동물 default 부모 카테고리 생성 + Member member = memberSearchService.findById(memberId); memberSaveService.mappingMemberAndPet(member, pet, ManageType.MASTER); } diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java b/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java index f213974e..4d4b21b2 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java @@ -5,6 +5,6 @@ import java.util.List; -public interface ScheduleRepository extends ExtendedRepository { +public interface ScheduleRepository extends ExtendedRepository, ScheduleQueryRepository { public List findAllById(Long id); } diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java index acf8911d..5303279e 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java @@ -16,7 +16,6 @@ @RequiredArgsConstructor public class ScheduleSearchService { private final ScheduleRepository scheduleRepository; - private final ScheduleQueryRepository scheduleQueryRepository; /** * pet_id로 반려동물이 등록된 해당 날짜의 schedule_id 리스트 조회 @@ -26,8 +25,8 @@ public class ScheduleSearchService { */ @Transactional(readOnly = true) public List findScheduleIdsByPetIdAfterDateTime(Long petId, LocalDateTime date, int count) { - return (count != -1) ? scheduleQueryRepository.findScheduleIds(petId, date, count) - : scheduleQueryRepository.findScheduleIds(petId, date); + return (count != -1) ? scheduleRepository.findScheduleIds(petId, date, count) + : scheduleRepository.findScheduleIds(petId, date); } /** @@ -35,7 +34,7 @@ public List findScheduleIdsByPetIdAfterDateTime(Long petId, LocalDateTime */ @Transactional(readOnly = true) public List findTopCountSchedulesByIds(List scheduleIds) { - return scheduleQueryRepository.findSchedulesByIds(scheduleIds); + return scheduleRepository.findSchedulesByIds(scheduleIds); } @Transactional(readOnly = true) @@ -43,6 +42,6 @@ public List findSchedulesByCalender( LocalDateTime date, List petIds ) { - return scheduleQueryRepository.findSchedulesByCalender(date, petIds); + return scheduleRepository.findSchedulesByCalender(date, petIds); } } From ce5f9a6644fc50a439fd00c099e0996aba93399f Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:36:43 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20#83=20extendedRepository=EC=97=90?= =?UTF-8?q?=20QueryDsl=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kcy/fitapet/FitapetApplication.java | 6 +- .../care/dao/CareCategoryJpaRepository.java | 10 ++++ .../care/dao/CareCategoryRepository.java | 11 ---- ...sitory.java => CareDateJpaRepository.java} | 4 +- .../domain/care/dao/CareJpaRepository.java | 7 +++ .../domain/care/dao/CareRepository.java | 8 --- .../care/service/module/CareSaveService.java | 12 ++-- .../service/module/CareSearchService.java | 12 ++-- ...ository.java => ManagerJpaRepository.java} | 5 +- ...pository.java => MemberJpaRepository.java} | 4 +- .../service/module/MemberSaveService.java | 8 +-- .../service/module/MemberSearchService.java | 9 ++- .../domain/model/AuthorAwareAudit.java | 2 - ...epository.java => OauthJpaRepository.java} | 4 +- .../service/module/OauthSearchService.java | 4 +- .../domain/pet/dao/PetJpaRepository.java | 8 +++ .../fitapet/domain/pet/dao/PetRepository.java | 11 ---- .../pet/dao/PetScheduleJpaRepository.java | 7 +++ .../domain/pet/dao/PetScheduleRepository.java | 7 --- .../pet/service/module/PetSaveService.java | 8 +-- .../pet/service/module/PetSearchService.java | 10 ++-- .../schedule/dao/ScheduleJpaRepository.java | 10 ++++ .../schedule/dao/ScheduleRepository.java | 10 ---- .../service/module/ScheduleSaveService.java | 4 +- .../service/module/ScheduleSearchService.java | 5 +- .../DefaultSearchQueryDslRepository.java | 58 ++++++++++++++++++ ...efaultSearchQueryDslRepositoryFactory.java | 55 +++++++++++++++++ .../DefaultSearchQueryDslRepositoryImpl.java | 54 +++++++++++++++++ .../repository/ExtendedJpaRepository.java | 16 +++++ .../ExtendedJpaRepositoryFactory.java | 4 +- ...pl.java => ExtendedJpaRepositoryImpl.java} | 6 +- .../common/repository/ExtendedRepository.java | 9 +-- .../repository/ExtendedRepositoryFactory.java | 59 +++++++++++++++++++ .../common/repository/QueryHandler.java | 8 +++ .../authentication/UserDetailServiceImpl.java | 4 +- .../authorization/ManagerAuthorize.java | 5 +- .../fitapet/global/config/FeignConfig.java | 1 + 37 files changed, 347 insertions(+), 118 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java rename src/main/java/com/kcy/fitapet/domain/care/dao/{CareDateRepository.java => CareDateJpaRepository.java} (60%) create mode 100644 src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java rename src/main/java/com/kcy/fitapet/domain/member/dao/{ManagerRepository.java => ManagerJpaRepository.java} (54%) rename src/main/java/com/kcy/fitapet/domain/member/dao/{MemberRepository.java => MemberJpaRepository.java} (72%) rename src/main/java/com/kcy/fitapet/domain/oauth/dao/{OauthRepository.java => OauthJpaRepository.java} (73%) create mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleJpaRepository.java delete mode 100644 src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepository.java rename src/main/java/com/kcy/fitapet/global/common/repository/{ExtendedRepositoryImpl.java => ExtendedJpaRepositoryImpl.java} (88%) create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryFactory.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/QueryHandler.java diff --git a/src/main/java/com/kcy/fitapet/FitapetApplication.java b/src/main/java/com/kcy/fitapet/FitapetApplication.java index 5052773b..376707ef 100644 --- a/src/main/java/com/kcy/fitapet/FitapetApplication.java +++ b/src/main/java/com/kcy/fitapet/FitapetApplication.java @@ -1,17 +1,15 @@ package com.kcy.fitapet; -import com.kcy.fitapet.global.common.repository.ExtendedJpaRepositoryFactory; +import com.kcy.fitapet.global.common.repository.ExtendedRepositoryFactory; import jakarta.annotation.PostConstruct; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import java.util.TimeZone; @SpringBootApplication -@EnableJpaRepositories(repositoryFactoryBeanClass = ExtendedJpaRepositoryFactory.class) +@EnableJpaRepositories(repositoryFactoryBeanClass = ExtendedRepositoryFactory.class) public class FitapetApplication { public static void main(String[] args) { SpringApplication.run(FitapetApplication.class, args); diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java new file mode 100644 index 00000000..b85e9acb --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.domain.care.dao; + +import com.kcy.fitapet.domain.care.domain.CareCategory; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; + +import java.util.List; + +public interface CareCategoryJpaRepository extends ExtendedJpaRepository { + List findAllByPet_Id(Long petId); +} diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java deleted file mode 100644 index 2c9e13bf..00000000 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.kcy.fitapet.domain.care.dao; - -import com.kcy.fitapet.domain.care.domain.CareCategory; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface CareCategoryRepository extends ExtendedRepository { - List findAllByPet_Id(Long petId); -} diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java similarity index 60% rename from src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java rename to src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java index 2decd749..dc24e0dd 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java @@ -2,10 +2,10 @@ import com.kcy.fitapet.domain.care.domain.CareDate; import com.kcy.fitapet.domain.care.type.WeekType; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; import java.util.List; -public interface CareDateRepository extends ExtendedRepository { +public interface CareDateJpaRepository extends ExtendedJpaRepository { List findAllByCare_IdAndWeek(Long careId, WeekType week); } diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java new file mode 100644 index 00000000..c508900b --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.care.dao; + +import com.kcy.fitapet.domain.care.domain.Care; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; + +public interface CareJpaRepository extends ExtendedJpaRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java deleted file mode 100644 index 753c9d84..00000000 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.kcy.fitapet.domain.care.dao; - -import com.kcy.fitapet.domain.care.domain.Care; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface CareRepository extends ExtendedRepository { -} diff --git a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java index 45253cdf..d4a92391 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java @@ -1,8 +1,8 @@ package com.kcy.fitapet.domain.care.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; -import com.kcy.fitapet.domain.care.dao.CareDateRepository; -import com.kcy.fitapet.domain.care.dao.CareRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareDateJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareJpaRepository; import com.kcy.fitapet.domain.care.domain.Care; import com.kcy.fitapet.domain.care.domain.CareCategory; import com.kcy.fitapet.domain.care.domain.CareDate; @@ -17,9 +17,9 @@ @RequiredArgsConstructor @Slf4j public class CareSaveService { - private final CareRepository careRepository; - private final CareDateRepository careDateRepository; - private final CareCategoryRepository careCategoryRepository; + private final CareJpaRepository careRepository; + private final CareDateJpaRepository careDateRepository; + private final CareCategoryJpaRepository careCategoryRepository; @Transactional public void saveCare(Care care) { diff --git a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java index 9e7475ff..65ff5bd2 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java @@ -1,8 +1,8 @@ package com.kcy.fitapet.domain.care.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; -import com.kcy.fitapet.domain.care.dao.CareDateRepository; -import com.kcy.fitapet.domain.care.dao.CareRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareDateJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareJpaRepository; import com.kcy.fitapet.domain.care.domain.Care; import com.kcy.fitapet.domain.care.domain.CareCategory; import com.kcy.fitapet.domain.care.domain.CareDate; @@ -17,9 +17,9 @@ @Service @RequiredArgsConstructor public class CareSearchService { - private final CareRepository careRepository; - private final CareCategoryRepository careCategoryRepository; - private final CareDateRepository careDateRepository; + private final CareJpaRepository careRepository; + private final CareCategoryJpaRepository careCategoryRepository; + private final CareDateJpaRepository careDateRepository; @Transactional(readOnly = true) public Care findCareById(Long careId) { diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java similarity index 54% rename from src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java rename to src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java index f7dde180..c15bdf0a 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java @@ -1,12 +1,11 @@ package com.kcy.fitapet.domain.member.dao; import com.kcy.fitapet.domain.member.domain.Manager; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; -import org.springframework.data.jpa.repository.Query; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; import java.util.List; -public interface ManagerRepository extends ExtendedRepository { +public interface ManagerJpaRepository extends ExtendedJpaRepository { boolean existsByMember_IdAndPet_Id(Long memberId, Long petId); List findAllByMember_Id(Long memberId); } diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java similarity index 72% rename from src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java rename to src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java index ceb69a19..94c49395 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java @@ -1,11 +1,11 @@ package com.kcy.fitapet.domain.member.dao; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; import java.util.Optional; -public interface MemberRepository extends ExtendedRepository { +public interface MemberJpaRepository extends ExtendedJpaRepository { Optional findByUid(String uid); Optional findByPhone(String phone); boolean existsByUidOrPhone(String uid, String phone); diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java index eb4ec4e7..717382a7 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.member.service.module; -import com.kcy.fitapet.domain.member.dao.ManagerRepository; -import com.kcy.fitapet.domain.member.dao.MemberRepository; +import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; +import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.ManageType; @@ -13,8 +13,8 @@ @Service @RequiredArgsConstructor public class MemberSaveService { - private final MemberRepository memberRepository; - private final ManagerRepository managerRepository; + private final MemberJpaRepository memberRepository; + private final ManagerJpaRepository managerRepository; @Transactional public Member saveMember(Member member) { diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java index 9cc4d66d..74cbb79e 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.member.service.module; -import com.kcy.fitapet.domain.member.dao.ManagerRepository; -import com.kcy.fitapet.domain.member.dao.MemberRepository; +import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; +import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.exception.AccountErrorCode; @@ -10,14 +10,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor public class MemberSearchService { - private final MemberRepository memberRepository; - private final ManagerRepository managerRepository; + private final MemberJpaRepository memberRepository; + private final ManagerJpaRepository managerRepository; @Transactional(readOnly = true) public Member findById(Long id) { diff --git a/src/main/java/com/kcy/fitapet/domain/model/AuthorAwareAudit.java b/src/main/java/com/kcy/fitapet/domain/model/AuthorAwareAudit.java index 7581995e..878c9e1a 100644 --- a/src/main/java/com/kcy/fitapet/domain/model/AuthorAwareAudit.java +++ b/src/main/java/com/kcy/fitapet/domain/model/AuthorAwareAudit.java @@ -1,6 +1,5 @@ package com.kcy.fitapet.domain.model; -import com.kcy.fitapet.domain.member.dao.MemberRepository; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.global.common.security.authentication.CustomUserDetails; import jakarta.persistence.EntityManager; @@ -8,7 +7,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java similarity index 73% rename from src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java rename to src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java index 781cf0d1..8f06e61a 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java @@ -2,12 +2,12 @@ import com.kcy.fitapet.domain.oauth.domain.OauthAccount; import com.kcy.fitapet.domain.oauth.type.ProviderType; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; import java.math.BigInteger; import java.util.Optional; -public interface OauthRepository extends ExtendedRepository { +public interface OauthJpaRepository extends ExtendedJpaRepository { Optional findByOauthIdAndProvider(BigInteger oauthId, ProviderType provider); boolean existsByOauthIdAndProvider(BigInteger oauthId, ProviderType provider); boolean existsByEmail(String email); diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java index 27f7b2ae..41515658 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.oauth.service.module; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.domain.oauth.dao.OauthRepository; +import com.kcy.fitapet.domain.oauth.dao.OauthJpaRepository; import com.kcy.fitapet.domain.oauth.domain.OauthAccount; import com.kcy.fitapet.domain.oauth.exception.OauthException; import com.kcy.fitapet.domain.oauth.type.ProviderType; @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor public class OauthSearchService { - private final OauthRepository oauthRepository; + private final OauthJpaRepository oauthRepository; @Transactional(readOnly = true) public boolean isExistMember(BigInteger oauthId, ProviderType provider) { diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java new file mode 100644 index 00000000..2966dbb2 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java @@ -0,0 +1,8 @@ +package com.kcy.fitapet.domain.pet.dao; + +import com.kcy.fitapet.domain.pet.domain.Pet; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; + +public interface PetJpaRepository extends ExtendedJpaRepository { + boolean existsById(Long id); +} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java deleted file mode 100644 index 110a79de..00000000 --- a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.kcy.fitapet.domain.pet.dao; - -import com.kcy.fitapet.domain.pet.domain.Pet; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; - -public interface PetRepository extends ExtendedRepository { - boolean existsById(Long id); -} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java new file mode 100644 index 00000000..87c3fdb1 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.pet.dao; + +import com.kcy.fitapet.domain.pet.domain.PetSchedule; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; + +public interface PetScheduleJpaRepository extends ExtendedJpaRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java deleted file mode 100644 index 087ee8da..00000000 --- a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.kcy.fitapet.domain.pet.dao; - -import com.kcy.fitapet.domain.pet.domain.PetSchedule; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; - -public interface PetScheduleRepository extends ExtendedRepository { -} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java index edaa453a..45aaa396 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.pet.service.module; -import com.kcy.fitapet.domain.pet.dao.PetRepository; -import com.kcy.fitapet.domain.pet.dao.PetScheduleRepository; +import com.kcy.fitapet.domain.pet.dao.PetJpaRepository; +import com.kcy.fitapet.domain.pet.dao.PetScheduleJpaRepository; import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.domain.pet.domain.PetSchedule; import com.kcy.fitapet.domain.schedule.domain.Schedule; @@ -15,8 +15,8 @@ @Service @RequiredArgsConstructor public class PetSaveService { - private final PetRepository petRepository; - private final PetScheduleRepository petScheduleRepository; + private final PetJpaRepository petRepository; + private final PetScheduleJpaRepository petScheduleRepository; @Transactional public Pet savePet(Pet pet) {; diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java index ef825ea4..6fcd3b5e 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java @@ -1,9 +1,7 @@ package com.kcy.fitapet.domain.pet.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; -import com.kcy.fitapet.domain.care.domain.CareCategory; -import com.kcy.fitapet.domain.care.dto.CareCategoryDto; -import com.kcy.fitapet.domain.pet.dao.PetRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; +import com.kcy.fitapet.domain.pet.dao.PetJpaRepository; import com.kcy.fitapet.domain.pet.domain.Pet; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,8 +12,8 @@ @Service @RequiredArgsConstructor public class PetSearchService { - private final PetRepository petRepository; - private final CareCategoryRepository careCategoryRepository; + private final PetJpaRepository petRepository; + private final CareCategoryJpaRepository careCategoryRepository; @Transactional(readOnly = true) public Pet findPetById(Long id) { diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleJpaRepository.java new file mode 100644 index 00000000..9f7a5c41 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleJpaRepository.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.domain.schedule.dao; + +import com.kcy.fitapet.domain.schedule.domain.Schedule; +import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; + +import java.util.List; + +public interface ScheduleJpaRepository extends ExtendedJpaRepository, ScheduleQueryRepository { + public List findAllById(Long id); +} diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java b/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java deleted file mode 100644 index 4d4b21b2..00000000 --- a/src/main/java/com/kcy/fitapet/domain/schedule/dao/ScheduleRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.kcy.fitapet.domain.schedule.dao; - -import com.kcy.fitapet.domain.schedule.domain.Schedule; -import com.kcy.fitapet.global.common.repository.ExtendedRepository; - -import java.util.List; - -public interface ScheduleRepository extends ExtendedRepository, ScheduleQueryRepository { - public List findAllById(Long id); -} diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSaveService.java b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSaveService.java index 2b853e9a..6afbf5d0 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSaveService.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.domain.schedule.service.module; -import com.kcy.fitapet.domain.schedule.dao.ScheduleRepository; +import com.kcy.fitapet.domain.schedule.dao.ScheduleJpaRepository; import com.kcy.fitapet.domain.schedule.domain.Schedule; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -10,7 +10,7 @@ @Service @RequiredArgsConstructor public class ScheduleSaveService { - private final ScheduleRepository scheduleRepository; + private final ScheduleJpaRepository scheduleRepository; public Schedule saveSchedule(Schedule schedule) { return scheduleRepository.save(schedule); diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java index 5303279e..9cac74f1 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/service/module/ScheduleSearchService.java @@ -1,7 +1,6 @@ package com.kcy.fitapet.domain.schedule.service.module; -import com.kcy.fitapet.domain.schedule.dao.ScheduleQueryRepository; -import com.kcy.fitapet.domain.schedule.dao.ScheduleRepository; +import com.kcy.fitapet.domain.schedule.dao.ScheduleJpaRepository; import com.kcy.fitapet.domain.schedule.dto.ScheduleInfoDto; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,7 +14,7 @@ @Slf4j @RequiredArgsConstructor public class ScheduleSearchService { - private final ScheduleRepository scheduleRepository; + private final ScheduleJpaRepository scheduleRepository; /** * pet_id로 반려동물이 등록된 해당 날짜의 schedule_id 리스트 조회 diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java new file mode 100644 index 00000000..d98cb0d7 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java @@ -0,0 +1,58 @@ +package com.kcy.fitapet.global.common.repository; + +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Predicate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.List; +import java.util.Map; + +/** + * QueryDSL을 이용한 검색 조건을 처리하는 기본적인 메서드를 선언한 인터페이스 + * @author 양재서 + */ +public interface DefaultSearchQueryDslRepository { + /** + * 검색 조건에 해당하는 도메인(혹은 DTO) 리스트를 조회하는 메서드 + * @param predicate : 검색 조건 + * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 + * @param sort : 정렬 조건 + * + *
+     * {@code
+     *
+     * }
+     * 
+ */ + List findList(Predicate predicate, QueryHandler queryHandler, Sort sort); + + /** + * 검색 조건에 해당하는 도메인(혹은 DTO) 페이지를 조회하는 메서드 + * @param predicate : + * @param queryHandler : + * @param pageable : + */ + Page findPage(Predicate predicate, QueryHandler queryHandler, Pageable pageable); + + /** + * 검색 조건에 해당하는 도메인(혹은 DTO) 리스트를 조회하는 메서드 + * @param predicate : 검색 조건 + * @param type : 조회할 도메인(혹은 DTO) 타입 + * @param bindings : + * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 + * @param sort : 정렬 조건 + */ +

List

selectList(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Sort sort); + + /** + * 검색 조건에 해당하는 도메인(혹은 DTO) 페이지를 조회하는 메서드 + * @param predicate : + * @param type : + * @param bindings : + * @param queryHandler : + * @param pageable : + */ +

Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java new file mode 100644 index 00000000..5a349b45 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java @@ -0,0 +1,55 @@ +package com.kcy.fitapet.global.common.repository; + +import jakarta.persistence.EntityManager; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryComposition; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.RepositoryFragment; + +public class DefaultSearchQueryDslRepositoryFactory, E, ID> extends JpaRepositoryFactoryBean { + /** + * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public DefaultSearchQueryDslRepositoryFactory(Class repositoryInterface) { + super(repositoryInterface); + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { + return new QueryDslRepositoryFactory(entityManager); + } + + private static class QueryDslRepositoryFactory extends JpaRepositoryFactory { + private final EntityManager em; + + /** + * Creates a new {@link JpaRepositoryFactory}. + * + * @param entityManager must not be {@literal null} + */ + public QueryDslRepositoryFactory(EntityManager entityManager) { + super(entityManager); + this.em = entityManager; + } + + @Override + protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { + RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata); + + if (ExtendedJpaRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { + var impl = super.instantiateClass( + DefaultSearchQueryDslRepository.class, + this.getEntityInformation(metadata.getDomainType()), + this.em + ); + fragments = fragments.append(RepositoryFragment.implemented(impl)); + } + return fragments; + } + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java new file mode 100644 index 00000000..9af7f1ad --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java @@ -0,0 +1,54 @@ +package com.kcy.fitapet.global.common.repository; + +import com.querydsl.core.types.EntityPath; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.EntityPathBase; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.querydsl.SimpleEntityPathResolver; + +import java.util.List; +import java.util.Map; + +public class DefaultSearchQueryDslRepositoryImpl implements DefaultSearchQueryDslRepository { + private final EntityManager em; + private final JPAQueryFactory queryFactory; + private final EntityPath path; + + public DefaultSearchQueryDslRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + this.em = entityManager; + this.queryFactory = new JPAQueryFactory(entityManager); + this.path = SimpleEntityPathResolver.INSTANCE.createPath(entityInformation.getJavaType()); + } + + public DefaultSearchQueryDslRepositoryImpl(Class type, EntityManager entityManager) { + this.em = entityManager; + this.queryFactory = new JPAQueryFactory(entityManager); + this.path = new EntityPathBase<>(type, "entity"); + } + + @Override + public List findList(Predicate predicate, QueryHandler queryHandler, Sort sort) { + return null; + } + + @Override + public Page findPage(Predicate predicate, QueryHandler queryHandler, Pageable pageable) { + return null; + } + + @Override + public

List

selectList(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Sort sort) { + return null; + } + + @Override + public

Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable) { + return null; + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepository.java new file mode 100644 index 00000000..56e50a2b --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepository.java @@ -0,0 +1,16 @@ +package com.kcy.fitapet.global.common.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.io.Serializable; + +@NoRepositoryBean +public interface ExtendedJpaRepository extends JpaRepository { + /** + * ID로 조회한 결과가 없을 경우 예외를 발생시키는 메서드 + * @param id : 조회할 ID + * @return : 조회한 결과 도메인 + */ + T findByIdOrElseThrow(ID id); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java index 0e4ea8aa..eb030f6e 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java @@ -36,9 +36,9 @@ public InnerRepositoryFactory(EntityManager em) { protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { var fragments = super.getRepositoryFragments(metadata); - if (ExtendedRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { + if (ExtendedJpaRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { var impl = super.instantiateClass( - ExtendedRepositoryImpl.class, + ExtendedJpaRepositoryImpl.class, this.getEntityInformation(metadata.getDomainType()), this.em ); diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java similarity index 88% rename from src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryImpl.java rename to src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java index cf66ff4c..f8cb107a 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java @@ -12,13 +12,13 @@ import java.io.Serializable; @Slf4j -public class ExtendedRepositoryImpl +public class ExtendedJpaRepositoryImpl extends SimpleJpaRepository - implements ExtendedRepository { + implements ExtendedJpaRepository { private final EntityManager em; private static final String ID_MUST_NOT_BE_NULL = "The given id must not be null!"; - public ExtendedRepositoryImpl(JpaEntityInformation entityInformation, EntityManager em) { + public ExtendedJpaRepositoryImpl(JpaEntityInformation entityInformation, EntityManager em) { super(entityInformation, em); this.em = em; } diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepository.java index 2f31e0c7..eaac131d 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepository.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepository.java @@ -1,16 +1,9 @@ package com.kcy.fitapet.global.common.repository; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; import java.io.Serializable; @NoRepositoryBean -public interface ExtendedRepository extends JpaRepository { - /** - * ID로 조회한 결과가 없을 경우 예외를 발생시키는 메서드 - * @param id : 조회할 ID - * @return : 조회한 결과 도메인 - */ - T findByIdOrElseThrow(ID id); +public interface ExtendedRepository extends ExtendedJpaRepository, DefaultSearchQueryDslRepository { } diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryFactory.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryFactory.java new file mode 100644 index 00000000..7d2a6778 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedRepositoryFactory.java @@ -0,0 +1,59 @@ +package com.kcy.fitapet.global.common.repository; + + +import jakarta.persistence.EntityManager; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; +import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryComposition; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; + +public class ExtendedRepositoryFactory, E, ID> extends JpaRepositoryFactoryBean { + /** + * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public ExtendedRepositoryFactory(Class repositoryInterface) { + super(repositoryInterface); + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) { + return new ExtendedRepositoryFactory.InnerRepositoryFactory(em); + } + + private static class InnerRepositoryFactory extends JpaRepositoryFactory { + private final EntityManager em; + + public InnerRepositoryFactory(EntityManager em) { + super(em); + this.em = em; + } + + @Override + protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { + RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata); + + if (ExtendedJpaRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { + var implExtendedJpa = super.instantiateClass( + ExtendedJpaRepositoryImpl.class, + this.getEntityInformation(metadata.getDomainType()), + this.em + ); + + var implQueryDsl = super.instantiateClass( + DefaultSearchQueryDslRepositoryImpl.class, + this.getEntityInformation(metadata.getDomainType()), + this.em + ); + + fragments = fragments.append(RepositoryComposition.RepositoryFragments.just(implExtendedJpa)) + .append(RepositoryComposition.RepositoryFragments.just(implQueryDsl)); + } + + return fragments; + } + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/QueryHandler.java b/src/main/java/com/kcy/fitapet/global/common/repository/QueryHandler.java new file mode 100644 index 00000000..de6852e0 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/repository/QueryHandler.java @@ -0,0 +1,8 @@ +package com.kcy.fitapet.global.common.repository; + +import com.querydsl.jpa.impl.JPAQuery; + +@FunctionalInterface +public interface QueryHandler { + JPAQuery apply(JPAQuery query); +} diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java index 5d6eb361..730c91d3 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.global.common.security.authentication; -import com.kcy.fitapet.domain.member.dao.MemberRepository; +import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; @@ -13,7 +13,7 @@ @Service @RequiredArgsConstructor public class UserDetailServiceImpl implements UserDetailsService { - private final MemberRepository userRepository; + private final MemberJpaRepository userRepository; @Override @Cacheable(value = "securityUser", key = "#userId", unless = "#result == null", cacheManager = "securityUserCacheManager") diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java index 990c1f11..10ebc0e6 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java @@ -1,7 +1,6 @@ package com.kcy.fitapet.global.common.security.authorization; -import com.kcy.fitapet.domain.member.dao.ManagerRepository; -import jakarta.persistence.EntityManager; +import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -10,7 +9,7 @@ @RequiredArgsConstructor @Slf4j public class ManagerAuthorize { - private final ManagerRepository managerRepository; + private final ManagerJpaRepository managerRepository; public boolean isManager(Long memberId, Long petId) { return managerRepository.existsByMember_IdAndPet_Id(memberId, petId); diff --git a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java index eea6fe47..8997bd2c 100644 --- a/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/FeignConfig.java @@ -5,6 +5,7 @@ import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration; import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @Configuration @EnableFeignClients(basePackages = "com.kcy.fitapet") From 1147ef45949828af110a7bcefc41ce6d9b6d1014 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 15:58:15 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20#83=20JPAQuery=20=EA=B5=AC?= =?UTF-8?q?=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...efaultSearchQueryDslRepositoryFactory.java | 55 ------------------- .../DefaultSearchQueryDslRepositoryImpl.java | 55 +++++++++++++++++-- .../ExtendedJpaRepositoryFactory.java | 51 ----------------- 3 files changed, 51 insertions(+), 110 deletions(-) delete mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java delete mode 100644 src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java deleted file mode 100644 index 5a349b45..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.kcy.fitapet.global.common.repository; - -import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; -import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.RepositoryComposition; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.data.repository.core.support.RepositoryFragment; - -public class DefaultSearchQueryDslRepositoryFactory, E, ID> extends JpaRepositoryFactoryBean { - /** - * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. - * - * @param repositoryInterface must not be {@literal null}. - */ - public DefaultSearchQueryDslRepositoryFactory(Class repositoryInterface) { - super(repositoryInterface); - } - - @Override - protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { - return new QueryDslRepositoryFactory(entityManager); - } - - private static class QueryDslRepositoryFactory extends JpaRepositoryFactory { - private final EntityManager em; - - /** - * Creates a new {@link JpaRepositoryFactory}. - * - * @param entityManager must not be {@literal null} - */ - public QueryDslRepositoryFactory(EntityManager entityManager) { - super(entityManager); - this.em = entityManager; - } - - @Override - protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { - RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata); - - if (ExtendedJpaRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { - var impl = super.instantiateClass( - DefaultSearchQueryDslRepository.class, - this.getEntityInformation(metadata.getDomainType()), - this.em - ); - fragments = fragments.append(RepositoryFragment.implemented(impl)); - } - return fragments; - } - } -} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java index 9af7f1ad..ebe4cff1 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java @@ -1,19 +1,22 @@ package com.kcy.fitapet.global.common.repository; -import com.querydsl.core.types.EntityPath; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.*; import com.querydsl.core.types.dsl.EntityPathBase; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.querydsl.QSort; import org.springframework.data.querydsl.SimpleEntityPathResolver; +import org.springframework.util.Assert; import java.util.List; import java.util.Map; +import java.util.function.Function; public class DefaultSearchQueryDslRepositoryImpl implements DefaultSearchQueryDslRepository { private final EntityManager em; @@ -49,6 +52,50 @@ public

List

selectList(Predicate predicate, Class

type, Map Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable) { - return null; + Assert.notNull(pageable, "pageable must not be null!"); + + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private JPAQuery buildWithoutSelect(Predicate predicate, Map> bindings, QueryHandler queryHandler, Sort sort) { + JPAQuery query = queryFactory.from(path); + + if (predicate != null) query = query.where(predicate); + if (queryHandler != null) query = queryHandler.apply(query); + if (sort != null) { + if (sort instanceof QSort qSort) { + query = query.orderBy(qSort.getOrderSpecifiers().toArray(new OrderSpecifier[0])); + } else { + Function castToQueryDsl = nullHandling -> switch (nullHandling) { + case NATIVE -> OrderSpecifier.NullHandling.Default; + case NULLS_FIRST -> OrderSpecifier.NullHandling.NullsFirst; + case NULLS_LAST -> OrderSpecifier.NullHandling.NullsLast; + }; + + for (Sort.Order order : sort) { + OrderSpecifier.NullHandling queryDslNullHandling = castToQueryDsl.apply(order.getNullHandling()); + + Order orderBy = order.isAscending() ? Order.ASC : Order.DESC; + OrderSpecifier os; + + if (bindings != null && bindings.containsKey(order.getProperty())) { + Expression expression = bindings.get(order.getProperty()); + + if (expression instanceof Operation && ((Operation) expression).getOperator() == Ops.ALIAS) { + os = new OrderSpecifier<>(orderBy, Expressions.stringPath(((Operation) expression).getArg(1).toString()), queryDslNullHandling); + } else { + os = new OrderSpecifier(orderBy, expression, queryDslNullHandling); + } + } else { + os = new OrderSpecifier<>(orderBy, Expressions.stringPath(order.getProperty()), queryDslNullHandling); + } + + query = query.orderBy(os); + } + } + } + + return query; } } diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java deleted file mode 100644 index eb030f6e..00000000 --- a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.kcy.fitapet.global.common.repository; - - -import jakarta.persistence.EntityManager; -import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; -import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.core.support.RepositoryComposition; -import org.springframework.data.repository.core.support.RepositoryFactorySupport; - -public class ExtendedJpaRepositoryFactory, E, ID> extends JpaRepositoryFactoryBean { - /** - * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. - * - * @param repositoryInterface must not be {@literal null}. - */ - public ExtendedJpaRepositoryFactory(Class repositoryInterface) { - super(repositoryInterface); - } - - @Override - protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) { - return new InnerRepositoryFactory(em); - } - - private static class InnerRepositoryFactory extends JpaRepositoryFactory { - private final EntityManager em; - - public InnerRepositoryFactory(EntityManager em) { - super(em); - this.em = em; - } - - @Override - protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { - var fragments = super.getRepositoryFragments(metadata); - - if (ExtendedJpaRepository.class.isAssignableFrom(metadata.getRepositoryInterface())) { - var impl = super.instantiateClass( - ExtendedJpaRepositoryImpl.class, - this.getEntityInformation(metadata.getDomainType()), this.em - ); - - fragments = fragments.append(RepositoryComposition.RepositoryFragments.just(impl)); - } - - return fragments; - } - } -} From 46418da44e309b50804114decffc7900499be916 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:04:08 +0900 Subject: [PATCH 04/18] =?UTF-8?q?refactor:=20#83=20byildWithoutSelect=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultSearchQueryDslRepositoryImpl.java | 88 ++++++++++++------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java index ebe4cff1..d052d3be 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java @@ -57,45 +57,71 @@ public

Page

selectPage(Predicate predicate, Class

type, Map buildWithoutSelect(Predicate predicate, Map> bindings, QueryHandler queryHandler, Sort sort) { JPAQuery query = queryFactory.from(path); - if (predicate != null) query = query.where(predicate); - if (queryHandler != null) query = queryHandler.apply(query); + applyPredicate(predicate, query); + applyQueryHandler(queryHandler, query); + applySort(query, sort, bindings); + + return query; + } + + private void applyPredicate(Predicate predicate, JPAQuery query) { + if (predicate != null) query.where(predicate); + } + + private void applyQueryHandler(QueryHandler queryHandler, JPAQuery query) { + if (queryHandler != null) queryHandler.apply(query); + } + + private void applySort(JPAQuery query, Sort sort, Map> bindings) { if (sort != null) { if (sort instanceof QSort qSort) { - query = query.orderBy(qSort.getOrderSpecifiers().toArray(new OrderSpecifier[0])); + query.orderBy(qSort.getOrderSpecifiers().toArray(new OrderSpecifier[0])); } else { - Function castToQueryDsl = nullHandling -> switch (nullHandling) { - case NATIVE -> OrderSpecifier.NullHandling.Default; - case NULLS_FIRST -> OrderSpecifier.NullHandling.NullsFirst; - case NULLS_LAST -> OrderSpecifier.NullHandling.NullsLast; - }; - - for (Sort.Order order : sort) { - OrderSpecifier.NullHandling queryDslNullHandling = castToQueryDsl.apply(order.getNullHandling()); - - Order orderBy = order.isAscending() ? Order.ASC : Order.DESC; - OrderSpecifier os; - - if (bindings != null && bindings.containsKey(order.getProperty())) { - Expression expression = bindings.get(order.getProperty()); - - if (expression instanceof Operation && ((Operation) expression).getOperator() == Ops.ALIAS) { - os = new OrderSpecifier<>(orderBy, Expressions.stringPath(((Operation) expression).getArg(1).toString()), queryDslNullHandling); - } else { - os = new OrderSpecifier(orderBy, expression, queryDslNullHandling); - } - } else { - os = new OrderSpecifier<>(orderBy, Expressions.stringPath(order.getProperty()), queryDslNullHandling); - } - - query = query.orderBy(os); - } + applySortOrders(query, sort, bindings); } } + } - return query; + private void applySortOrders(JPAQuery query, Sort sort, Map> bindings) { + for (Sort.Order order : sort) { + OrderSpecifier.NullHandling queryDslNullHandling = getQueryDslNullHandling(order); + + OrderSpecifier os = getOrderSpecifier(order, bindings, queryDslNullHandling); + + query.orderBy(os); + } + } + + private OrderSpecifier.NullHandling getQueryDslNullHandling(Sort.Order order) { + Function castToQueryDsl = nullHandling -> switch (nullHandling) { + case NATIVE -> OrderSpecifier.NullHandling.Default; + case NULLS_FIRST -> OrderSpecifier.NullHandling.NullsFirst; + case NULLS_LAST -> OrderSpecifier.NullHandling.NullsLast; + }; + + return castToQueryDsl.apply(order.getNullHandling()); + } + + private OrderSpecifier getOrderSpecifier(Sort.Order order, Map> bindings, OrderSpecifier.NullHandling queryDslNullHandling) { + Order orderBy = order.isAscending() ? Order.ASC : Order.DESC; + + if (bindings != null && bindings.containsKey(order.getProperty())) { + Expression expression = bindings.get(order.getProperty()); + return createOrderSpecifier(orderBy, expression, queryDslNullHandling); + } else { + return createOrderSpecifier(orderBy, Expressions.stringPath(order.getProperty()), queryDslNullHandling); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private OrderSpecifier createOrderSpecifier(Order orderBy, Expression expression, OrderSpecifier.NullHandling queryDslNullHandling) { + if (expression instanceof Operation && ((Operation) expression).getOperator() == Ops.ALIAS) { + return new OrderSpecifier<>(orderBy, Expressions.stringPath(((Operation) expression).getArg(1).toString()), queryDslNullHandling); + } else { + return new OrderSpecifier(orderBy, expression, queryDslNullHandling); + } } } From b74486e2b957674912f6d7257efe0f2d25aa6030 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:31:19 +0900 Subject: [PATCH 05/18] =?UTF-8?q?feat:=20#83=20Select=20Query=20Dsl=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=ED=99=94=20&&=20docs:=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultSearchQueryDslRepository.java | 60 +++++++++++++------ .../DefaultSearchQueryDslRepositoryImpl.java | 19 +++++- .../repository/ExtendedJpaRepositoryImpl.java | 1 - 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java index d98cb0d7..8b4cef24 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java @@ -15,44 +15,66 @@ */ public interface DefaultSearchQueryDslRepository { /** - * 검색 조건에 해당하는 도메인(혹은 DTO) 리스트를 조회하는 메서드 + * 검색 조건에 해당하는 도메인 리스트를 조회하는 메서드 * @param predicate : 검색 조건 * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 * @param sort : 정렬 조건 - * - *

-     * {@code
-     *
-     * }
-     * 
*/ List findList(Predicate predicate, QueryHandler queryHandler, Sort sort); /** - * 검색 조건에 해당하는 도메인(혹은 DTO) 페이지를 조회하는 메서드 - * @param predicate : - * @param queryHandler : - * @param pageable : + * 검색 조건에 해당하는 도메인 페이지를 조회하는 메서드 + * @param predicate : 검색 조건 + * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 + * @param pageable : 페이지 정보 */ Page findPage(Predicate predicate, QueryHandler queryHandler, Pageable pageable); /** - * 검색 조건에 해당하는 도메인(혹은 DTO) 리스트를 조회하는 메서드 + * 검색 조건에 해당하는 DTO 리스트를 조회하는 메서드 * @param predicate : 검색 조건 * @param type : 조회할 도메인(혹은 DTO) 타입 - * @param bindings : + * @param bindings : 검색 조건에 해당하는 도메인(혹은 DTO)의 필드 * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 * @param sort : 정렬 조건 + *
+     * {@code
+     * @Component
+     * class SearchService {
+     *      private final QEntity entity = QEntity.entity;
+     *
+     *      private final QEntityChild entityChild = QEntityChild.entityChild;
+     *
+     *      private EntityDto select() {
+     *          Predicate predicate = new BooleanBuilder();
+     *          predicate.and(entity.id.eq(1L));
+     *
+     *          QueryHandler queryHandler = query -> query.leftJoin(entityChild).on(entity.id.eq(entityChild.entity.id));
+     *          Sort sort = Sort.by(Sort.Direction.DESC, entity.id);
+     *
+     *          return searchRepository.findList(predicate, EntityDto.class, this.buildBindings(), queryHandler, sort);
+     *      }
+     *
+     *      private Map> buildBindings() {
+     *          Map> bindings = new HashMap<>();
+     *
+     *          bindings.put("id", entity.id);
+     *          bindings.put("name", entity.name);
+     *
+     *          return bindings;
+     *      }
+     * }
+     * 
*/

List

selectList(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Sort sort); /** - * 검색 조건에 해당하는 도메인(혹은 DTO) 페이지를 조회하는 메서드 - * @param predicate : - * @param type : - * @param bindings : - * @param queryHandler : - * @param pageable : + * 검색 조건에 해당하는 DTO 페이지를 조회하는 메서드 + * @param predicate : 검색 조건 + * @param type : 조회할 도메인(혹은 DTO) 타입 + * @param bindings : 검색 조건에 해당하는 도메인(혹은 DTO)의 필드 + * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 + * @param pageable : 페이지 정보 */

Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable); } diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java index d052d3be..dbde50c8 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java @@ -7,6 +7,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.support.JpaEntityInformation; @@ -37,24 +38,36 @@ public DefaultSearchQueryDslRepositoryImpl(Class type, EntityManager entityMa @Override public List findList(Predicate predicate, QueryHandler queryHandler, Sort sort) { - return null; + return this.buildWithoutSelect(predicate, null, queryHandler, sort).select(path).fetch(); } @Override public Page findPage(Predicate predicate, QueryHandler queryHandler, Pageable pageable) { - return null; + Assert.notNull(pageable, "pageable must not be null!"); + + JPAQuery query = this.buildWithoutSelect(predicate, null, queryHandler, pageable.getSort()).select(path); + + int totalSize = query.fetch().size(); + query = query.offset(pageable.getOffset()).limit(pageable.getPageSize()); + + return new PageImpl<>(query.select(path).fetch(), pageable, totalSize); } @Override public

List

selectList(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Sort sort) { - return null; + return this.buildWithoutSelect(predicate, bindings, queryHandler, sort).select(Projections.bean(type, bindings)).fetch(); } @Override public

Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable) { Assert.notNull(pageable, "pageable must not be null!"); + JPAQuery query = this.buildWithoutSelect(predicate, bindings, queryHandler, pageable.getSort()).select(path); + + int totalSize = query.fetch().size(); + query = query.offset(pageable.getOffset()).limit(pageable.getPageSize()); + return new PageImpl<>(query.select(Projections.bean(type, bindings)).fetch(), pageable, totalSize); } private JPAQuery buildWithoutSelect(Predicate predicate, Map> bindings, QueryHandler queryHandler, Sort sort) { diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java index f8cb107a..03a203a4 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/ExtendedJpaRepositoryImpl.java @@ -38,7 +38,6 @@ public T findByIdOrElseThrow(ID id) { } // TODO: 2021-11-30. 이름에 의존적인 메서드 제거하고, 상태 패턴을 적용하여 의존도 낮추기 - // TODO: [점검 사항] camelCase -> CAMEL_CASE로 변경이 되는지? private String getClassName() { Class domainType = getDomainClass(); return StringUtils.capitalize(domainType.getSimpleName()) From c175bd73e6331175424aefaa38c7946ada798f9b Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:39:14 +0900 Subject: [PATCH 06/18] =?UTF-8?q?feat:=20#83=20docs:=20#83=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=98=88=EC=8B=9C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultSearchQueryDslRepository.java | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java index 8b4cef24..6084287e 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java @@ -19,6 +19,25 @@ public interface DefaultSearchQueryDslRepository { * @param predicate : 검색 조건 * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 * @param sort : 정렬 조건 + *

+     * {@code
+     * @Component
+     * class SearchService {
+     *      private final QEntity entity = QEntity.entity;
+     *      private final QEntityChild entityChild = QEntityChild.entityChild;
+     *
+     *      private Entity select() {
+     *          Predicate predicate = new BooleanBuilder();
+     *          predicate.and(entity.id.eq(1L));
+     *
+     *          QueryHandler queryHandler = query -> query.leftJoin(entityChild).on(entity.id.eq(entityChild.entity.id));
+     *          Sort sort = Sort.by(Sort.Direction.DESC, entity.id);
+     *
+     *          return searchRepository.findList(predicate, queryHandler, );
+     *      }
+     * }
+     * }
+     * 
*/ List findList(Predicate predicate, QueryHandler queryHandler, Sort sort); @@ -27,6 +46,25 @@ public interface DefaultSearchQueryDslRepository { * @param predicate : 검색 조건 * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 * @param pageable : 페이지 정보 + *
+     * {@code
+     * @Component
+     * class SearchService {
+     *      private final QEntity entity = QEntity.entity;
+     *      private final QEntityChild entityChild = QEntityChild.entityChild;
+     *
+     *      private Entity select() {
+     *          Predicate predicate = new BooleanBuilder();
+     *          predicate.and(entity.id.eq(1L));
+     *
+     *          QueryHandler queryHandler = query -> query.leftJoin(entityChild).on(entity.id.eq(entityChild.entity.id));
+     *          Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, entity.id));
+     *
+     *          return searchRepository.findList(predicate, queryHandler, pageable);
+     *      }
+     * }
+     * }
+     * 
*/ Page findPage(Predicate predicate, QueryHandler queryHandler, Pageable pageable); @@ -42,7 +80,6 @@ public interface DefaultSearchQueryDslRepository { * @Component * class SearchService { * private final QEntity entity = QEntity.entity; - * * private final QEntityChild entityChild = QEntityChild.entityChild; * * private EntityDto select() { @@ -64,6 +101,7 @@ public interface DefaultSearchQueryDslRepository { * return bindings; * } * } + * } * */

List

selectList(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Sort sort); @@ -75,6 +113,31 @@ public interface DefaultSearchQueryDslRepository { * @param bindings : 검색 조건에 해당하는 도메인(혹은 DTO)의 필드 * @param queryHandler : 검색 조건에 추가적으로 적용할 조건 * @param pageable : 페이지 정보 + *

+     * {@code
+     * @Component
+     * class SearchService {
+     *      private final QEntity entity = QEntity.entity;
+     *      private final QEntityChild entityChild = QEntityChild.entityChild;
+     *
+     *      private EntityDto select() {
+     *          Predicate predicate = new BooleanBuilder();
+     *          predicate.and(entity.id.eq(1L));
+     *          QueryHandler queryHandler = query -> query.leftJoin(entityChild).on(entity.id.eq(entityChild.entity.id));
+     *          Pageable pageable = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, entity.id));
+     *
+     *          return searchRepository.findPage(predicate, EntityDto.class, this.buildBindings(), queryHandler, pageable);
+     *      }
+     *
+     *      private Map> buildBindings() {
+     *          Map> bindings = new HashMap<>();
+     *          bindings.put("id", entity.id);
+     *          bindings.put("name", entity.name);
+     *          return bindings;
+     *      }
+     *  }
+     *  }
+     *  
*/

Page

selectPage(Predicate predicate, Class

type, Map> bindings, QueryHandler queryHandler, Pageable pageable); } From a8e7e3aa5b741a4766c2e7d18add29d4623a2473 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:57:44 +0900 Subject: [PATCH 07/18] =?UTF-8?q?fix:=20#83=20=EB=B0=98=EB=A0=A4=EB=8F=99?= =?UTF-8?q?=EB=AC=BC=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C,=20=EB=A3=A8?= =?UTF-8?q?=ED=8A=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 17 +++++++++++++++++ .../domain/memo/dao/MemoRepository.java | 7 +++++++ .../domain/memo/domain/MemoCategory.java | 19 ++++++++++++------- .../service/component/MemoManageService.java | 15 +++++++++++++++ .../memo/service/module/MemoSaveService.java | 13 +++++++++++++ .../service/module/MemoSearchService.java | 13 +++++++++++++ .../service/component/PetManageService.java | 3 ++- 7 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java new file mode 100644 index 00000000..c0fb08ce --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -0,0 +1,17 @@ +package com.kcy.fitapet.domain.memo.api; + +import com.kcy.fitapet.domain.memo.service.component.MemoManageService; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "일기 API", description = "일기 관리 API") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v2/users/{user_id}") +public class MemoApi { + private final MemoManageService memoManageService; +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java new file mode 100644 index 00000000..b5904386 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.domain.Memo; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; + +public interface MemoRepository extends ExtendedRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java index 3c647ec1..87596c17 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java @@ -30,16 +30,21 @@ public class MemoCategory extends DateAuditable { private List children = new ArrayList<>(); @Builder - private MemoCategory(String categoryName, MemoCategory parent) { + private MemoCategory(String categoryName) { this.categoryName = categoryName; - this.parent = parent; } - public static MemoCategory of(String categoryName, MemoCategory parent) { - return MemoCategory.builder() - .categoryName(categoryName) - .parent(parent) - .build(); + public static MemoCategory ofChildrenInstance(String categoryName, MemoCategory parent, Pet pet) { + MemoCategory category = MemoCategory.builder().categoryName(categoryName).build(); + category.updatePet(pet); + category.updateParent(parent); + return category; + } + + public static MemoCategory ofRootInstance(String categoryName, Pet pet) { + MemoCategory category = MemoCategory.builder().categoryName(categoryName).build(); + category.updatePet(pet); + return category; } public void updatePet(Pet pet) { diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java new file mode 100644 index 00000000..31eeee16 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -0,0 +1,15 @@ +package com.kcy.fitapet.domain.memo.service.component; + +import com.kcy.fitapet.domain.memo.service.module.MemoSaveService; +import com.kcy.fitapet.domain.memo.service.module.MemoSearchService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MemoManageService { + private final MemoSearchService memoSearchService; + private final MemoSaveService memoSaveService; +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java new file mode 100644 index 00000000..10f3a900 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.domain.memo.service.module; + +import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class MemoSaveService { + private final MemoRepository memoRepository; +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java new file mode 100644 index 00000000..5ee3ac83 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.domain.memo.service.module; + +import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +@RequiredArgsConstructor +public class MemoSearchService { + private final MemoRepository memoRepository; +} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java index 002d99b1..563f9293 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/component/PetManageService.java @@ -6,6 +6,7 @@ import com.kcy.fitapet.domain.member.service.module.MemberSaveService; import com.kcy.fitapet.domain.member.service.module.MemberSearchService; import com.kcy.fitapet.domain.member.type.ManageType; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.domain.pet.dto.PetInfoRes; import com.kcy.fitapet.domain.pet.service.module.PetSaveService; @@ -31,7 +32,7 @@ public class PetManageService { public void savePet(Pet pet, Long memberId) { pet = petSaveService.savePet(pet); - // TODO : 반려동물 default 부모 카테고리 생성 + MemoCategory.ofRootInstance(pet.getPetName(), pet); Member member = memberSearchService.findById(memberId); memberSaveService.mappingMemberAndPet(member, pet, ManageType.MASTER); From 8ec00ea99477e5286363c5dfe29a6a2d72d7bee9 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Sat, 27 Jan 2024 21:58:19 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20#83=201=EC=B0=A8=20URL=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fitapet/domain/member/api/AccountApi.java | 12 +- .../kcy/fitapet/domain/memo/api/MemoApi.java | 143 +++++++++++++++++- .../domain/memo/domain/MemoCategory.java | 8 +- .../domain/schedule/dto/ScheduleInfoDto.java | 1 - .../authorization/ManagerAuthorize.java | 2 + .../global/config/redis/CacheConfig.java | 26 ++++ .../global/config/redis/RedisConfig.java | 12 +- .../redis/annotation/ManagerCacheManager.java | 13 ++ .../{ => annotation}/OidcCacheManager.java | 2 +- .../RedisCacheConnectionFactory.java | 2 +- .../SecurityUserCacheManager.java | 2 +- 11 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/global/config/redis/annotation/ManagerCacheManager.java rename src/main/java/com/kcy/fitapet/global/config/redis/{ => annotation}/OidcCacheManager.java (85%) rename src/main/java/com/kcy/fitapet/global/config/redis/{ => annotation}/RedisCacheConnectionFactory.java (86%) rename src/main/java/com/kcy/fitapet/global/config/redis/{ => annotation}/SecurityUserCacheManager.java (86%) diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java index f4046681..7ba47483 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java @@ -46,8 +46,8 @@ public ResponseEntity getProfile(@PathVariable("id") Long id) { } @Operation(summary = "닉네임 존재 확인") - @GetMapping("/exists") @Parameter(name = "uid", description = "확인할 유저 닉네임", in = ParameterIn.QUERY, required = true) + @GetMapping("/exists") @PreAuthorize("isAnonymous()") public ResponseEntity getExistsUid(@RequestParam("uid") @NotBlank String uid) { boolean exists = memberAccountService.existsUid(uid); @@ -73,11 +73,11 @@ public ResponseEntity putProfile( } @Operation(summary = "ID/PW 찾기") - @PostMapping("/search") @Parameters({ @Parameter(name = "type", description = "찾을 타입", example = "uid/password", in = ParameterIn.QUERY, required = true), @Parameter(name = "code", description = "인증번호", in = ParameterIn.QUERY, required = true), }) + @PostMapping("/search") @PreAuthorize("isAnonymous()") public ResponseEntity postSearchIdOrPassword( @RequestParam("type") @NotBlank SmsPrefix type, @@ -107,4 +107,12 @@ public ResponseEntity putNotify( memberAccountService.updateNotification(id, user.getUserId(), type); return ResponseEntity.ok(SuccessResponse.noContent()); } + + @Operation(summary = "관리 중인 반려동물의 모든 메모 카테고리 조회") + @Parameter(name = "id", description = "조회할 프로필 ID", in = ParameterIn.PATH, required = true) + @GetMapping("/{id}/memo-categories") + @PreAuthorize("isAuthenticated() and #id == principal.userId") + public ResponseEntity getMemoCategories(@PathVariable("id") Long id) { + return null; + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index c0fb08ce..9f2b1011 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -1,17 +1,152 @@ package com.kcy.fitapet.domain.memo.api; import com.kcy.fitapet.domain.memo.service.component.MemoManageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; -@Tag(name = "일기 API", description = "일기 관리 API") +@Tag(name = "메모 API", description = "메모 관리 API") @Slf4j @RestController @RequiredArgsConstructor -@RequestMapping("/api/v2/users/{user_id}") +@RequestMapping("/api/v2/pets/{pet_id}") public class MemoApi { private final MemoManageService memoManageService; + + @Operation(summary = "서브 메모 카테고리 저장") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + }) + @PostMapping("/root-memo-categories/{root_memo_category_id}") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + public ResponseEntity saveSubMemoCategory( + @PathVariable("pet_id") Long petId, + @PathVariable("root_memo_category_id") Long rootMemoCategoryId + ) { + return null; + } + + @Operation(summary = "루트 카테고리의 서브 메모리 카테고리 리스트 조회") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + }) + @GetMapping("/root-memo-categories/{root_memo_category_id}") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 + public ResponseEntity getSubMemoCategories( + @PathVariable("pet_id") Long petId, + @PathVariable("root_memo_category_id") Long rootMemoCategoryId + ) { + return null; + } + + @Operation(summary = "서브 메모 카테고리 삭제") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "sub_memo_category_id", description = "서브 메모 카테고리 ID를 의미하며, 루트 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + }) + @DeleteMapping("/sub-memo-categories/{sub_memo_category_id}") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet -> sub-memo-categories 권한 검사 + public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petId, @PathVariable("sub_memo_category_id") Long subMemoCategoryId) { + return null; + } + + @Operation(summary = "폴더 안의 메모 리스트 조회 및 검색", description = """ + 타입이 root인 경우, sub category 리스트는 별도로 조회해야 합니다. `/api/v2/pets/{pet_id}/root-memo-categories/{root_memo_category_id}` + search에 검색어를 입력하면 해당 검색어를 포함하는 메모 리스트를 조회합니다. (빈 문자열인 경우, 전체 메모 리스트를 조회합니다.) + """) + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_category_id", description = "메모 카테고리 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "search", description = "검색어", in = ParameterIn.QUERY), + @Parameter(name = "size", description = "페이지 사이즈", example = "5", in = ParameterIn.QUERY), + @Parameter(name = "page", description = "페이지 번호", example = "1", in = ParameterIn.QUERY), + @Parameter(name = "sort", description = "정렬 기준", example = "createdAt", in = ParameterIn.QUERY), + @Parameter(name = "direction", description = "정렬 방식", example = "DESC" , in = ParameterIn.QUERY) + }) + @GetMapping("/memo-categories/{memo_category_id}/memos") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id 검사 + public ResponseEntity getMemosAndSubCategories( + @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, + @RequestParam(value = "search", defaultValue = "", required = false) String search, + @PageableDefault(size = 5, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + ) { + return null; + } + + /* + * 메모 API + */ + + @Operation(summary = "반려동물에게 등록된 메모 리스트 조회") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "size", description = "페이지 사이즈", example = "5", in = ParameterIn.QUERY), + @Parameter(name = "page", description = "페이지 번호", example = "1", in = ParameterIn.QUERY), + @Parameter(name = "sort", description = "정렬 기준", example = "createdAt", in = ParameterIn.QUERY), + @Parameter(name = "direction", description = "정렬 방식", example = "DESC" , in = ParameterIn.QUERY) + }) + @GetMapping("/memos") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + public ResponseEntity getMemosByPet( + @PathVariable("pet_id") Long petId, + @PageableDefault(size = 5, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + ) { + return null; + } + + @Operation(summary = "메모 단건 조회") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_category_id", description = "메모 카테고리 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) + }) + @GetMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id && memo_category_id -> memo_id 체크 + public ResponseEntity getMemo( + @PathVariable("pet_id") Long petId, + @PathVariable("memo_category_id") Long memoCategoryId, + @PathVariable("memo_id") Long memoId + ) { + return null; + } + + @Operation(summary = "메모 작성") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_category_id", description = "서브 메모 카테고리 ID를 의미하며, 루트 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + }) + @PostMapping("/memo-categories/{memo_category_id}/memos") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId) { + return null; + } + + @Operation(summary = "메모 삭제") + @Parameters({ + @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_category_id", description = "메모 카테고리 ID", in = ParameterIn.PATH, required = true), + @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) + }) + @DeleteMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") + @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id && memo_category_id -> memo_id 체크 + public ResponseEntity deleteMemo( + @PathVariable("pet_id") Long petId, + @PathVariable("memo_category_id") Long memoCategoryId, + @PathVariable("memo_id") Long memoId + ) { + return null; + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java index 87596c17..1f505d97 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java @@ -47,7 +47,7 @@ public static MemoCategory ofRootInstance(String categoryName, Pet pet) { return category; } - public void updatePet(Pet pet) { + private void updatePet(Pet pet) { if (this.pet != null) this.pet.getMemoCategories().remove(this); @@ -55,11 +55,15 @@ public void updatePet(Pet pet) { pet.getMemoCategories().add(this); } - public void updateParent(MemoCategory parent) { + private void updateParent(MemoCategory parent) { if (this.parent != null) this.parent.getChildren().remove(this); this.parent = parent; parent.getChildren().add(this); } + + public boolean isRootCategory() { + return this.parent == null; + } } diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleInfoDto.java b/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleInfoDto.java index 88d49e40..9fd9010e 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleInfoDto.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleInfoDto.java @@ -32,7 +32,6 @@ public record ScheduleInfo( @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonProperty("reservationDate") LocalDateTime reservationDt, - @JsonInclude(JsonInclude.Include.NON_EMPTY) List pets ) { public static ScheduleInfo from(Schedule schedule, List pets) { diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java index 10ebc0e6..072ebe8d 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; @Component("managerAuthorize") @@ -11,6 +12,7 @@ public class ManagerAuthorize { private final ManagerJpaRepository managerRepository; + @Cacheable(value = "manager", key = "#memberId + '@' + #petId", unless = "#result == false", cacheManager = "managerCacheManager") public boolean isManager(Long memberId, Long petId) { return managerRepository.existsByMember_IdAndPet_Id(memberId, petId); } diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/CacheConfig.java b/src/main/java/com/kcy/fitapet/global/config/redis/CacheConfig.java index 3c6d50d1..d8e5ce80 100644 --- a/src/main/java/com/kcy/fitapet/global/config/redis/CacheConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/redis/CacheConfig.java @@ -1,5 +1,9 @@ package com.kcy.fitapet.global.config.redis; +import com.kcy.fitapet.global.config.redis.annotation.ManagerCacheManager; +import com.kcy.fitapet.global.config.redis.annotation.OidcCacheManager; +import com.kcy.fitapet.global.config.redis.annotation.RedisCacheConnectionFactory; +import com.kcy.fitapet.global.config.redis.annotation.SecurityUserCacheManager; import lombok.RequiredArgsConstructor; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; @@ -62,6 +66,28 @@ public CacheManager securityUserCacheManager(RedisConnectionFactory redisConnect .build(); } + @Bean + @ManagerCacheManager + public CacheManager managerCacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .disableCachingNullValues() + .entryTtl(Duration.ofMinutes(3)) + .computePrefixWith(CacheKeyPrefix.simple()) + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()) + ) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()) + ); + Map redisCacheConfigurationMap = Map.of("managerConfig", config); + + return RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(config) + .withInitialCacheConfigurations(redisCacheConfigurationMap) + .build(); + } + @Bean @OidcCacheManager public CacheManager oidcCacheManger(RedisConnectionFactory cf) { diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/RedisConfig.java b/src/main/java/com/kcy/fitapet/global/config/redis/RedisConfig.java index 0425ba17..7c0cfee1 100644 --- a/src/main/java/com/kcy/fitapet/global/config/redis/RedisConfig.java +++ b/src/main/java/com/kcy/fitapet/global/config/redis/RedisConfig.java @@ -1,13 +1,9 @@ package com.kcy.fitapet.global.config.redis; +import com.kcy.fitapet.global.config.redis.annotation.RedisCacheConnectionFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.data.redis.cache.RedisCacheConfiguration; -import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; @@ -15,15 +11,9 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; -import java.sql.SQLException; -import java.time.Duration; - @Configuration @EnableRedisRepositories @EnableTransactionManagement diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/annotation/ManagerCacheManager.java b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/ManagerCacheManager.java new file mode 100644 index 00000000..c6c078da --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/ManagerCacheManager.java @@ -0,0 +1,13 @@ +package com.kcy.fitapet.global.config.redis.annotation; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, + ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier("managerCacheManager") +public @interface ManagerCacheManager { +} diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/OidcCacheManager.java b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/OidcCacheManager.java similarity index 85% rename from src/main/java/com/kcy/fitapet/global/config/redis/OidcCacheManager.java rename to src/main/java/com/kcy/fitapet/global/config/redis/annotation/OidcCacheManager.java index e8aa1c83..367c8f58 100644 --- a/src/main/java/com/kcy/fitapet/global/config/redis/OidcCacheManager.java +++ b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/OidcCacheManager.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.config.redis; +package com.kcy.fitapet.global.config.redis.annotation; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/RedisCacheConnectionFactory.java b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/RedisCacheConnectionFactory.java similarity index 86% rename from src/main/java/com/kcy/fitapet/global/config/redis/RedisCacheConnectionFactory.java rename to src/main/java/com/kcy/fitapet/global/config/redis/annotation/RedisCacheConnectionFactory.java index 2951eec7..38b159d8 100644 --- a/src/main/java/com/kcy/fitapet/global/config/redis/RedisCacheConnectionFactory.java +++ b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/RedisCacheConnectionFactory.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.config.redis; +package com.kcy.fitapet.global.config.redis.annotation; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/src/main/java/com/kcy/fitapet/global/config/redis/SecurityUserCacheManager.java b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/SecurityUserCacheManager.java similarity index 86% rename from src/main/java/com/kcy/fitapet/global/config/redis/SecurityUserCacheManager.java rename to src/main/java/com/kcy/fitapet/global/config/redis/annotation/SecurityUserCacheManager.java index 67142b8a..4f60b4f3 100644 --- a/src/main/java/com/kcy/fitapet/global/config/redis/SecurityUserCacheManager.java +++ b/src/main/java/com/kcy/fitapet/global/config/redis/annotation/SecurityUserCacheManager.java @@ -1,4 +1,4 @@ -package com.kcy.fitapet.global.config.redis; +package com.kcy.fitapet.global.config.redis.annotation; import org.springframework.beans.factory.annotation.Qualifier; From 8bbfec910f55828eefab208e564d0dbd4313d353 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Sun, 28 Jan 2024 12:32:23 +0900 Subject: [PATCH 09/18] =?UTF-8?q?feat:=20#83=20memo=20api=20preAuthorize?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 20 +++++------ .../memo/dao/MemoCategoryRepository.java | 11 +++++++ .../domain/memo/dao/MemoImageRepository.java | 7 ++++ .../domain/memo/dao/MemoRepository.java | 1 + .../DefaultSearchQueryDslRepositoryImpl.java | 6 ++++ .../security/authorization/MemoAuthorize.java | 33 +++++++++++++++++++ 6 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoImageRepository.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/security/authorization/MemoAuthorize.java diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index 9f2b1011..0abcdf09 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -29,7 +29,7 @@ public class MemoApi { @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) @PostMapping("/root-memo-categories/{root_memo_category_id}") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(rootMemoCategoryId, petId)") public ResponseEntity saveSubMemoCategory( @PathVariable("pet_id") Long petId, @PathVariable("root_memo_category_id") Long rootMemoCategoryId @@ -43,7 +43,7 @@ public ResponseEntity saveSubMemoCategory( @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) @GetMapping("/root-memo-categories/{root_memo_category_id}") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(rootMemoCategoryId, petId)") public ResponseEntity getSubMemoCategories( @PathVariable("pet_id") Long petId, @PathVariable("root_memo_category_id") Long rootMemoCategoryId @@ -57,7 +57,7 @@ public ResponseEntity getSubMemoCategories( @Parameter(name = "sub_memo_category_id", description = "서브 메모 카테고리 ID를 의미하며, 루트 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) @DeleteMapping("/sub-memo-categories/{sub_memo_category_id}") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet -> sub-memo-categories 권한 검사 + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidSubMemoCategory(subMemoCategoryId, petId)") public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petId, @PathVariable("sub_memo_category_id") Long subMemoCategoryId) { return null; } @@ -77,11 +77,11 @@ public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petI @Parameter(name = "direction", description = "정렬 방식", example = "DESC" , in = ParameterIn.QUERY) }) @GetMapping("/memo-categories/{memo_category_id}/memos") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id 검사 + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(memoCategoryId, petId)") public ResponseEntity getMemosAndSubCategories( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, @RequestParam(value = "search", defaultValue = "", required = false) String search, - @PageableDefault(size = 5, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + @PageableDefault(size = 15, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { return null; } @@ -99,7 +99,7 @@ public ResponseEntity getMemosAndSubCategories( @Parameter(name = "direction", description = "정렬 방식", example = "DESC" , in = ParameterIn.QUERY) }) @GetMapping("/memos") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId)") public ResponseEntity getMemosByPet( @PathVariable("pet_id") Long petId, @PageableDefault(size = 5, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable @@ -114,7 +114,7 @@ public ResponseEntity getMemosByPet( @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) }) @GetMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id && memo_category_id -> memo_id 체크 + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(memoCategoryId, memoId, petId)") public ResponseEntity getMemo( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, @@ -126,10 +126,10 @@ public ResponseEntity getMemo( @Operation(summary = "메모 작성") @Parameters({ @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), - @Parameter(name = "memo_category_id", description = "서브 메모 카테고리 ID를 의미하며, 루트 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + @Parameter(name = "memo_category_id", in = ParameterIn.PATH, required = true) }) @PostMapping("/memo-categories/{memo_category_id}/memos") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(memoCategoryId, petId)") public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId) { return null; } @@ -141,7 +141,7 @@ public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVaria @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) }) @DeleteMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") - @PreAuthorize("isAuthenticated() && @managerAuthorize.isManager(principal.userId, #petId)") // TODO: 2024-01-27: pet_id -> memo_category_id && memo_category_id -> memo_id 체크 + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(memoCategoryId, memoId, petId)") public ResponseEntity deleteMemo( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java new file mode 100644 index 00000000..10ee8277 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java @@ -0,0 +1,11 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; + +public interface MemoCategoryRepository extends ExtendedRepository { + boolean existsByIdAndPet_Id(Long memoCategoryId, Long petId); + boolean existsByIdAndPet_IdAndParentIsNull(Long memoCategoryId, Long petId); + boolean existsByIdAndPet_IdAndParentIsNotNull(Long memoCategoryId, Long petId); + +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoImageRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoImageRepository.java new file mode 100644 index 00000000..c89728cf --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoImageRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.domain.MemoImage; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; + +public interface MemoImageRepository extends ExtendedRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java index b5904386..5fc256f4 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java @@ -4,4 +4,5 @@ import com.kcy.fitapet.global.common.repository.ExtendedRepository; public interface MemoRepository extends ExtendedRepository { + boolean existsByIdAndMemoCategory_Id(Long memoId, Long memoCategoryId); } diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java index dbde50c8..608c390e 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepositoryImpl.java @@ -70,6 +70,9 @@ public

Page

selectPage(Predicate predicate, Class

type, Map(query.select(Projections.bean(type, bindings)).fetch(), pageable, totalSize); } + /** + * 파라미터를 기반으로 Querydsl의 JPAQuery를 생성합니다. + */ private JPAQuery buildWithoutSelect(Predicate predicate, Map> bindings, QueryHandler queryHandler, Sort sort) { JPAQuery query = queryFactory.from(path); @@ -80,6 +83,9 @@ private JPAQuery buildWithoutSelect(Predicate predicate, Map query) { if (predicate != null) query.where(predicate); } diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authorization/MemoAuthorize.java b/src/main/java/com/kcy/fitapet/global/common/security/authorization/MemoAuthorize.java new file mode 100644 index 00000000..53988428 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/security/authorization/MemoAuthorize.java @@ -0,0 +1,33 @@ +package com.kcy.fitapet.global.common.security.authorization; + +import com.kcy.fitapet.domain.memo.dao.MemoCategoryRepository; +import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component("memoAuthorize") +@RequiredArgsConstructor +public class MemoAuthorize { + private final MemoRepository memoRepository; + private final MemoCategoryRepository memoCategoryRepository; + + public boolean isValidMemoCategory(Long memoCategoryId, Long petId) { + return memoCategoryRepository.existsByIdAndPet_Id(memoCategoryId, petId); + } + + public boolean isValidRootMemoCategory(Long memoCategoryId, Long petId) { + return memoCategoryRepository.existsByIdAndPet_IdAndParentIsNull(memoCategoryId, petId); + } + + public boolean isValidSubMemoCategory(Long memoCategoryId, Long petId) { + return memoCategoryRepository.existsByIdAndPet_IdAndParentIsNotNull(memoCategoryId, petId); + } + + public boolean isValidMemoCategoryAndMemo(Long memoCategoryId, Long memoId, Long petId) { + return isValidMemoCategory(memoCategoryId, petId) && isValidMemo(memoId, memoCategoryId); + } + + private boolean isValidMemo(Long memoId, Long memoCategoryId) { + return memoRepository.existsByIdAndMemoCategory_Id(memoId, memoCategoryId); + } +} From 3c612a0b99e3df08bd16c680efa132e06b9190c1 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Sun, 28 Jan 2024 23:14:15 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20#83=20memo=20category=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=B0=8F=20sub=20category=20=EC=A0=80=EC=9E=A5=20A?= =?UTF-8?q?PI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 35 ++++---- .../dao/MemoCategoryQueryDslRepository.java | 10 +++ .../MemoCategoryQueryDslRepositoryImpl.java | 53 ++++++++++++ .../memo/dao/MemoCategoryRepository.java | 2 +- .../domain/memo/dto/MemoCategoryInfoDto.java | 83 +++++++++++++++++++ .../memo/dto/SubMemoCategorySaveReq.java | 19 +++++ .../domain/memo/exception/MemoErrorCode.java | 23 +++++ .../service/component/MemoManageService.java | 19 +++++ .../memo/service/module/MemoSaveService.java | 9 ++ .../service/module/MemoSearchService.java | 51 +++++++++++- .../domain/memo/type/MemoCategoryType.java | 12 +++ .../DefaultSearchQueryDslRepository.java | 1 + 12 files changed, 301 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dto/MemoCategoryInfoDto.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dto/SubMemoCategorySaveReq.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/type/MemoCategoryType.java diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index 0abcdf09..f0d832fa 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -1,13 +1,18 @@ package com.kcy.fitapet.domain.memo.api; +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.component.MemoManageService; +import com.kcy.fitapet.global.common.response.SuccessResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpStatus; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -29,26 +34,28 @@ public class MemoApi { @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) @PostMapping("/root-memo-categories/{root_memo_category_id}") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(rootMemoCategoryId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(#rootMemoCategoryId, #petId)") public ResponseEntity saveSubMemoCategory( @PathVariable("pet_id") Long petId, - @PathVariable("root_memo_category_id") Long rootMemoCategoryId + @PathVariable("root_memo_category_id") Long rootMemoCategoryId, + @RequestBody @Valid SubMemoCategorySaveReq req ) { - return null; + memoManageService.saveSubMemoCategory(petId, rootMemoCategoryId, req); + return ResponseEntity.status(HttpStatus.SC_CREATED).body(SuccessResponse.noContent()); } - @Operation(summary = "루트 카테고리의 서브 메모리 카테고리 리스트 조회") + @Operation(summary = "메모 카테고리 리스트 조회", description = "메모 카테고리 타입이 root인 경우, 서브 메모 카테고리 리스트도 함께 조회합니다.") @Parameters({ @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), - @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) + @Parameter(name = "memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) - @GetMapping("/root-memo-categories/{root_memo_category_id}") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(rootMemoCategoryId, petId)") + @GetMapping("/memo-categories/{memo_category_id}") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(#memoCategoryId, #petId)") public ResponseEntity getSubMemoCategories( @PathVariable("pet_id") Long petId, - @PathVariable("root_memo_category_id") Long rootMemoCategoryId + @PathVariable("memo_category_id") Long memoCategoryId ) { - return null; + return ResponseEntity.ok(SuccessResponse.from(memoManageService.findCategoryById(memoCategoryId))); } @Operation(summary = "서브 메모 카테고리 삭제") @@ -57,7 +64,7 @@ public ResponseEntity getSubMemoCategories( @Parameter(name = "sub_memo_category_id", description = "서브 메모 카테고리 ID를 의미하며, 루트 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) @DeleteMapping("/sub-memo-categories/{sub_memo_category_id}") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidSubMemoCategory(subMemoCategoryId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidSubMemoCategory(#subMemoCategoryId, #petId)") public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petId, @PathVariable("sub_memo_category_id") Long subMemoCategoryId) { return null; } @@ -77,7 +84,7 @@ public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petI @Parameter(name = "direction", description = "정렬 방식", example = "DESC" , in = ParameterIn.QUERY) }) @GetMapping("/memo-categories/{memo_category_id}/memos") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(memoCategoryId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(#memoCategoryId, #petId)") public ResponseEntity getMemosAndSubCategories( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, @RequestParam(value = "search", defaultValue = "", required = false) String search, @@ -114,7 +121,7 @@ public ResponseEntity getMemosByPet( @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) }) @GetMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(memoCategoryId, memoId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(#memoCategoryId, #memoId, #petId)") public ResponseEntity getMemo( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, @@ -129,7 +136,7 @@ public ResponseEntity getMemo( @Parameter(name = "memo_category_id", in = ParameterIn.PATH, required = true) }) @PostMapping("/memo-categories/{memo_category_id}/memos") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(memoCategoryId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(#memoCategoryId, #petId)") public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId) { return null; } @@ -141,7 +148,7 @@ public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVaria @Parameter(name = "memo_id", description = "메모 ID", in = ParameterIn.PATH, required = true) }) @DeleteMapping("/memo-categories/{memo_category_id}/memos/{memo_id}") - @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(memoCategoryId, memoId, petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategoryAndMemo(#memoCategoryId, #memoId, #petId)") public ResponseEntity deleteMemo( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java new file mode 100644 index 00000000..2fa33a00 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; + +import java.util.List; + +public interface MemoCategoryQueryDslRepository { + MemoCategoryInfoDto.MemoCategoryQueryDslRes findMemoCategoryById(Long memoCategoryId); + List findMemoCategoriesByParent(Long parentId); +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java new file mode 100644 index 00000000..8ec2eaa7 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java @@ -0,0 +1,53 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.domain.QMemo; +import com.kcy.fitapet.domain.memo.domain.QMemoCategory; +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class MemoCategoryQueryDslRepositoryImpl implements MemoCategoryQueryDslRepository { + private final JPAQueryFactory queryFactory; + private final QMemoCategory memoCategory = QMemoCategory.memoCategory; + private final QMemo memo = QMemo.memo; + + @Override + public MemoCategoryInfoDto.MemoCategoryQueryDslRes findMemoCategoryById(Long memoCategoryId) { + return queryFactory.select( + Projections.constructor( + MemoCategoryInfoDto.MemoCategoryQueryDslRes.class, + memoCategory.id, + memoCategory.categoryName, + memoCategory.parent.id, + memoCategory.id.count() + )) + .from(memoCategory) + .leftJoin(memo).on(memo.memoCategory.id.eq(memoCategory.id)) + .where(memoCategory.id.eq(memoCategoryId)) + .groupBy(memoCategory.id, memoCategory.categoryName) + .fetchFirst(); + } + + @Override + public List findMemoCategoriesByParent(Long parentId) { + return queryFactory.select( + Projections.constructor( + MemoCategoryInfoDto.MemoCategoryQueryDslRes.class, + memoCategory.id, + memoCategory.categoryName, + memoCategory.parent.id, + memoCategory.id.count() + )) + .from(memoCategory) + .leftJoin(memo).on(memo.memoCategory.id.eq(memoCategory.id)) + .where(memoCategory.parent.id.eq(parentId)) + .groupBy(memoCategory.id, memoCategory.categoryName) + .fetch(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java index 10ee8277..5d8c99b8 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryRepository.java @@ -3,7 +3,7 @@ import com.kcy.fitapet.domain.memo.domain.MemoCategory; import com.kcy.fitapet.global.common.repository.ExtendedRepository; -public interface MemoCategoryRepository extends ExtendedRepository { +public interface MemoCategoryRepository extends ExtendedRepository, MemoCategoryQueryDslRepository { boolean existsByIdAndPet_Id(Long memoCategoryId, Long petId); boolean existsByIdAndPet_IdAndParentIsNull(Long memoCategoryId, Long petId); boolean existsByIdAndPet_IdAndParentIsNotNull(Long memoCategoryId, Long petId); diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoCategoryInfoDto.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoCategoryInfoDto.java new file mode 100644 index 00000000..0e7f2251 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoCategoryInfoDto.java @@ -0,0 +1,83 @@ +package com.kcy.fitapet.domain.memo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.domain.memo.type.MemoCategoryType; +import com.kcy.fitapet.global.common.util.bind.Dto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class MemoCategoryInfoDto { + private final List memoCategories; + + private MemoCategoryInfoDto(List memoCategories) { + this.memoCategories = memoCategories; + } + + public static MemoCategoryInfoDto fromMemoCategory(List memoCategories) { + return new MemoCategoryInfoDto(memoCategories); + } + + @Schema(description = "메모 카테고리 정보") + @Builder + @Dto(name = "memoCategory") + public record MemoCategoryInfo( + @Schema(description = "메모 카테고리 ID") + Long memoCategoryId, + @Schema(description = "메모 카테고리 이름") + String memoCategoryName, + @Schema(description = "메모 카테고리 타입 (root: 상위 카테고리, sub: 하위 카테고리)") + MemoCategoryType type, + @Schema(description = "메모 카테고리에 속한 메모 개수") + Long totalMemoCount, + @Schema(description = "하위 메모 카테고리 리스트. 메모 카테고리 타입이 root일 경우에만 존재합니다.") + @JsonInclude(JsonInclude.Include.NON_NULL) + List subMemoCategories + ) { + /** + * 단일 MemoCategoryQueryDslRes 객체를 MemoCategoryInfo 객체로 변환합니다.
+ * SubMemoCategorySaveReq 객체를 생성할 때 사용합니다. + */ + public static MemoCategoryInfo from(MemoCategoryQueryDslRes res) { + return MemoCategoryInfo.builder() + .memoCategoryId(res.memoCategoryId()) + .memoCategoryName(res.memoCategoryName()) + .type(res.parentId() == null ? MemoCategoryType.ROOT : MemoCategoryType.SUB) + .subMemoCategories(res.parentId() == null ? new ArrayList<>() : null) + .totalMemoCount(res.totalMemoCount()) + .build(); + } + + /** + * Root MemoCategoryQueryDslRes 객체와 Sub MemoCategoryQueryDslRes 객체 리스트를 MemoCategoryInfo 객체로 변환합니다.
+ */ + public static MemoCategoryInfo ofRootInstance(MemoCategoryQueryDslRes rootMemoCategory, List subMemoCategories) { + return MemoCategoryInfo.builder() + .memoCategoryId(rootMemoCategory.memoCategoryId()) + .memoCategoryName(rootMemoCategory.memoCategoryName()) + .type(MemoCategoryType.ROOT) + .totalMemoCount(rootMemoCategory.totalMemoCount() + subMemoCategories.stream().mapToLong(MemoCategoryQueryDslRes::totalMemoCount).sum()) + .subMemoCategories(subMemoCategories.stream().map(MemoCategoryInfo::from).toList()) + .build(); + } + } + + public record MemoCategoryQueryDslRes( + Long memoCategoryId, + String memoCategoryName, + Long parentId, + Long totalMemoCount + ) { + public MemoCategoryQueryDslRes(Long memoCategoryId, String memoCategoryName, Long parentId, Long totalMemoCount) { + this.memoCategoryId = memoCategoryId; + this.memoCategoryName = memoCategoryName; + this.parentId = parentId; + this.totalMemoCount = totalMemoCount; + } + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/SubMemoCategorySaveReq.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/SubMemoCategorySaveReq.java new file mode 100644 index 00000000..cc794458 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/SubMemoCategorySaveReq.java @@ -0,0 +1,19 @@ +package com.kcy.fitapet.domain.memo.dto; + +import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.domain.pet.domain.Pet; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +@Schema(description = "서브 메모 카테고리 저장 요청") +public record SubMemoCategorySaveReq( + @Schema(description = "하위 메모 카테고리 이름") + @NotBlank(message = "하위 메모 카테고리 이름은 필수입니다.") + @Size(max = 20, message = "하위 메모 카테고리 이름은 20자 이하로 입력해주세요.") + String subMemoCategoryName +) { + public MemoCategory toEntity(MemoCategory parent, Pet pet) { + return MemoCategory.ofChildrenInstance(subMemoCategoryName, parent, pet); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java b/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java new file mode 100644 index 00000000..76c86e5e --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java @@ -0,0 +1,23 @@ +package com.kcy.fitapet.domain.memo.exception; + +import com.kcy.fitapet.global.common.response.code.StatusCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum MemoErrorCode implements StatusCode { + + /* 404 NOT_FOUND */ + MEMO_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "메모 카테고리를 찾을 수 없습니다."), + ; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public String getName() { + return name(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java index 31eeee16..fe2ec21a 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -1,15 +1,34 @@ package com.kcy.fitapet.domain.memo.service.component; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.module.MemoSaveService; import com.kcy.fitapet.domain.memo.service.module.MemoSearchService; +import com.kcy.fitapet.domain.pet.service.module.PetSearchService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; @Slf4j @Service @RequiredArgsConstructor public class MemoManageService { + private final PetSearchService petSearchService; + private final MemoSearchService memoSearchService; private final MemoSaveService memoSaveService; + + @Transactional + public void saveSubMemoCategory(Long petId, Long rootMemoCategoryId, SubMemoCategorySaveReq req) { + req.toEntity(memoSearchService.findMemoCategoryById(rootMemoCategoryId), petSearchService.findPetById(petId)); + } + + @Transactional(readOnly = true) + public MemoCategoryInfoDto.MemoCategoryInfo findCategoryById(Long memoCategoryId) { + return memoSearchService.findMemoCategoryWithMemoCount(memoCategoryId); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java index 10f3a900..f1f385a9 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java @@ -1,6 +1,9 @@ package com.kcy.fitapet.domain.memo.service.module; +import com.kcy.fitapet.domain.memo.dao.MemoCategoryRepository; +import com.kcy.fitapet.domain.memo.dao.MemoImageRepository; import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -10,4 +13,10 @@ @RequiredArgsConstructor public class MemoSaveService { private final MemoRepository memoRepository; + private final MemoCategoryRepository memoCategoryRepository; + private final MemoImageRepository memoImageRepository; + + public void saveMemoCategory(MemoCategory memoCategory) { + memoCategoryRepository.save(memoCategory); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java index 5ee3ac83..791508c6 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -1,13 +1,62 @@ package com.kcy.fitapet.domain.memo.service.module; +import com.kcy.fitapet.domain.memo.dao.MemoCategoryRepository; +import com.kcy.fitapet.domain.memo.dao.MemoImageRepository; import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.domain.memo.domain.QMemo; +import com.kcy.fitapet.domain.memo.domain.QMemoCategory; +import com.kcy.fitapet.domain.memo.domain.QMemoImage; +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.exception.MemoErrorCode; +import com.kcy.fitapet.global.common.repository.QueryHandler; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.Expressions; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.querydsl.core.group.GroupBy.groupBy; @Service @Slf4j @RequiredArgsConstructor public class MemoSearchService { private final MemoRepository memoRepository; -} + private final MemoCategoryRepository memoCategoryRepository; + private final MemoImageRepository memoImageRepository; + + private final QMemoCategory memoCategory = QMemoCategory.memoCategory; + private final QMemo memo = QMemo.memo; + private final QMemoImage memoImage = QMemoImage.memoImage; + + @Transactional(readOnly = true) + public MemoCategory findMemoCategoryById(Long memoCategoryId) { + return memoCategoryRepository.findByIdOrElseThrow(memoCategoryId); + } + + @Transactional(readOnly = true) + public MemoCategoryInfoDto.MemoCategoryInfo findMemoCategoryWithMemoCount(Long memoCategoryId) { + MemoCategoryInfoDto.MemoCategoryQueryDslRes dto = memoCategoryRepository.findMemoCategoryById(memoCategoryId); + log.info("dto: {}", dto); + + if (dto == null) throw new GlobalErrorException(MemoErrorCode.MEMO_CATEGORY_NOT_FOUND); + if (dto.parentId() == null) { + List subMemoCategories = memoCategoryRepository.findMemoCategoriesByParent(dto.memoCategoryId()); + + return MemoCategoryInfoDto.MemoCategoryInfo.ofRootInstance(dto, subMemoCategories); + } + + return MemoCategoryInfoDto.MemoCategoryInfo.from(dto); + } +} \ No newline at end of file diff --git a/src/main/java/com/kcy/fitapet/domain/memo/type/MemoCategoryType.java b/src/main/java/com/kcy/fitapet/domain/memo/type/MemoCategoryType.java new file mode 100644 index 00000000..d7c40c0a --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/type/MemoCategoryType.java @@ -0,0 +1,12 @@ +package com.kcy.fitapet.domain.memo.type; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum MemoCategoryType { + ROOT, SUB; + + @JsonCreator + public static MemoCategoryType from(String s) { + return MemoCategoryType.valueOf(s.toUpperCase()); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java index 6084287e..1f6b850c 100644 --- a/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/global/common/repository/DefaultSearchQueryDslRepository.java @@ -14,6 +14,7 @@ * @author 양재서 */ public interface DefaultSearchQueryDslRepository { + /** * 검색 조건에 해당하는 도메인 리스트를 조회하는 메서드 * @param predicate : 검색 조건 From d18be1bc8f0e435157821deb31d8bf0c2314cfe3 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Mon, 29 Jan 2024 20:34:13 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20#83=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=9D=98=20=EB=AA=A8=EB=93=A0=20=EB=A9=94=EB=AA=A8=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...itory.java => CareCategoryRepository.java} | 2 +- ...epository.java => CareDateRepository.java} | 2 +- ...JpaRepository.java => CareRepository.java} | 2 +- .../care/service/module/CareSaveService.java | 12 ++++---- .../service/module/CareSearchService.java | 12 ++++---- .../fitapet/domain/member/api/AccountApi.java | 2 +- ...Repository.java => ManagerRepository.java} | 2 +- .../member/dao/MemberQueryDslRepository.java | 7 +++++ .../dao/MemberQueryDslRepositoryImpl.java | 29 +++++++++++++++++++ ...aRepository.java => MemberRepository.java} | 4 +-- .../component/MemberAccountService.java | 19 ++++++++++++ .../service/module/MemberSaveService.java | 8 ++--- .../service/module/MemberSearchService.java | 13 ++++++--- .../dao/MemoCategoryQueryDslRepository.java | 1 + .../MemoCategoryQueryDslRepositoryImpl.java | 11 +++++++ .../service/module/MemoSearchService.java | 16 ++++------ ...paRepository.java => OauthRepository.java} | 2 +- .../service/module/OauthSearchService.java | 4 +-- .../domain/pet/dao/PetQueryDslRepository.java | 4 +++ .../pet/dao/PetQueryDslRepositoryImpl.java | 10 +++++++ ...tJpaRepository.java => PetRepository.java} | 2 +- ...sitory.java => PetScheduleRepository.java} | 2 +- .../pet/service/module/PetSaveService.java | 8 ++--- .../pet/service/module/PetSearchService.java | 8 ++--- .../authentication/UserDetailServiceImpl.java | 4 +-- .../authorization/ManagerAuthorize.java | 4 +-- 26 files changed, 135 insertions(+), 55 deletions(-) rename src/main/java/com/kcy/fitapet/domain/care/dao/{CareCategoryJpaRepository.java => CareCategoryRepository.java} (72%) rename src/main/java/com/kcy/fitapet/domain/care/dao/{CareDateJpaRepository.java => CareDateRepository.java} (78%) rename src/main/java/com/kcy/fitapet/domain/care/dao/{CareJpaRepository.java => CareRepository.java} (67%) rename src/main/java/com/kcy/fitapet/domain/member/dao/{ManagerJpaRepository.java => ManagerRepository.java} (78%) create mode 100644 src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepositoryImpl.java rename src/main/java/com/kcy/fitapet/domain/member/dao/{MemberJpaRepository.java => MemberRepository.java} (70%) rename src/main/java/com/kcy/fitapet/domain/oauth/dao/{OauthJpaRepository.java => OauthRepository.java} (85%) create mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepositoryImpl.java rename src/main/java/com/kcy/fitapet/domain/pet/dao/{PetJpaRepository.java => PetRepository.java} (71%) rename src/main/java/com/kcy/fitapet/domain/pet/dao/{PetScheduleJpaRepository.java => PetScheduleRepository.java} (64%) diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java similarity index 72% rename from src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java index b85e9acb..60d713cf 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareCategoryRepository.java @@ -5,6 +5,6 @@ import java.util.List; -public interface CareCategoryJpaRepository extends ExtendedJpaRepository { +public interface CareCategoryRepository extends ExtendedJpaRepository { List findAllByPet_Id(Long petId); } diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java similarity index 78% rename from src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java index dc24e0dd..61597c87 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareDateRepository.java @@ -6,6 +6,6 @@ import java.util.List; -public interface CareDateJpaRepository extends ExtendedJpaRepository { +public interface CareDateRepository extends ExtendedJpaRepository { List findAllByCare_IdAndWeek(Long careId, WeekType week); } diff --git a/src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java similarity index 67% rename from src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java index c508900b..d25a6ce5 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/dao/CareJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/care/dao/CareRepository.java @@ -3,5 +3,5 @@ import com.kcy.fitapet.domain.care.domain.Care; import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; -public interface CareJpaRepository extends ExtendedJpaRepository { +public interface CareRepository extends ExtendedJpaRepository { } diff --git a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java index d4a92391..45253cdf 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSaveService.java @@ -1,8 +1,8 @@ package com.kcy.fitapet.domain.care.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; -import com.kcy.fitapet.domain.care.dao.CareDateJpaRepository; -import com.kcy.fitapet.domain.care.dao.CareJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; +import com.kcy.fitapet.domain.care.dao.CareDateRepository; +import com.kcy.fitapet.domain.care.dao.CareRepository; import com.kcy.fitapet.domain.care.domain.Care; import com.kcy.fitapet.domain.care.domain.CareCategory; import com.kcy.fitapet.domain.care.domain.CareDate; @@ -17,9 +17,9 @@ @RequiredArgsConstructor @Slf4j public class CareSaveService { - private final CareJpaRepository careRepository; - private final CareDateJpaRepository careDateRepository; - private final CareCategoryJpaRepository careCategoryRepository; + private final CareRepository careRepository; + private final CareDateRepository careDateRepository; + private final CareCategoryRepository careCategoryRepository; @Transactional public void saveCare(Care care) { diff --git a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java index 65ff5bd2..9e7475ff 100644 --- a/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/care/service/module/CareSearchService.java @@ -1,8 +1,8 @@ package com.kcy.fitapet.domain.care.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; -import com.kcy.fitapet.domain.care.dao.CareDateJpaRepository; -import com.kcy.fitapet.domain.care.dao.CareJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; +import com.kcy.fitapet.domain.care.dao.CareDateRepository; +import com.kcy.fitapet.domain.care.dao.CareRepository; import com.kcy.fitapet.domain.care.domain.Care; import com.kcy.fitapet.domain.care.domain.CareCategory; import com.kcy.fitapet.domain.care.domain.CareDate; @@ -17,9 +17,9 @@ @Service @RequiredArgsConstructor public class CareSearchService { - private final CareJpaRepository careRepository; - private final CareCategoryJpaRepository careCategoryRepository; - private final CareDateJpaRepository careDateRepository; + private final CareRepository careRepository; + private final CareCategoryRepository careCategoryRepository; + private final CareDateRepository careDateRepository; @Transactional(readOnly = true) public Care findCareById(Long careId) { diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java index 7ba47483..acdedd20 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java @@ -113,6 +113,6 @@ public ResponseEntity putNotify( @GetMapping("/{id}/memo-categories") @PreAuthorize("isAuthenticated() and #id == principal.userId") public ResponseEntity getMemoCategories(@PathVariable("id") Long id) { - return null; + return ResponseEntity.ok(SuccessResponse.from(memberAccountService.getMemoCategories(id))); } } diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java similarity index 78% rename from src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java index c15bdf0a..9058bb97 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/ManagerRepository.java @@ -5,7 +5,7 @@ import java.util.List; -public interface ManagerJpaRepository extends ExtendedJpaRepository { +public interface ManagerRepository extends ExtendedJpaRepository { boolean existsByMember_IdAndPet_Id(Long memberId, Long petId); List findAllByMember_Id(Long memberId); } diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepository.java new file mode 100644 index 00000000..3a715417 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepository.java @@ -0,0 +1,7 @@ +package com.kcy.fitapet.domain.member.dao; + +import java.util.List; + +public interface MemberQueryDslRepository { + List findMyPetIds(Long memberId); +} diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepositoryImpl.java new file mode 100644 index 00000000..2319edeb --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberQueryDslRepositoryImpl.java @@ -0,0 +1,29 @@ +package com.kcy.fitapet.domain.member.dao; + +import com.kcy.fitapet.domain.member.domain.QManager; +import com.kcy.fitapet.domain.member.domain.QMember; +import com.kcy.fitapet.domain.pet.domain.QPet; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class MemberQueryDslRepositoryImpl implements MemberQueryDslRepository { + private final JPAQueryFactory queryFactory; + private final QMember member = QMember.member; + private final QManager manager = QManager.manager; + private final QPet pet = QPet.pet; + + @Override + public List findMyPetIds(Long memberId) { + return queryFactory.select(pet.id) + .from(member) + .leftJoin(manager).on(manager.member.id.eq(member.id)) + .leftJoin(pet).on(pet.id.eq(manager.pet.id)) + .where(member.id.eq(memberId)) + .fetch(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java similarity index 70% rename from src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java index 94c49395..601f35a3 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/dao/MemberJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/member/dao/MemberRepository.java @@ -1,11 +1,11 @@ package com.kcy.fitapet.domain.member.dao; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; +import com.kcy.fitapet.global.common.repository.ExtendedRepository; import java.util.Optional; -public interface MemberJpaRepository extends ExtendedJpaRepository { +public interface MemberRepository extends ExtendedRepository, MemberQueryDslRepository { Optional findByUid(String uid); Optional findByPhone(String phone); boolean existsByUidOrPhone(String uid, String phone); diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java index 7b2f5df3..d4142da5 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java @@ -10,6 +10,8 @@ import com.kcy.fitapet.domain.member.exception.SmsErrorCode; import com.kcy.fitapet.domain.member.service.module.MemberSearchService; import com.kcy.fitapet.domain.member.type.MemberAttrType; +import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.service.module.MemoSearchService; import com.kcy.fitapet.domain.notification.type.NotificationType; import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.domain.pet.dto.PetInfoRes; @@ -25,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import java.util.ArrayList; import java.util.List; @Service @@ -32,6 +35,7 @@ @Slf4j public class MemberAccountService { private final MemberSearchService memberSearchService; + private final MemoSearchService memoSearchService; private final SmsRedisHelper smsRedisHelper; @@ -91,6 +95,21 @@ public void updateNotification(Long requestId, Long userId, NotificationType typ member.updateNotificationFromType(type); } + @Transactional(readOnly = true) + public List getMemoCategories(Long userId) { + List petIds = memberSearchService.findMyPetIds(userId); + log.info("userId: {}, petIds: {}", userId, petIds); + + List rootMemoCategoryIds = memoSearchService.findRootMemoCategoriesIdByPetIds(petIds); + List rootMemoCategories = new ArrayList<>(); + + for (Long rootMemoCategoryId : rootMemoCategoryIds) { + rootMemoCategories.add(memoSearchService.findMemoCategoryWithMemoCount(rootMemoCategoryId)); + } + + return rootMemoCategories; + } + /** * 사용자 입력 닉네임과 기존 닉네임이 일치하는지 확인하고 변경 사항이 없으면 예외
* @param member Member : 사용자 정보 diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java index 717382a7..eb4ec4e7 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSaveService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.member.service.module; -import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; -import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; +import com.kcy.fitapet.domain.member.dao.ManagerRepository; +import com.kcy.fitapet.domain.member.dao.MemberRepository; import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.type.ManageType; @@ -13,8 +13,8 @@ @Service @RequiredArgsConstructor public class MemberSaveService { - private final MemberJpaRepository memberRepository; - private final ManagerJpaRepository managerRepository; + private final MemberRepository memberRepository; + private final ManagerRepository managerRepository; @Transactional public Member saveMember(Member member) { diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java index 74cbb79e..399fc913 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/module/MemberSearchService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.member.service.module; -import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; -import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; +import com.kcy.fitapet.domain.member.dao.ManagerRepository; +import com.kcy.fitapet.domain.member.dao.MemberRepository; import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.member.domain.Member; import com.kcy.fitapet.domain.member.exception.AccountErrorCode; @@ -15,8 +15,8 @@ @Service @RequiredArgsConstructor public class MemberSearchService { - private final MemberJpaRepository memberRepository; - private final ManagerJpaRepository managerRepository; + private final MemberRepository memberRepository; + private final ManagerRepository managerRepository; @Transactional(readOnly = true) public Member findById(Long id) { @@ -42,6 +42,11 @@ public List findAllManagerByMemberId(Long memberId) { return managerRepository.findAllByMember_Id(memberId); } + @Transactional(readOnly = true) + public List findMyPetIds(Long memberId) { + return memberRepository.findMyPetIds(memberId); + } + @Transactional(readOnly = true) public boolean isExistByUidOrPhone(String uid, String phone) { return memberRepository.existsByUidOrPhone(uid, phone); diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java index 2fa33a00..dba3f470 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepository.java @@ -6,5 +6,6 @@ public interface MemoCategoryQueryDslRepository { MemoCategoryInfoDto.MemoCategoryQueryDslRes findMemoCategoryById(Long memoCategoryId); + List findRootMemoCategoryIdByPetId(List petId); List findMemoCategoriesByParent(Long parentId); } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java index 8ec2eaa7..c75d2601 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoCategoryQueryDslRepositoryImpl.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.memo.domain.QMemo; import com.kcy.fitapet.domain.memo.domain.QMemoCategory; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.pet.domain.QPet; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -16,6 +17,16 @@ public class MemoCategoryQueryDslRepositoryImpl implements MemoCategoryQueryDslR private final JPAQueryFactory queryFactory; private final QMemoCategory memoCategory = QMemoCategory.memoCategory; private final QMemo memo = QMemo.memo; + private final QPet pet = QPet.pet; + + @Override + public List findRootMemoCategoryIdByPetId(List petIds) { + return queryFactory.select(memoCategory.id) + .from(memoCategory) + .leftJoin(pet).on(pet.id.eq(memoCategory.pet.id)) + .where(pet.id.in(petIds).and(memoCategory.parent.id.isNull())) + .fetch(); + } @Override public MemoCategoryInfoDto.MemoCategoryQueryDslRes findMemoCategoryById(Long memoCategoryId) { diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java index 791508c6..b6596793 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -9,24 +9,13 @@ import com.kcy.fitapet.domain.memo.domain.QMemoImage; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; import com.kcy.fitapet.domain.memo.exception.MemoErrorCode; -import com.kcy.fitapet.global.common.repository.QueryHandler; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.Projections; -import com.querydsl.core.types.dsl.CaseBuilder; -import com.querydsl.core.types.dsl.Expressions; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import static com.querydsl.core.group.GroupBy.groupBy; @Service @Slf4j @@ -45,6 +34,11 @@ public MemoCategory findMemoCategoryById(Long memoCategoryId) { return memoCategoryRepository.findByIdOrElseThrow(memoCategoryId); } + @Transactional(readOnly = true) + public List findRootMemoCategoriesIdByPetIds(List petIds) { + return memoCategoryRepository.findRootMemoCategoryIdByPetId(petIds); + } + @Transactional(readOnly = true) public MemoCategoryInfoDto.MemoCategoryInfo findMemoCategoryWithMemoCount(Long memoCategoryId) { MemoCategoryInfoDto.MemoCategoryQueryDslRes dto = memoCategoryRepository.findMemoCategoryById(memoCategoryId); diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java similarity index 85% rename from src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java index 8f06e61a..71a20545 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/dao/OauthRepository.java @@ -7,7 +7,7 @@ import java.math.BigInteger; import java.util.Optional; -public interface OauthJpaRepository extends ExtendedJpaRepository { +public interface OauthRepository extends ExtendedJpaRepository { Optional findByOauthIdAndProvider(BigInteger oauthId, ProviderType provider); boolean existsByOauthIdAndProvider(BigInteger oauthId, ProviderType provider); boolean existsByEmail(String email); diff --git a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java index 41515658..27f7b2ae 100644 --- a/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/oauth/service/module/OauthSearchService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.oauth.service.module; import com.kcy.fitapet.domain.member.domain.Member; -import com.kcy.fitapet.domain.oauth.dao.OauthJpaRepository; +import com.kcy.fitapet.domain.oauth.dao.OauthRepository; import com.kcy.fitapet.domain.oauth.domain.OauthAccount; import com.kcy.fitapet.domain.oauth.exception.OauthException; import com.kcy.fitapet.domain.oauth.type.ProviderType; @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor public class OauthSearchService { - private final OauthJpaRepository oauthRepository; + private final OauthRepository oauthRepository; @Transactional(readOnly = true) public boolean isExistMember(BigInteger oauthId, ProviderType provider) { diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepository.java new file mode 100644 index 00000000..cb856be4 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepository.java @@ -0,0 +1,4 @@ +package com.kcy.fitapet.domain.pet.dao; + +public interface PetQueryDslRepository { +} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepositoryImpl.java new file mode 100644 index 00000000..bb730db4 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetQueryDslRepositoryImpl.java @@ -0,0 +1,10 @@ +package com.kcy.fitapet.domain.pet.dao; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class PetQueryDslRepositoryImpl implements PetQueryDslRepository { + +} diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java similarity index 71% rename from src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java index 2966dbb2..d645700f 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetRepository.java @@ -3,6 +3,6 @@ import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; -public interface PetJpaRepository extends ExtendedJpaRepository { +public interface PetRepository extends ExtendedJpaRepository { boolean existsById(Long id); } diff --git a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java similarity index 64% rename from src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java rename to src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java index 87c3fdb1..f6194eae 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleJpaRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/dao/PetScheduleRepository.java @@ -3,5 +3,5 @@ import com.kcy.fitapet.domain.pet.domain.PetSchedule; import com.kcy.fitapet.global.common.repository.ExtendedJpaRepository; -public interface PetScheduleJpaRepository extends ExtendedJpaRepository { +public interface PetScheduleRepository extends ExtendedJpaRepository { } diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java index 45aaa396..edaa453a 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSaveService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.pet.service.module; -import com.kcy.fitapet.domain.pet.dao.PetJpaRepository; -import com.kcy.fitapet.domain.pet.dao.PetScheduleJpaRepository; +import com.kcy.fitapet.domain.pet.dao.PetRepository; +import com.kcy.fitapet.domain.pet.dao.PetScheduleRepository; import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.domain.pet.domain.PetSchedule; import com.kcy.fitapet.domain.schedule.domain.Schedule; @@ -15,8 +15,8 @@ @Service @RequiredArgsConstructor public class PetSaveService { - private final PetJpaRepository petRepository; - private final PetScheduleJpaRepository petScheduleRepository; + private final PetRepository petRepository; + private final PetScheduleRepository petScheduleRepository; @Transactional public Pet savePet(Pet pet) {; diff --git a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java index 6fcd3b5e..3b96e804 100644 --- a/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/pet/service/module/PetSearchService.java @@ -1,7 +1,7 @@ package com.kcy.fitapet.domain.pet.service.module; -import com.kcy.fitapet.domain.care.dao.CareCategoryJpaRepository; -import com.kcy.fitapet.domain.pet.dao.PetJpaRepository; +import com.kcy.fitapet.domain.care.dao.CareCategoryRepository; +import com.kcy.fitapet.domain.pet.dao.PetRepository; import com.kcy.fitapet.domain.pet.domain.Pet; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,8 +12,8 @@ @Service @RequiredArgsConstructor public class PetSearchService { - private final PetJpaRepository petRepository; - private final CareCategoryJpaRepository careCategoryRepository; + private final PetRepository petRepository; + private final CareCategoryRepository careCategoryRepository; @Transactional(readOnly = true) public Pet findPetById(Long id) { diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java b/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java index 730c91d3..5d6eb361 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authentication/UserDetailServiceImpl.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.global.common.security.authentication; -import com.kcy.fitapet.domain.member.dao.MemberJpaRepository; +import com.kcy.fitapet.domain.member.dao.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; @@ -13,7 +13,7 @@ @Service @RequiredArgsConstructor public class UserDetailServiceImpl implements UserDetailsService { - private final MemberJpaRepository userRepository; + private final MemberRepository userRepository; @Override @Cacheable(value = "securityUser", key = "#userId", unless = "#result == null", cacheManager = "securityUserCacheManager") diff --git a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java index 072ebe8d..3bfc5bcd 100644 --- a/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java +++ b/src/main/java/com/kcy/fitapet/global/common/security/authorization/ManagerAuthorize.java @@ -1,6 +1,6 @@ package com.kcy.fitapet.global.common.security.authorization; -import com.kcy.fitapet.domain.member.dao.ManagerJpaRepository; +import com.kcy.fitapet.domain.member.dao.ManagerRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.Cacheable; @@ -10,7 +10,7 @@ @RequiredArgsConstructor @Slf4j public class ManagerAuthorize { - private final ManagerJpaRepository managerRepository; + private final ManagerRepository managerRepository; @Cacheable(value = "manager", key = "#memberId + '@' + #petId", unless = "#result == false", cacheManager = "managerCacheManager") public boolean isManager(Long memberId, Long petId) { From aa6fdbf116e5ffcc09607e488219643a81fa5314 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Mon, 29 Jan 2024 22:31:32 +0900 Subject: [PATCH 12/18] =?UTF-8?q?fix:=20#90=20schedule=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=8B=9C,=20url=EC=97=90=20pet=5Fid=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fitapet/domain/member/api/AccountApi.java | 18 +++++++++- .../component/MemberAccountService.java | 15 +++++++++ .../domain/schedule/api/ScheduleApi.java | 33 +++++-------------- .../domain/schedule/dto/ScheduleSaveDto.java | 4 +-- .../schedule/exception/ScheduleErrorCode.java | 22 +++++++++++++ .../component/ScheduleManageService.java | 22 ++++--------- 6 files changed, 71 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/schedule/exception/ScheduleErrorCode.java diff --git a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java index acdedd20..47ed91cc 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java +++ b/src/main/java/com/kcy/fitapet/domain/member/api/AccountApi.java @@ -25,6 +25,8 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -108,11 +110,25 @@ public ResponseEntity putNotify( return ResponseEntity.ok(SuccessResponse.noContent()); } + @Operation(summary = "관리 중인 반려동물 날짜별 스케줄 전체 조회") + @GetMapping("/{user_id}/schedules") + @PreAuthorize("isAuthenticated() and #userId == principal.userId") + public ResponseEntity getCalendarSchedules( + @PathVariable("user_id") Long userId, + @RequestParam(value = "year") Integer year, + @RequestParam(value = "month") Integer month, + @RequestParam(value = "day") Integer day + ) { + LocalDateTime date = LocalDate.of(year, month, day).atStartOfDay(); + log.info("date: {}", date); + return ResponseEntity.ok(SuccessResponse.from("schedules", memberAccountService.findPetSchedules(userId, date).getSchedules())); + } + @Operation(summary = "관리 중인 반려동물의 모든 메모 카테고리 조회") @Parameter(name = "id", description = "조회할 프로필 ID", in = ParameterIn.PATH, required = true) @GetMapping("/{id}/memo-categories") @PreAuthorize("isAuthenticated() and #id == principal.userId") public ResponseEntity getMemoCategories(@PathVariable("id") Long id) { - return ResponseEntity.ok(SuccessResponse.from(memberAccountService.getMemoCategories(id))); + return ResponseEntity.ok(SuccessResponse.from("rootMemoCategories", memberAccountService.getMemoCategories(id))); } } diff --git a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java index d4142da5..a60b5bae 100644 --- a/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java +++ b/src/main/java/com/kcy/fitapet/domain/member/service/component/MemberAccountService.java @@ -16,6 +16,8 @@ import com.kcy.fitapet.domain.pet.domain.Pet; import com.kcy.fitapet.domain.pet.dto.PetInfoRes; import com.kcy.fitapet.domain.pet.service.module.PetSearchService; +import com.kcy.fitapet.domain.schedule.dto.ScheduleInfoDto; +import com.kcy.fitapet.domain.schedule.service.module.ScheduleSearchService; import com.kcy.fitapet.global.common.redis.sms.SmsRedisHelper; import com.kcy.fitapet.global.common.redis.sms.type.SmsPrefix; import com.kcy.fitapet.global.common.response.code.StatusCode; @@ -27,6 +29,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -35,6 +38,8 @@ @Slf4j public class MemberAccountService { private final MemberSearchService memberSearchService; + + private final ScheduleSearchService scheduleSearchService; private final MemoSearchService memoSearchService; private final SmsRedisHelper smsRedisHelper; @@ -95,6 +100,16 @@ public void updateNotification(Long requestId, Long userId, NotificationType typ member.updateNotificationFromType(type); } + @Transactional(readOnly = true) + public ScheduleInfoDto findPetSchedules(Long userId, LocalDateTime date) { + List pets = memberSearchService.findAllManagerByMemberId(userId) + .stream().map(Manager::getPet).toList(); + List petIds = pets.stream().map(Pet::getId).toList(); + + List scheduleInfo = scheduleSearchService.findSchedulesByCalender(date, petIds); + return ScheduleInfoDto.of(scheduleInfo); + } + @Transactional(readOnly = true) public List getMemoCategories(Long userId) { List petIds = memberSearchService.findMyPetIds(userId); diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/api/ScheduleApi.java b/src/main/java/com/kcy/fitapet/domain/schedule/api/ScheduleApi.java index 320c7435..6cd30ab4 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/api/ScheduleApi.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/api/ScheduleApi.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.schedule.dto.ScheduleSaveDto; import com.kcy.fitapet.domain.schedule.service.component.ScheduleManageService; import com.kcy.fitapet.global.common.response.SuccessResponse; +import com.kcy.fitapet.global.common.security.authentication.CustomUserDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -10,6 +11,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -19,44 +21,25 @@ @Slf4j @RestController @RequiredArgsConstructor -@RequestMapping("/api/v2/users/{user_id}") +@RequestMapping("/api/v2") public class ScheduleApi { private final ScheduleManageService scheduleManageService; @Operation(summary = "스케줄 등록") - @PostMapping("/pets/{pet_id}/schedules") - @PreAuthorize("isAuthenticated() and #userId == principal.userId and @managerAuthorize.isManager(#userId, #petId)") - public ResponseEntity saveSchedule( - @PathVariable("user_id") Long userId, - @PathVariable("pet_id") Long petId, - @RequestBody @Valid ScheduleSaveDto.Request request - ) { - scheduleManageService.saveSchedule(petId, request); + @PostMapping("/schedules") + @PreAuthorize("isAuthenticated()") + public ResponseEntity saveSchedule(@RequestBody @Valid ScheduleSaveDto.Request request, @AuthenticationPrincipal CustomUserDetails principal) { + scheduleManageService.saveSchedule(principal.getUserId(), request); return ResponseEntity.ok(SuccessResponse.noContent()); } @Operation(summary = "pet_id에 속하는 반려동물 스케줄 조회", description = "count가 null이면 전체 조회, null이 아니면 현재 날짜&시간 이후 count 만큼 조회") @GetMapping("/pets/{pet_id}/schedules") - @PreAuthorize("isAuthenticated() and #userId == principal.userId and @managerAuthorize.isManager(#userId, #petId)") + @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId)") public ResponseEntity getPetSchedules( - @PathVariable("user_id") Long userId, @PathVariable("pet_id") Long petId, @RequestParam(value = "count", required = false, defaultValue = "-1") int count ) { return ResponseEntity.ok(SuccessResponse.from("schedules", scheduleManageService.findPetSchedules(petId, count).getSchedules())); } - - @Operation(summary = "관리 중인 반려동물 날짜별 스케줄 전체 조회") - @GetMapping("/schedules") - @PreAuthorize("isAuthenticated() and #userId == principal.userId") - public ResponseEntity getCalendarSchedules( - @PathVariable("user_id") Long userId, - @RequestParam(value = "year") Integer year, - @RequestParam(value = "month") Integer month, - @RequestParam(value = "day") Integer day - ) { - LocalDateTime date = LocalDate.of(year, month, day).atStartOfDay(); - log.info("date: {}", date); - return ResponseEntity.ok(SuccessResponse.from("schedules", scheduleManageService.findPetSchedules(userId, date).getSchedules())); - } } diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleSaveDto.java b/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleSaveDto.java index da42862f..afea67e4 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleSaveDto.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/dto/ScheduleSaveDto.java @@ -28,8 +28,8 @@ public record Request( @Schema(description = "알림 시간(분 단위)", example = "30 (단, 없으면 0)", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull Integer notifyTime, - @Schema(description = "케어 동물 추가", example = "[1, 2, 3] (단, 없으면 빈 배열)", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull + @Schema(description = "케어 동물 추가", example = "[1, 2, 3]", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty List petIds ) { public Schedule toEntity() { diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/exception/ScheduleErrorCode.java b/src/main/java/com/kcy/fitapet/domain/schedule/exception/ScheduleErrorCode.java new file mode 100644 index 00000000..e6e6108f --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/schedule/exception/ScheduleErrorCode.java @@ -0,0 +1,22 @@ +package com.kcy.fitapet.domain.schedule.exception; + +import com.kcy.fitapet.global.common.response.code.StatusCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ScheduleErrorCode implements StatusCode { + /* 404 FORBIDDEN */ + SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 스케줄을 찾을 수 없습니다.") + ; + + private final HttpStatus httpStatus; + private final String message; + + @Override + public String getName() { + return name(); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/schedule/service/component/ScheduleManageService.java b/src/main/java/com/kcy/fitapet/domain/schedule/service/component/ScheduleManageService.java index f78195a5..5dc8d39c 100644 --- a/src/main/java/com/kcy/fitapet/domain/schedule/service/component/ScheduleManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/schedule/service/component/ScheduleManageService.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.member.domain.Manager; import com.kcy.fitapet.domain.member.service.module.MemberSearchService; import com.kcy.fitapet.domain.pet.domain.Pet; +import com.kcy.fitapet.domain.pet.exception.PetErrorCode; import com.kcy.fitapet.domain.pet.service.module.PetSaveService; import com.kcy.fitapet.domain.pet.service.module.PetSearchService; import com.kcy.fitapet.domain.schedule.domain.Schedule; @@ -10,6 +11,7 @@ import com.kcy.fitapet.domain.schedule.dto.ScheduleSaveDto; import com.kcy.fitapet.domain.schedule.service.module.ScheduleSaveService; import com.kcy.fitapet.domain.schedule.service.module.ScheduleSearchService; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -31,12 +33,12 @@ public class ScheduleManageService { private final PetSaveService petSaveService; @Transactional - public void saveSchedule(Long petId, ScheduleSaveDto.Request request) { - Schedule schedule = scheduleSaveService.saveSchedule(request.toEntity()); - + public void saveSchedule(Long userId, ScheduleSaveDto.Request request) { List petIds = request.petIds(); - if (!petIds.contains(petId)) - petIds.add(petId); + if (!memberSearchService.isManagerAll(userId, petIds)) + throw new GlobalErrorException(PetErrorCode.NOT_MANAGER_PET); + + Schedule schedule = scheduleSaveService.saveSchedule(request.toEntity()); List pets = petSearchService.findPetsByIds(petIds); petSaveService.mappingAllPetAndSchedule(pets, schedule); @@ -51,14 +53,4 @@ public ScheduleInfoDto findPetSchedules(Long petId, int count) { return ScheduleInfoDto.of(scheduleSearchService.findTopCountSchedulesByIds(scheduleIds)); } - - @Transactional(readOnly = true) - public ScheduleInfoDto findPetSchedules(Long userId, LocalDateTime date) { - List pets = memberSearchService.findAllManagerByMemberId(userId) - .stream().map(Manager::getPet).toList(); - List petIds = pets.stream().map(Pet::getId).toList(); - - List scheduleInfo = scheduleSearchService.findSchedulesByCalender(date, petIds); - return ScheduleInfoDto.of(scheduleInfo); - } } From e1ae4f23cbe4c4810e16d88c2c2cf77a8ef1ff76 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Tue, 30 Jan 2024 01:05:46 +0900 Subject: [PATCH 13/18] feat: #83 memo save api --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 10 ++++++-- .../kcy/fitapet/domain/memo/domain/Memo.java | 9 +++++++ .../domain/memo/domain/MemoCategory.java | 3 +++ .../fitapet/domain/memo/domain/MemoImage.java | 16 +++++++++--- .../fitapet/domain/memo/dto/MemoSaveReq.java | 25 +++++++++++++++++++ .../service/component/MemoManageService.java | 18 +++++++++++-- .../memo/service/module/MemoSaveService.java | 5 ++++ 7 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dto/MemoSaveReq.java diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index f0d832fa..44cf3045 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -1,6 +1,7 @@ package com.kcy.fitapet.domain.memo.api; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.component.MemoManageService; import com.kcy.fitapet.global.common.response.SuccessResponse; @@ -137,8 +138,13 @@ public ResponseEntity getMemo( }) @PostMapping("/memo-categories/{memo_category_id}/memos") @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidMemoCategory(#memoCategoryId, #petId)") - public ResponseEntity saveMemo(@PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId) { - return null; + public ResponseEntity saveMemo( + @PathVariable("pet_id") Long petId, + @PathVariable("memo_category_id") Long memoCategoryId, + @RequestBody @Valid MemoSaveReq req + ) { + memoManageService.saveMemo(memoCategoryId, req); + return ResponseEntity.status(HttpStatus.SC_CREATED).body(SuccessResponse.noContent()); } @Operation(summary = "메모 삭제") diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java index dff4ed81..66962aaf 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/Memo.java @@ -39,4 +39,13 @@ public static Memo of(String title, String content) { .content(content) .build(); } + + public void updateMemoCategory(MemoCategory memoCategory) { + if (this.memoCategory != null) { + this.memoCategory.getMemos().remove(this); + } + + this.memoCategory = memoCategory; + memoCategory.getMemos().add(this); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java index 1f505d97..6f0b212f 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoCategory.java @@ -29,6 +29,9 @@ public class MemoCategory extends DateAuditable { @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) private List children = new ArrayList<>(); + @OneToMany(mappedBy = "memoCategory", cascade = CascadeType.ALL) + private List memos = new ArrayList<>(); + @Builder private MemoCategory(String categoryName) { this.categoryName = categoryName; diff --git a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoImage.java b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoImage.java index f1673d61..64e147af 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoImage.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/domain/MemoImage.java @@ -25,8 +25,18 @@ private MemoImage(String imgUrl) { this.imgUrl = imgUrl; } - public static MemoImage of(String imgUrl) { - return MemoImage.builder() - .imgUrl(imgUrl).build(); + public static MemoImage of(String imgUrl, Memo memo) { + MemoImage memoImage = new MemoImage(imgUrl); + memoImage.updateMemo(memo); + return memoImage; + } + + private void updateMemo(Memo memo) { + if (this.memo != null) { + this.memo.getMemoImages().remove(this); + } + + this.memo = memo; + memo.getMemoImages().add(this); } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoSaveReq.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoSaveReq.java new file mode 100644 index 00000000..26dfd7dc --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoSaveReq.java @@ -0,0 +1,25 @@ +package com.kcy.fitapet.domain.memo.dto; + +import com.kcy.fitapet.domain.memo.domain.Memo; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.util.List; + +@Schema(description = "메모 저장 요청") +public record MemoSaveReq( + @NotBlank(message = "제목은 필수입니다.") + @Schema(description = "제목", example = "오늘의 일기", requiredMode = Schema.RequiredMode.REQUIRED) + String title, + @NotBlank(message = "내용은 필수입니다.") + @Schema(description = "내용", example = "오늘은 매우 행복했다.", requiredMode = Schema.RequiredMode.REQUIRED) + String content, + @NotNull(message = "메모 이미지는 필수입니다. 없으면 빈 배열을 보내주세요.") + @Schema(description = "메모 이미지", example = "[\"https://fitapet.com/image/1.jpg\", \"https://fitapet.com/image/2.jpg\"]", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + List memoImageUrls +) { + public Memo toEntity() { + return Memo.of(title, content); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java index fe2ec21a..9b30b726 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -1,7 +1,10 @@ package com.kcy.fitapet.domain.memo.service.component; +import com.kcy.fitapet.domain.memo.domain.Memo; import com.kcy.fitapet.domain.memo.domain.MemoCategory; +import com.kcy.fitapet.domain.memo.domain.MemoImage; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.module.MemoSaveService; import com.kcy.fitapet.domain.memo.service.module.MemoSearchService; @@ -11,8 +14,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Slf4j @Service @RequiredArgsConstructor @@ -27,6 +28,19 @@ public void saveSubMemoCategory(Long petId, Long rootMemoCategoryId, SubMemoCate req.toEntity(memoSearchService.findMemoCategoryById(rootMemoCategoryId), petSearchService.findPetById(petId)); } + @Transactional + public void saveMemo(Long MemoCategoryId, MemoSaveReq req) { + MemoCategory memoCategory = memoSearchService.findMemoCategoryById(MemoCategoryId); + + Memo memo = req.toEntity(); + memo.updateMemoCategory(memoCategory); + + if (req.memoImageUrls() != null) { + memoSaveService.saveMemo(memo); + req.memoImageUrls().forEach(url -> MemoImage.of(url, memo)); + } + } + @Transactional(readOnly = true) public MemoCategoryInfoDto.MemoCategoryInfo findCategoryById(Long memoCategoryId) { return memoSearchService.findMemoCategoryWithMemoCount(memoCategoryId); diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java index f1f385a9..fec02673 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSaveService.java @@ -3,6 +3,7 @@ import com.kcy.fitapet.domain.memo.dao.MemoCategoryRepository; import com.kcy.fitapet.domain.memo.dao.MemoImageRepository; import com.kcy.fitapet.domain.memo.dao.MemoRepository; +import com.kcy.fitapet.domain.memo.domain.Memo; import com.kcy.fitapet.domain.memo.domain.MemoCategory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,4 +20,8 @@ public class MemoSaveService { public void saveMemoCategory(MemoCategory memoCategory) { memoCategoryRepository.save(memoCategory); } + + public void saveMemo(Memo memo) { + memoRepository.save(memo); + } } From 23af5183bfb45228023aed452186d146a858c0aa Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:28:37 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20#83=20memo=20=EB=8B=A8=EA=B1=B4?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 4 +- .../memo/dao/MemoQueryDslRepository.java | 9 +++ .../memo/dao/MemoQueryDslRepositoryImpl.java | 43 ++++++++++++++ .../domain/memo/dao/MemoRepository.java | 2 +- .../fitapet/domain/memo/dto/MemoInfoDto.java | 56 +++++++++++++++++++ .../domain/memo/exception/MemoErrorCode.java | 1 + .../service/component/MemoManageService.java | 6 ++ .../service/module/MemoSearchService.java | 8 +++ 8 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java create mode 100644 src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index 44cf3045..dc0d9eb1 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -1,6 +1,7 @@ package com.kcy.fitapet.domain.memo.api; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.component.MemoManageService; @@ -128,7 +129,8 @@ public ResponseEntity getMemo( @PathVariable("memo_category_id") Long memoCategoryId, @PathVariable("memo_id") Long memoId ) { - return null; + MemoInfoDto.MemoInfo info = memoManageService.findMemoAndMemoImageUrlsById(memoId); + return ResponseEntity.ok(SuccessResponse.from(info)); } @Operation(summary = "메모 작성") diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java new file mode 100644 index 00000000..1d497498 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java @@ -0,0 +1,9 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; + +import java.util.Optional; + +public interface MemoQueryDslRepository { + Optional findMemoAndMemoImageUrlsById(Long memoId); +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java new file mode 100644 index 00000000..4411a0aa --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java @@ -0,0 +1,43 @@ +package com.kcy.fitapet.domain.memo.dao; + +import com.kcy.fitapet.domain.memo.domain.QMemo; +import com.kcy.fitapet.domain.memo.domain.QMemoImage; +import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import javax.swing.text.html.Option; + +import java.util.Optional; + +import static com.querydsl.core.group.GroupBy.groupBy; +import static com.querydsl.core.types.Projections.list; + +@Repository +@RequiredArgsConstructor +public class MemoQueryDslRepositoryImpl implements MemoQueryDslRepository { + private final JPAQueryFactory queryFactory; + private final QMemo memo = QMemo.memo; + private final QMemoImage memoImage = QMemoImage.memoImage; + + + @Override + public Optional findMemoAndMemoImageUrlsById(Long memoId) { + return Optional.of(queryFactory + .select( + Projections.constructor( + MemoInfoDto.MemoInfo.class, + memo.id, + memo.title, + memo.content, + list(memoImage.imgUrl).skipNulls() + ) + ) + .from(memo) + .leftJoin(memoImage).on(memo.id.eq(memoImage.memo.id)) + .where(memo.id.eq(memoId)) + .fetchFirst()); + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java index 5fc256f4..5c44357e 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoRepository.java @@ -3,6 +3,6 @@ import com.kcy.fitapet.domain.memo.domain.Memo; import com.kcy.fitapet.global.common.repository.ExtendedRepository; -public interface MemoRepository extends ExtendedRepository { +public interface MemoRepository extends ExtendedRepository, MemoQueryDslRepository { boolean existsByIdAndMemoCategory_Id(Long memoId, Long memoCategoryId); } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java new file mode 100644 index 00000000..abeda663 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java @@ -0,0 +1,56 @@ +package com.kcy.fitapet.domain.memo.dto; + +import com.kcy.fitapet.domain.memo.domain.Memo; +import com.kcy.fitapet.global.common.util.bind.Dto; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +public class MemoInfoDto { + private final List memoInfos; + + private MemoInfoDto(List memoInfos) { + this.memoInfos = memoInfos; + } + + public static MemoInfoDto of(List memoInfos) { + return new MemoInfoDto(memoInfos); + } + + @Builder + @Dto(name = "memo") + public record MemoInfo( + Long memoId, + String title, + String content, + List memoImageUrls + ) { + public MemoInfo(Long memoId, String title, String content, List memoImageUrls) { + this.memoId = memoId; + this.title = title; + this.content = content; + this.memoImageUrls = (memoImageUrls == null) ? List.of() : List.copyOf(memoImageUrls); + } + + public static MemoInfo from(Memo memo) { + return MemoInfo.builder() + .memoId(memo.getId()) + .title(memo.getTitle()) + .content(memo.getContent()) + .memoImageUrls(List.of()) + .build(); + } + + public static MemoInfo valueOf(Memo memo, List memoImageUrls) { + return MemoInfo.builder() + .memoId(memo.getId()) + .title(memo.getTitle()) + .content(memo.getContent()) + .memoImageUrls(memoImageUrls) + .build(); + } + } +} diff --git a/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java b/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java index 76c86e5e..6fc85810 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/exception/MemoErrorCode.java @@ -11,6 +11,7 @@ public enum MemoErrorCode implements StatusCode { /* 404 NOT_FOUND */ MEMO_CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "메모 카테고리를 찾을 수 없습니다."), + MEMO_NOT_FOUND(HttpStatus.NOT_FOUND, "메모를 찾을 수 없습니다."), ; private final HttpStatus httpStatus; diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java index 9b30b726..a2982dfc 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -4,6 +4,7 @@ import com.kcy.fitapet.domain.memo.domain.MemoCategory; import com.kcy.fitapet.domain.memo.domain.MemoImage; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; import com.kcy.fitapet.domain.memo.service.module.MemoSaveService; @@ -45,4 +46,9 @@ public void saveMemo(Long MemoCategoryId, MemoSaveReq req) { public MemoCategoryInfoDto.MemoCategoryInfo findCategoryById(Long memoCategoryId) { return memoSearchService.findMemoCategoryWithMemoCount(memoCategoryId); } + + @Transactional(readOnly = true) + public MemoInfoDto.MemoInfo findMemoAndMemoImageUrlsById(Long memoId) { + return memoSearchService.findMemoAndMemoImageUrlsById(memoId); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java index b6596793..eeb4fb52 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -8,6 +8,7 @@ import com.kcy.fitapet.domain.memo.domain.QMemoCategory; import com.kcy.fitapet.domain.memo.domain.QMemoImage; import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; +import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.exception.MemoErrorCode; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import lombok.RequiredArgsConstructor; @@ -53,4 +54,11 @@ public MemoCategoryInfoDto.MemoCategoryInfo findMemoCategoryWithMemoCount(Long m return MemoCategoryInfoDto.MemoCategoryInfo.from(dto); } + + @Transactional(readOnly = true) + public MemoInfoDto.MemoInfo findMemoAndMemoImageUrlsById(Long memoId) { + return memoRepository.findMemoAndMemoImageUrlsById(memoId).orElseThrow( + () -> new GlobalErrorException(MemoErrorCode.MEMO_NOT_FOUND) + ); + } } \ No newline at end of file From de5a58a797c1f57a0c54b10dbd1ecbfd95944ae7 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:54:45 +0900 Subject: [PATCH 15/18] =?UTF-8?q?feat:=20#83=20MySQL=20Full=20Text=20Searc?= =?UTF-8?q?h=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=A0=95=EC=9D=98=20=ED=95=A8=EC=88=98=20=EC=A0=95=EC=9D=98?= =?UTF-8?q?=20(=EC=9E=90=EC=97=B0=EC=96=B4,=20=EB=B6=88=EB=A6=B0,=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=ED=99=95=EC=9E=A5=20=EB=AA=A8=EB=93=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/MySqlFunctionContributor.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java diff --git a/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java new file mode 100644 index 00000000..45e2e7d3 --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java @@ -0,0 +1,49 @@ +package com.kcy.fitapet.global.config; + +import org.hibernate.boot.model.FunctionContributions; +import org.hibernate.boot.model.FunctionContributor; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * MySQL의 match_against 함수를 사용하기 위한 FunctionContributor + * + *

+ * Boolean Mode 검색 예시
+ * {@code
+ * public List findAllBy(final String entityNameWord, final String entityAttrWord) {
+ *    return queryFactory.selectFrom(entity)
+ *        .where(
+ *             matchAgainst(entity.name, entityNameWord),
+ *             matchAgainst(entity.attr, entityAttrWord)
+ *        )
+ *        .fetch();
+ * }
+ *
+ * private BooleanExpression matchAgainst(final String target, final String searchWord) {
+ *    if (!StringUtils.hasText(searchWord)) {
+ *       return null
+ *    }
+ *    return Expressions.booleanTemplate("match({0}) against({1} in boolean mode) > 0", target, searchWord);
+ * }
+ * 
+ * + * @see 참고 블로그 + */ +public class MySqlFunctionContributor implements FunctionContributor { + private static final String FUNCTION_NAME = "match_against"; + private static final String NATURAL_LANGUAGE_PATTERN = "match (?1) against (?2 in natural language mode)"; + private static final String BOOLEAN_PATTERN = "match (?1) against (?2 in boolean mode)"; + private static final String QUERY_EXPANSION_PATTERN = "match (?1) against (?2 in query expansion mode)"; + + @Override + public void contributeFunctions(FunctionContributions functionContributions) { + SqmFunctionRegistry registry = functionContributions.getFunctionRegistry(); + TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); + + registry.registerPattern( FUNCTION_NAME, NATURAL_LANGUAGE_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); + registry.registerPattern( FUNCTION_NAME, BOOLEAN_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); + registry.registerPattern( FUNCTION_NAME, QUERY_EXPANSION_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); + } +} From b6959e8c57219884aae2dc56f9a48c952324d16c Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:24:23 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20#83=20memo=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20API=20&&=20QueryDslUtil=20=EB=B0=8F=20MySQL=20=EB=B0=A9?= =?UTF-8?q?=EC=96=B8=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 11 +-- .../memo/dao/MemoQueryDslRepository.java | 4 ++ .../memo/dao/MemoQueryDslRepositoryImpl.java | 70 +++++++++++++++++-- .../fitapet/domain/memo/dto/MemoInfoDto.java | 51 +++++++++++--- .../service/component/MemoManageService.java | 11 +++ .../service/module/MemoSearchService.java | 27 +++++-- .../common/response/SuccessResponse.java | 19 ++--- .../common/util/querydsl/QueryDslUtil.java | 64 +++++++++++++++++ .../util/querydsl/RepositorySliceHelper.java | 23 ++++++ .../config/MySqlFunctionContributor.java | 11 ++- ...g.hibernate.boot.model.FunctionContributor | 1 + 11 files changed, 253 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java create mode 100644 src/main/java/com/kcy/fitapet/global/common/util/querydsl/RepositorySliceHelper.java create mode 100644 src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index dc0d9eb1..58f9a121 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -1,6 +1,5 @@ package com.kcy.fitapet.domain.memo.api; -import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; @@ -90,16 +89,18 @@ public ResponseEntity deleteSubMemoCategory(@PathVariable("pet_id") Long petI public ResponseEntity getMemosAndSubCategories( @PathVariable("pet_id") Long petId, @PathVariable("memo_category_id") Long memoCategoryId, @RequestParam(value = "search", defaultValue = "", required = false) String search, - @PageableDefault(size = 15, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + @PageableDefault(size = 15, page = 0, sort = "memo.createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { - return null; + MemoInfoDto.PageResponse res = memoManageService.findMemosInMemoCategory(memoCategoryId, pageable, search); + return ResponseEntity.ok(SuccessResponse.from(res)); } /* * 메모 API */ - @Operation(summary = "반려동물에게 등록된 메모 리스트 조회") + @Operation(summary = "반려동물에게 등록된 메모 리스트 조회", description = """ + 기본 값으로는 상위/하위 카테고리를 포함한 가장 최근 내역의 메모 리스트를 조회합니다. (페이지 사이즈: 5, 페이지 번호: 1, 정렬 기준: createdAt, 정렬 방식: DESC)""") @Parameters({ @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), @Parameter(name = "size", description = "페이지 사이즈", example = "5", in = ParameterIn.QUERY), @@ -111,7 +112,7 @@ public ResponseEntity getMemosAndSubCategories( @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId)") public ResponseEntity getMemosByPet( @PathVariable("pet_id") Long petId, - @PageableDefault(size = 5, page = 1, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + @PageableDefault(size = 5, page = 0, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { return null; } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java index 1d497498..c5a60d31 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java @@ -1,9 +1,13 @@ package com.kcy.fitapet.domain.memo.dao; import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import java.util.Optional; public interface MemoQueryDslRepository { Optional findMemoAndMemoImageUrlsById(Long memoId); + Slice findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target); } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java index 4411a0aa..cfbb946c 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java @@ -1,15 +1,30 @@ package com.kcy.fitapet.domain.memo.dao; import com.kcy.fitapet.domain.memo.domain.QMemo; +import com.kcy.fitapet.domain.memo.domain.QMemoCategory; import com.kcy.fitapet.domain.memo.domain.QMemoImage; import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; +import com.kcy.fitapet.global.common.util.querydsl.QueryDslUtil; +import com.kcy.fitapet.global.common.util.querydsl.RepositorySliceHelper; +import com.querydsl.core.QueryResults; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Order; +import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; -import javax.swing.text.html.Option; - +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.Optional; import static com.querydsl.core.group.GroupBy.groupBy; @@ -17,11 +32,12 @@ @Repository @RequiredArgsConstructor +@Slf4j public class MemoQueryDslRepositoryImpl implements MemoQueryDslRepository { private final JPAQueryFactory queryFactory; private final QMemo memo = QMemo.memo; private final QMemoImage memoImage = QMemoImage.memoImage; - + private final QMemoCategory memoCategory = QMemoCategory.memoCategory; @Override public Optional findMemoAndMemoImageUrlsById(Long memoId) { @@ -30,9 +46,16 @@ public Optional findMemoAndMemoImageUrlsById(Long memoId) Projections.constructor( MemoInfoDto.MemoInfo.class, memo.id, - memo.title, - memo.content, - list(memoImage.imgUrl).skipNulls() + QueryDslUtil.left(memo.title, Expressions.constant(19)), + QueryDslUtil.left(memo.content, Expressions.constant(16)), + memo.createdAt, + list( + Projections.constructor( + MemoInfoDto.MemoImageInfo.class, + memoImage.id, + memoImage.imgUrl + ).skipNulls() + ).skipNulls() ) ) .from(memo) @@ -40,4 +63,39 @@ public Optional findMemoAndMemoImageUrlsById(Long memoId) .where(memo.id.eq(memoId)) .fetchFirst()); } + + @Override + public Slice findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target) { + List results = queryFactory + .select(memoCategory.id, memo.id, memo.title, memo.content, memo.createdAt, memoImage.id, memoImage.imgUrl) + .from(memo) + .innerJoin(memoCategory).on(memoCategory.id.eq(memo.memoCategory.id)) + .leftJoin(memoImage).on(memoImage.memo.id.eq(memo.id)) + .where(memoCategory.id.eq(memoCategoryId) + .and(QueryDslUtil.matchAgainst(memo.title, memo.content, target)) + ) + .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + .transform( + groupBy(memo.id).list( + Projections.constructor( + MemoInfoDto.MemoInfo.class, + memo.id, + QueryDslUtil.left(memo.title, Expressions.constant(19)), + QueryDslUtil.left(memo.content, Expressions.constant(16)), + memo.createdAt, + list( + Projections.constructor( + MemoInfoDto.MemoImageInfo.class, + memoImage.id, + memoImage.imgUrl + ).skipNulls() + ).skipNulls() + ) + ) + ); + + return RepositorySliceHelper.toSlice(results, pageable); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java index abeda663..7c43f7bd 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java @@ -1,11 +1,18 @@ package com.kcy.fitapet.domain.memo.dto; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.kcy.fitapet.domain.memo.domain.Memo; import com.kcy.fitapet.global.common.util.bind.Dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Slice; +import java.time.LocalDateTime; import java.util.List; @Getter @@ -26,13 +33,17 @@ public record MemoInfo( Long memoId, String title, String content, - List memoImageUrls + @JsonSerialize(using = LocalDateTimeSerializer.class) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createdAt, + List memoImages ) { - public MemoInfo(Long memoId, String title, String content, List memoImageUrls) { + public MemoInfo(Long memoId, String title, String content, LocalDateTime createdAt, List memoImages) { this.memoId = memoId; - this.title = title; - this.content = content; - this.memoImageUrls = (memoImageUrls == null) ? List.of() : List.copyOf(memoImageUrls); + this.title = title.length() == 19 ? title + "..." : title; + this.content = content.length() == 16 ? content + "..." : content; + this.createdAt = createdAt; + this.memoImages = (memoImages == null) ? List.of() : memoImages; } public static MemoInfo from(Memo memo) { @@ -40,17 +51,41 @@ public static MemoInfo from(Memo memo) { .memoId(memo.getId()) .title(memo.getTitle()) .content(memo.getContent()) - .memoImageUrls(List.of()) + .createdAt(memo.getCreatedAt()) + .memoImages(List.of()) .build(); } - public static MemoInfo valueOf(Memo memo, List memoImageUrls) { + public static MemoInfo valueOf(Memo memo, List memoImageUrls) { return MemoInfo.builder() .memoId(memo.getId()) .title(memo.getTitle()) .content(memo.getContent()) - .memoImageUrls(memoImageUrls) + .createdAt(memo.getCreatedAt()) + .memoImages(memoImageUrls) .build(); } } + + public record MemoImageInfo( + Long memoImageId, + String imgUrl + ) { + public MemoImageInfo(Long memoImageId, String imgUrl) { + this.memoImageId = memoImageId; + this.imgUrl = imgUrl; + } + } + + public record PageResponse( + @Schema(description = "메모 목록") List memos, + @Schema(description = "현재 페이지") int number, + @Schema(description = "페이지 크기") int size, + @Schema(description = "현재 페이지의 데이터 개수") int numberOfElements, + @Schema(description = "다음 페이지 존재 여부") boolean hasNext + ) { + public static PageResponse from(@NotNull Slice page) { + return new PageResponse(page.getContent(), page.getPageable().getPageNumber(), page.getPageable().getPageSize(), page.getNumberOfElements(), page.hasNext()); + } + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java index a2982dfc..072647ba 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -7,14 +7,20 @@ import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoSaveReq; import com.kcy.fitapet.domain.memo.dto.SubMemoCategorySaveReq; +import com.kcy.fitapet.domain.memo.exception.MemoErrorCode; import com.kcy.fitapet.domain.memo.service.module.MemoSaveService; import com.kcy.fitapet.domain.memo.service.module.MemoSearchService; import com.kcy.fitapet.domain.pet.service.module.PetSearchService; +import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor @@ -51,4 +57,9 @@ public MemoCategoryInfoDto.MemoCategoryInfo findCategoryById(Long memoCategoryId public MemoInfoDto.MemoInfo findMemoAndMemoImageUrlsById(Long memoId) { return memoSearchService.findMemoAndMemoImageUrlsById(memoId); } + + @Transactional(readOnly = true) + public MemoInfoDto.PageResponse findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target) { + return memoSearchService.findMemosInMemoCategory(memoCategoryId, pageable, target); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java index eeb4fb52..6fc641e1 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -10,13 +10,29 @@ import com.kcy.fitapet.domain.memo.dto.MemoCategoryInfoDto; import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.domain.memo.exception.MemoErrorCode; +import com.kcy.fitapet.global.common.repository.QueryHandler; import com.kcy.fitapet.global.common.response.exception.GlobalErrorException; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.group.GroupBy; +import com.querydsl.core.types.Expression; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringPath; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import static com.querydsl.core.group.GroupBy.groupBy; @Service @Slf4j @@ -26,10 +42,6 @@ public class MemoSearchService { private final MemoCategoryRepository memoCategoryRepository; private final MemoImageRepository memoImageRepository; - private final QMemoCategory memoCategory = QMemoCategory.memoCategory; - private final QMemo memo = QMemo.memo; - private final QMemoImage memoImage = QMemoImage.memoImage; - @Transactional(readOnly = true) public MemoCategory findMemoCategoryById(Long memoCategoryId) { return memoCategoryRepository.findByIdOrElseThrow(memoCategoryId); @@ -61,4 +73,11 @@ public MemoInfoDto.MemoInfo findMemoAndMemoImageUrlsById(Long memoId) { () -> new GlobalErrorException(MemoErrorCode.MEMO_NOT_FOUND) ); } + + @Transactional(readOnly = true) + public MemoInfoDto.PageResponse findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target) { + Slice page = memoRepository.findMemosInMemoCategory(memoCategoryId, pageable, target); + + return MemoInfoDto.PageResponse.from(page); + } } \ No newline at end of file diff --git a/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java b/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java index 109f133b..6410fdf1 100644 --- a/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java +++ b/src/main/java/com/kcy/fitapet/global/common/response/SuccessResponse.java @@ -22,16 +22,17 @@ public class SuccessResponse { @Schema(description = "응답 상태", defaultValue = "success") private final String status = "success"; @Schema(description = "응답 코드", example = "data or no_content") - private Map data; + private T data; @Builder - private SuccessResponse(Map data) { + private SuccessResponse(T data) { this.data = data; } + @SuppressWarnings("unchecked") public static SuccessResponse from(String key, T data) { return SuccessResponse.builder() - .data(Map.of(key, data)) + .data((T) Map.of(key, data)) .build(); } @@ -39,16 +40,16 @@ public static SuccessResponse from(String key, T data) { * 전송할 Application Level Data를 설정한다. * @param data : 전송할 데이터 */ + @SuppressWarnings("unchecked") public static SuccessResponse from(T data) { String key = getDtoName(data); - if (data instanceof Map) { - key = ((Map) data).keySet().stream().findFirst().orElse(null).toString(); - data = (T) ((Map) data).get(key); - } - if (key == null) key = "data"; + SuccessResponseBuilder builder = SuccessResponse.builder(); + + if (key != null) builder.data((T) Map.of(key, data)); + else builder.data(data); - return from(key, data); + return builder.build(); } /** diff --git a/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java new file mode 100644 index 00000000..cd26610c --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java @@ -0,0 +1,64 @@ +package com.kcy.fitapet.global.common.util.querydsl; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.*; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.core.types.dsl.StringPath; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@Slf4j +public class QueryDslUtil { + private static final Function castToQueryDsl = nullHandling -> switch (nullHandling) { + case NATIVE -> OrderSpecifier.NullHandling.Default; + case NULLS_FIRST -> OrderSpecifier.NullHandling.NullsFirst; + case NULLS_LAST -> OrderSpecifier.NullHandling.NullsLast; + }; + + public static BooleanExpression matchAgainst(final StringPath c1, final StringPath c2, final String target) { + if (!StringUtils.hasText(target)) { return null; } + String template = "'" + target + "*'"; + log.info("template: {}", template); + return Expressions.booleanTemplate( "function('match_against', {0}, {1}, {2})", c1, c2, template); + } + + public static StringExpression left(final StringPath c1, final Expression c2) { + return Expressions.stringTemplate("function('left', {0}, {1})", c1, c2); + } + + public static List> getOrderSpecifier(Sort sort) { + List> orders = new ArrayList<>(); + + for (Sort.Order order : sort) { + OrderSpecifier.NullHandling nullHandling = castToQueryDsl.apply(order.getNullHandling()); + orders.add(getOrderSpecifier(order, nullHandling)); + } + + return orders; + } + + private static OrderSpecifier getOrderSpecifier(Sort.Order order, OrderSpecifier.NullHandling nullHandling) { + Order orderBy = order.isAscending() ? Order.ASC : Order.DESC; + + return createOrderSpecifier(orderBy, Expressions.stringPath(order.getProperty()), nullHandling); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static OrderSpecifier createOrderSpecifier(Order orderBy, Expression expression, OrderSpecifier.NullHandling queryDslNullHandling) { + if (expression instanceof Operation && ((Operation) expression).getOperator() == Ops.ALIAS) { + log.info("first expression: {}", expression); + return new OrderSpecifier<>(orderBy, Expressions.stringPath(((Operation) expression).getArg(1).toString()), queryDslNullHandling); + } else { + log.info("second expression: {}", expression); + return new OrderSpecifier(orderBy, expression, queryDslNullHandling); + } + } +} diff --git a/src/main/java/com/kcy/fitapet/global/common/util/querydsl/RepositorySliceHelper.java b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/RepositorySliceHelper.java new file mode 100644 index 00000000..4cf5d44b --- /dev/null +++ b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/RepositorySliceHelper.java @@ -0,0 +1,23 @@ +package com.kcy.fitapet.global.common.util.querydsl; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; + +import java.util.List; + +public class RepositorySliceHelper { + public static Slice toSlice(List contents, Pageable pageable) { + boolean hasNext = isContentSizeGreaterThanPageSize(contents, pageable); + return new SliceImpl<>(hasNext ? subListLastContent(contents, pageable) : contents, pageable, hasNext); + } + + private static boolean isContentSizeGreaterThanPageSize(List content, Pageable pageable) { + return pageable.isPaged() && content.size() > pageable.getPageSize(); + } + + // 데이터 1개 빼고 반환 + private static List subListLastContent(List content, Pageable pageable) { + return content.subList(0, pageable.getPageSize()); + } +} diff --git a/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java index 45e2e7d3..206f7ba9 100644 --- a/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java +++ b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java @@ -33,17 +33,14 @@ */ public class MySqlFunctionContributor implements FunctionContributor { private static final String FUNCTION_NAME = "match_against"; - private static final String NATURAL_LANGUAGE_PATTERN = "match (?1) against (?2 in natural language mode)"; - private static final String BOOLEAN_PATTERN = "match (?1) against (?2 in boolean mode)"; - private static final String QUERY_EXPANSION_PATTERN = "match (?1) against (?2 in query expansion mode)"; + private static final String TWO_COLUMN_BOOLEAN_PATTERN = "match(?1, ?2) against(?3 in boolean mode)"; @Override - public void contributeFunctions(FunctionContributions functionContributions) { + public void contributeFunctions(final FunctionContributions functionContributions) { SqmFunctionRegistry registry = functionContributions.getFunctionRegistry(); TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); - registry.registerPattern( FUNCTION_NAME, NATURAL_LANGUAGE_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); - registry.registerPattern( FUNCTION_NAME, BOOLEAN_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); - registry.registerPattern( FUNCTION_NAME, QUERY_EXPANSION_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.DOUBLE) ); + registry.registerPattern( FUNCTION_NAME, TWO_COLUMN_BOOLEAN_PATTERN, typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.BOOLEAN) ); + registry.registerPattern( "left", "left(?1, ?2)", typeConfiguration.getBasicTypeRegistry().resolve(StandardBasicTypes.STRING) ); } } diff --git a/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor b/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor new file mode 100644 index 00000000..fd20671f --- /dev/null +++ b/src/main/resources/META-INF/services/org.hibernate.boot.model.FunctionContributor @@ -0,0 +1 @@ +com.kcy.fitapet.global.config.MySqlFunctionContributor \ No newline at end of file From 3dcc9e64b7869b406816666f10f83621d4d96554 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Thu, 1 Feb 2024 02:52:12 +0900 Subject: [PATCH 17/18] =?UTF-8?q?fix:=20#83=20memo=5Fimage=20=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=B8=94=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20limit=20?= =?UTF-8?q?=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20Query=20=EC=A1=B0=EC=A0=95=20&&=20docs:=20QueryDsl?= =?UTF-8?q?=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 4 +- .../memo/dao/MemoQueryDslRepositoryImpl.java | 43 ++++++++++++++++--- .../fitapet/domain/memo/dto/MemoInfoDto.java | 28 +++--------- .../common/util/querydsl/QueryDslUtil.java | 16 ++++++- .../config/MySqlFunctionContributor.java | 14 +++--- 5 files changed, 62 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index 58f9a121..ecc4b9ce 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -34,7 +34,7 @@ public class MemoApi { @Parameter(name = "pet_id", description = "반려동물 ID", in = ParameterIn.PATH, required = true), @Parameter(name = "root_memo_category_id", description = "루트 메모 카테고리 ID를 의미하며, 서브 메모 카테고리인 경우 거부됩니다.", in = ParameterIn.PATH, required = true) }) - @PostMapping("/root-memo-categories/{root_memo_category_id}") // TODO: 2024-01-27: pet -> root-memo-categories 권한 검사 + @PostMapping("/root-memo-categories/{root_memo_category_id}") @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId) and @memoAuthorize.isValidRootMemoCategory(#rootMemoCategoryId, #petId)") public ResponseEntity saveSubMemoCategory( @PathVariable("pet_id") Long petId, @@ -112,7 +112,7 @@ public ResponseEntity getMemosAndSubCategories( @PreAuthorize("isAuthenticated() and @managerAuthorize.isManager(principal.userId, #petId)") public ResponseEntity getMemosByPet( @PathVariable("pet_id") Long petId, - @PageableDefault(size = 5, page = 0, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + @PageableDefault(size = 5, page = 0, sort = "memo.createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { return null; } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java index cfbb946c..5b65b754 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java @@ -29,6 +29,8 @@ import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.types.Projections.list; +import static com.querydsl.core.types.dsl.Expressions.constant; +import static com.querydsl.core.types.dsl.Expressions.constantAs; @Repository @RequiredArgsConstructor @@ -46,6 +48,7 @@ public Optional findMemoAndMemoImageUrlsById(Long memoId) Projections.constructor( MemoInfoDto.MemoInfo.class, memo.id, + memoCategory.categoryName, QueryDslUtil.left(memo.title, Expressions.constant(19)), QueryDslUtil.left(memo.content, Expressions.constant(16)), memo.createdAt, @@ -64,24 +67,50 @@ public Optional findMemoAndMemoImageUrlsById(Long memoId) .fetchFirst()); } + /** + * 메모 카테고리 ID로 메모 리스트 조회 및 검색 + *
+     * SELECT (SELECT category_name FROM memo_category WHERE id = 4) category_name, m.id, LEFT(m.title, 19), LEFT(m.content, 16) content, m.created_at, i.id, i.img_url
+     * FROM memo m
+     * LEFT JOIN memo_image i ON i.memo_id = m.id
+     * WHERE m.id IN (
+     * 	SELECT *
+     *     FROM (
+     * 		SELECT m.id
+     * 		FROM memo_category c
+     * 		INNER JOIN memo m ON m.category_id = c.id
+     * 		WHERE c.id = 4
+     * 		AND MATCH(m.title, m.content) AGAINST('병원*' IN BOOLEAN MODE)
+     * 		LIMIT 4
+     *     ) id
+     * )
+     * ORDER BY m.created_at DESC
+     * ;
+     * 
+ */ @Override public Slice findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target) { List results = queryFactory - .select(memoCategory.id, memo.id, memo.title, memo.content, memo.createdAt, memoImage.id, memoImage.imgUrl) .from(memo) - .innerJoin(memoCategory).on(memoCategory.id.eq(memo.memoCategory.id)) .leftJoin(memoImage).on(memoImage.memo.id.eq(memo.id)) - .where(memoCategory.id.eq(memoCategoryId) - .and(QueryDslUtil.matchAgainst(memo.title, memo.content, target)) - ) + .where(memo.id.in( + queryFactory + .select(memo.id) + .from(memoCategory) + .leftJoin(memo).on(memo.memoCategory.id.eq(memoCategory.id)) + .where(memoCategory.id.eq(memoCategoryId) + .and(QueryDslUtil.matchAgainst(memo.title, memo.content, target)) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + )) .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize() + 1) .transform( groupBy(memo.id).list( Projections.constructor( MemoInfoDto.MemoInfo.class, memo.id, + queryFactory.select(memoCategory.categoryName).from(memoCategory).where(memoCategory.id.eq(memoCategoryId)), QueryDslUtil.left(memo.title, Expressions.constant(19)), QueryDslUtil.left(memo.content, Expressions.constant(16)), memo.createdAt, diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java index 7c43f7bd..06019a86 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dto/MemoInfoDto.java @@ -31,6 +31,7 @@ public static MemoInfoDto of(List memoInfos) { @Dto(name = "memo") public record MemoInfo( Long memoId, + String categorySuffix, String title, String content, @JsonSerialize(using = LocalDateTimeSerializer.class) @@ -38,33 +39,14 @@ public record MemoInfo( LocalDateTime createdAt, List memoImages ) { - public MemoInfo(Long memoId, String title, String content, LocalDateTime createdAt, List memoImages) { + public MemoInfo(Long memoId, String categorySuffix, String title, String content, LocalDateTime createdAt, List memoImages) { this.memoId = memoId; + this.categorySuffix = categorySuffix; this.title = title.length() == 19 ? title + "..." : title; this.content = content.length() == 16 ? content + "..." : content; this.createdAt = createdAt; this.memoImages = (memoImages == null) ? List.of() : memoImages; } - - public static MemoInfo from(Memo memo) { - return MemoInfo.builder() - .memoId(memo.getId()) - .title(memo.getTitle()) - .content(memo.getContent()) - .createdAt(memo.getCreatedAt()) - .memoImages(List.of()) - .build(); - } - - public static MemoInfo valueOf(Memo memo, List memoImageUrls) { - return MemoInfo.builder() - .memoId(memo.getId()) - .title(memo.getTitle()) - .content(memo.getContent()) - .createdAt(memo.getCreatedAt()) - .memoImages(memoImageUrls) - .build(); - } } public record MemoImageInfo( @@ -79,8 +61,8 @@ public MemoImageInfo(Long memoImageId, String imgUrl) { public record PageResponse( @Schema(description = "메모 목록") List memos, - @Schema(description = "현재 페이지") int number, - @Schema(description = "페이지 크기") int size, + @Schema(description = "현재 페이지") int currentPageNumber, + @Schema(description = "페이지 크기") int pageSize, @Schema(description = "현재 페이지의 데이터 개수") int numberOfElements, @Schema(description = "다음 페이지 존재 여부") boolean hasNext ) { diff --git a/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java index cd26610c..4b9ab3ea 100644 --- a/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java +++ b/src/main/java/com/kcy/fitapet/global/common/util/querydsl/QueryDslUtil.java @@ -23,6 +23,12 @@ public class QueryDslUtil { case NULLS_LAST -> OrderSpecifier.NullHandling.NullsLast; }; + /** + * match_against 함수를 사용하여 memo 테이블의 title, content 컬럼과 target을 비교한다. + * @param c1 : memo.title + * @param c2 : memo.content + * @param target : 검색어 + */ public static BooleanExpression matchAgainst(final StringPath c1, final StringPath c2, final String target) { if (!StringUtils.hasText(target)) { return null; } String template = "'" + target + "*'"; @@ -30,10 +36,17 @@ public static BooleanExpression matchAgainst(final StringPath c1, final StringPa return Expressions.booleanTemplate( "function('match_against', {0}, {1}, {2})", c1, c2, template); } + /** + * LEFT 함수로 문자열을 c2 길이만큼 잘라서 반환한다. + */ public static StringExpression left(final StringPath c1, final Expression c2) { return Expressions.stringTemplate("function('left', {0}, {1})", c1, c2); } + /** + * Pageable의 sort를 QueryDsl의 OrderSpecifier로 변환한다. + * @param sort : Pageable의 sort + */ public static List> getOrderSpecifier(Sort sort) { List> orders = new ArrayList<>(); @@ -47,6 +60,7 @@ public static List> getOrderSpecifier(Sort sort) { private static OrderSpecifier getOrderSpecifier(Sort.Order order, OrderSpecifier.NullHandling nullHandling) { Order orderBy = order.isAscending() ? Order.ASC : Order.DESC; + log.info("isAscending: {}", order.isAscending()); return createOrderSpecifier(orderBy, Expressions.stringPath(order.getProperty()), nullHandling); } @@ -54,10 +68,8 @@ private static OrderSpecifier getOrderSpecifier(Sort.Order order, OrderSpecif @SuppressWarnings({ "rawtypes", "unchecked" }) private static OrderSpecifier createOrderSpecifier(Order orderBy, Expression expression, OrderSpecifier.NullHandling queryDslNullHandling) { if (expression instanceof Operation && ((Operation) expression).getOperator() == Ops.ALIAS) { - log.info("first expression: {}", expression); return new OrderSpecifier<>(orderBy, Expressions.stringPath(((Operation) expression).getArg(1).toString()), queryDslNullHandling); } else { - log.info("second expression: {}", expression); return new OrderSpecifier(orderBy, expression, queryDslNullHandling); } } diff --git a/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java index 206f7ba9..fdfc4732 100644 --- a/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java +++ b/src/main/java/com/kcy/fitapet/global/config/MySqlFunctionContributor.java @@ -14,18 +14,14 @@ * {@code * public List findAllBy(final String entityNameWord, final String entityAttrWord) { * return queryFactory.selectFrom(entity) - * .where( - * matchAgainst(entity.name, entityNameWord), - * matchAgainst(entity.attr, entityAttrWord) - * ) + * .where(matchAgainst(entity.name, entity.attr, entityAttrWord)) * .fetch(); * } * - * private BooleanExpression matchAgainst(final String target, final String searchWord) { - * if (!StringUtils.hasText(searchWord)) { - * return null - * } - * return Expressions.booleanTemplate("match({0}) against({1} in boolean mode) > 0", target, searchWord); + * public static BooleanExpression matchAgainst(final StringPath c1, final StringPath c2, final String target) { + * if (!StringUtils.hasText(target)) { return null; } + * String template = "'" + target + "*'"; + * return Expressions.booleanTemplate( "function('match_against', {0}, {1}, {2})", c1, c2, template); * } * * From 2e73481e800a8501ca8304c43a73458c5996b124 Mon Sep 17 00:00:00 2001 From: JaeSeo Yang <96044622+psychology50@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:16:40 +0900 Subject: [PATCH 18/18] =?UTF-8?q?feat:=20#83=20=EB=B0=98=EB=A0=A4=EB=8F=99?= =?UTF-8?q?=EB=AC=BC=EC=9D=98=20=EA=B0=80=EC=9E=A5=20=EC=B5=9C=EA=B7=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kcy/fitapet/domain/memo/api/MemoApi.java | 3 +- .../memo/dao/MemoQueryDslRepository.java | 1 + .../memo/dao/MemoQueryDslRepositoryImpl.java | 83 +++++++++++-------- .../service/component/MemoManageService.java | 5 ++ .../service/module/MemoSearchService.java | 7 ++ 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java index ecc4b9ce..a22d311b 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/api/MemoApi.java @@ -114,7 +114,8 @@ public ResponseEntity getMemosByPet( @PathVariable("pet_id") Long petId, @PageableDefault(size = 5, page = 0, sort = "memo.createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { - return null; + MemoInfoDto.PageResponse res = memoManageService.findMemosByPetId(petId, pageable); + return ResponseEntity.ok(SuccessResponse.from(res)); } @Operation(summary = "메모 단건 조회") diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java index c5a60d31..339bcd2f 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepository.java @@ -10,4 +10,5 @@ public interface MemoQueryDslRepository { Optional findMemoAndMemoImageUrlsById(Long memoId); Slice findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target); + Slice findMemosByPetId(Long petId, Pageable pageable); } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java index 5b65b754..3ab22082 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/dao/MemoQueryDslRepositoryImpl.java @@ -6,31 +6,22 @@ import com.kcy.fitapet.domain.memo.dto.MemoInfoDto; import com.kcy.fitapet.global.common.util.querydsl.QueryDslUtil; import com.kcy.fitapet.global.common.util.querydsl.RepositorySliceHelper; -import com.querydsl.core.QueryResults; -import com.querydsl.core.types.Expression; -import com.querydsl.core.types.Order; +import com.querydsl.core.ResultTransformer; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Repository; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Optional; import static com.querydsl.core.group.GroupBy.groupBy; import static com.querydsl.core.types.Projections.list; -import static com.querydsl.core.types.dsl.Expressions.constant; -import static com.querydsl.core.types.dsl.Expressions.constantAs; @Repository @RequiredArgsConstructor @@ -78,10 +69,11 @@ public Optional findMemoAndMemoImageUrlsById(Long memoId) * FROM ( * SELECT m.id * FROM memo_category c - * INNER JOIN memo m ON m.category_id = c.id - * WHERE c.id = 4 + * LEFT JOIN memo m ON m.category_id = c.id + * WHERE c.id = ? * AND MATCH(m.title, m.content) AGAINST('병원*' IN BOOLEAN MODE) - * LIMIT 4 + * ORDER BY m.created_at DESC + * LIMIT ?, ? * ) id * ) * ORDER BY m.created_at DESC @@ -101,30 +93,55 @@ public Slice findMemosInMemoCategory(Long memoCategoryId, .where(memoCategory.id.eq(memoCategoryId) .and(QueryDslUtil.matchAgainst(memo.title, memo.content, target)) ) + .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) .offset(pageable.getOffset()) .limit(pageable.getPageSize() + 1) )) - .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) - .transform( - groupBy(memo.id).list( - Projections.constructor( - MemoInfoDto.MemoInfo.class, - memo.id, - queryFactory.select(memoCategory.categoryName).from(memoCategory).where(memoCategory.id.eq(memoCategoryId)), - QueryDslUtil.left(memo.title, Expressions.constant(19)), - QueryDslUtil.left(memo.content, Expressions.constant(16)), - memo.createdAt, - list( - Projections.constructor( - MemoInfoDto.MemoImageInfo.class, - memoImage.id, - memoImage.imgUrl - ).skipNulls() - ).skipNulls() - ) - ) - ); + .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) + .transform(createMemoInfoDtoResultTransformer()); return RepositorySliceHelper.toSlice(results, pageable); } + + @Override + public Slice findMemosByPetId(Long petId, Pageable pageable) { + List results = queryFactory + .from(memoCategory) + .leftJoin(memo).on(memo.memoCategory.id.eq(memoCategory.id)) + .leftJoin(memoImage).on(memoImage.memo.id.eq(memo.id)) + .where(memo.id.in( + queryFactory + .select(memo.id) + .from(memoCategory) + .leftJoin(memo).on(memo.memoCategory.id.eq(memoCategory.id)) + .where(memoCategory.pet.id.eq(petId)) + .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize() + 1) + )) + .orderBy(QueryDslUtil.getOrderSpecifier(pageable.getSort()).toArray(OrderSpecifier[]::new)) + .transform(createMemoInfoDtoResultTransformer()); + + return RepositorySliceHelper.toSlice(results, pageable); + } + + private ResultTransformer> createMemoInfoDtoResultTransformer() { + return groupBy(memo.id).list( + Projections.constructor( + MemoInfoDto.MemoInfo.class, + memo.id, + queryFactory.select(memoCategory.categoryName).from(memoCategory).where(memoCategory.id.eq(memo.memoCategory.id)), + QueryDslUtil.left(memo.title, Expressions.constant(19)), + QueryDslUtil.left(memo.content, Expressions.constant(16)), + memo.createdAt, + list( + Projections.constructor( + MemoInfoDto.MemoImageInfo.class, + memoImage.id, + memoImage.imgUrl + ).skipNulls() + ).skipNulls() + ) + ); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java index 072647ba..8c3ee7e0 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/component/MemoManageService.java @@ -62,4 +62,9 @@ public MemoInfoDto.MemoInfo findMemoAndMemoImageUrlsById(Long memoId) { public MemoInfoDto.PageResponse findMemosInMemoCategory(Long memoCategoryId, Pageable pageable, String target) { return memoSearchService.findMemosInMemoCategory(memoCategoryId, pageable, target); } + + @Transactional(readOnly = true) + public MemoInfoDto.PageResponse findMemosByPetId(Long petId, Pageable pageable) { + return memoSearchService.findMemosByPetId(petId, pageable); + } } diff --git a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java index 6fc641e1..46f41041 100644 --- a/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java +++ b/src/main/java/com/kcy/fitapet/domain/memo/service/module/MemoSearchService.java @@ -80,4 +80,11 @@ public MemoInfoDto.PageResponse findMemosInMemoCategory(Long memoCategoryId, Pag return MemoInfoDto.PageResponse.from(page); } + + @Transactional(readOnly = true) + public MemoInfoDto.PageResponse findMemosByPetId(Long petId, Pageable pageable) { + Slice page = memoRepository.findMemosByPetId(petId, pageable); + + return MemoInfoDto.PageResponse.from(page); + } } \ No newline at end of file