diff --git a/.idea/compiler.xml b/.idea/compiler.xml index dcf922e5..ff2e3754 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -9,7 +9,7 @@ - + diff --git a/gusto/.idea/inspectionProfiles/Project_Default.xml b/gusto/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..6f67cf72 --- /dev/null +++ b/gusto/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java index ee41a8d1..7585f247 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/group/service/GroupServiceImpl.java @@ -360,7 +360,7 @@ public PagingResponse getAllGroupList(Long groupId, Long groupListId,User user) // 그룹 리스트에 해당하는 각 상점 정보 조회 List list = groupLists.stream().map(gl -> { - Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(user,gl.getStore()); // 가장 좋아요가 많은 review + Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(gl.getStore()); // 가장 좋아요가 많은 review String reviewImg = topReviewOptional.map(Review::getImg1).orElse(""); return GroupListResponse.builder() .groupListId(gl.getGroupListId()) diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/controller/MyCategoryController.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/controller/MyCategoryController.java index 0a5b33e9..cd96986b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/controller/MyCategoryController.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/controller/MyCategoryController.java @@ -33,7 +33,8 @@ public ResponseEntity allMyCategory( @RequestParam(name = "nickname", required = false) String nickname, @RequestParam(name = "townName", required = false) String townName, @RequestParam(name = "myCategoryId", required = false) Long myCategoryId) { // paging 처리를 위해 마지막 리턴 myCategoryId 사용 - User user = authUser.getUser(); + + User user = (nickname == null) ? authUser.getUser() : null; PagingResponse pagingResponse = myCategoryService.getAllMyCategory(user, nickname, townName, myCategoryId); return ResponseEntity.status(HttpStatus.OK).body(pagingResponse); @@ -51,19 +52,15 @@ public ResponseEntity allPinByMyCategory( @RequestParam(name = "townName", required = false) String townName, @RequestParam(name = "pinId", required = false) Long pinId, @RequestParam(name = "storeName", required = false) String storeName, - @RequestParam(name = "sort", required = false) String sort// paging 처리를 위해 마지막 리턴 pinId 사용 + @RequestParam(name = "sort", required = false) String sort // paging 처리를 위해 마지막 리턴 pinId 사용 ) { - User user = authUser.getUser(); + + User user = (nickname == null) ? authUser.getUser() : null; PagingResponse pagingResponse = myCategoryService.getAllPinByMyCategory(user, nickname, myCategoryId, townName, pinId, storeName, sort); return ResponseEntity.status(HttpStatus.OK).body(pagingResponse); } - /** - * 내 카테고리 공개 상태 업데이트 - * [ - */ - /** * 내 카테고리 생성 * [POST] /myCategories diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/MyCategoryResponse.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/MyCategoryResponse.java index 567dd51f..380cd874 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/MyCategoryResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/MyCategoryResponse.java @@ -1,6 +1,6 @@ package com.umc.gusto.domain.myCategory.model.response; -import com.umc.gusto.global.common.PublishStatus; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.*; @Builder @@ -12,8 +12,9 @@ public class MyCategoryResponse{ String myCategoryName; String myCategoryScript; Integer myCategoryIcon; - PublishStatus publishCategory; - PublishStatus userPublishCategory; + @JsonInclude(JsonInclude.Include.NON_NULL) + Boolean publishCategory; + Boolean userPublishCategory; Integer pinCnt; } diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PagingResponse.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PagingResponse.java index b49bb9f7..a886a000 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PagingResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/model/response/PagingResponse.java @@ -12,7 +12,5 @@ @Builder public class PagingResponse { private boolean hasNext; - private PublishStatus userPublishCategory; - private PublishStatus publishCategory; private List result; } diff --git a/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java index 875a25c0..d2fa25d6 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/myCategory/service/MyCategoryServiceImpl.java @@ -44,10 +44,6 @@ public class MyCategoryServiceImpl implements MyCategoryService { public PagingResponse getAllMyCategory(User user, String nickname, String townName, Long myCategoryId) { Page myCategoryList; if (nickname != null) { - if (nickname.equals(user.getNickname())) { - throw new GeneralException(Code.USER_NOT_FOUND_SELF); - } - user = userRepository.findByNickname(nickname) // 타 닉네임 조회 .orElseThrow(() -> new GeneralException(Code.USER_NOT_FOUND)); if (myCategoryId != null) { @@ -78,8 +74,8 @@ public PagingResponse getAllMyCategory(User user, String nickname, String townNa .myCategoryName(myCategory.getMyCategoryName()) .myCategoryScript(myCategory.getMyCategoryScript()) .myCategoryIcon(myCategory.getMyCategoryIcon()) - .publishCategory(myCategory.getPublishCategory()) - .userPublishCategory(finalUser.getPublishCategory()) + .publishCategory(myCategory.getPublishCategory() == PublishStatus.PUBLIC) // PublishCategory가 PUBLIC이면 true 반환 + .userPublishCategory(finalUser.getPublishCategory() == PublishStatus.PUBLIC) .pinCnt(pinList.size()) .build(); }) @@ -165,7 +161,7 @@ public PagingResponse getAllPinByMyCategory(User user, String nickname, Long myC List result = pinList.stream() // townName을 기준으로 보일 수 있는 store가 포함된 pin만 보이기 .map(pin -> { Store store = pin.getStore(); - List topReviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store, finalUser); // 가장 좋아요가 많은 review // 가장 좋아요가 많은 review 이미지(TO DO: 3개 출력으로 변경) + List topReviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store); // 가장 좋아요가 많은 review // 가장 좋아요가 많은 review 이미지(TO DO: 3개 출력으로 변경) Integer reviewCnt = reviewRepository.countByStoreAndUserNickname(store, finalUser.getNickname()); // 내가 작성한 리뷰의 개수 == 방문 횟수 String img1 = !topReviews.isEmpty() ? topReviews.get(0).getImg1() : ""; @@ -188,8 +184,6 @@ public PagingResponse getAllPinByMyCategory(User user, String nickname, Long myC return PagingResponse.builder() .hasNext(pinList.hasNext()) - .userPublishCategory(user.getPublishCategory()) - .publishCategory(myCategory.get().getPublishCategory()) .result(result) .build(); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/controller/FeedController.java b/gusto/src/main/java/com/umc/gusto/domain/review/controller/FeedController.java index 77616a09..83b62af7 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/controller/FeedController.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/controller/FeedController.java @@ -24,8 +24,9 @@ public ResponseEntity getRandomFeed(@AuthenticationPrincipal AuthUser authUse } @GetMapping("/search") - public ResponseEntity searchFeed(@RequestParam(name = "keyword", required = false) String keyword, @RequestParam(name = "hashTags", required = false) List hashTags){ - return ResponseEntity.status(HttpStatus.OK).body(feedService.searchFeed(keyword, hashTags)); + public ResponseEntity searchFeed(@RequestParam(name = "keyword", required = false) String keyword, @RequestParam(name = "hashTags", required = false) List hashTags, + @RequestParam(name = "cursorId", required = false) Long cursorId){ + return ResponseEntity.status(HttpStatus.OK).body(feedService.searchFeed(keyword, hashTags, cursorId)); } @GetMapping("{reviewId}") diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/entity/Review.java b/gusto/src/main/java/com/umc/gusto/domain/review/entity/Review.java index a12324ba..0c8e7849 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/entity/Review.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/entity/Review.java @@ -99,22 +99,6 @@ public void updateTaste(Integer taste) { this.taste = taste; } - public void updateSpiciness(Integer spiciness) { - this.spiciness = spiciness; - } - - public void updateMood(Integer mood){ - this.mood = mood; - } - - public void updateToilet(Integer toilet){ - this.toilet = toilet; - } - - public void updateParking(Integer parking){ - this.parking = parking; - } - public void updateComment(String comment){ this.comment = comment; } @@ -144,4 +128,8 @@ public void updateLiked(String type){ if(type.equals("like")) this.liked += 1; else if(type.equals("unlike")) this.liked -= 1; } + + public void updatePublishReview(boolean check){ + this.publishReview = PublishStatus.of(check); + } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/model/request/CreateReviewRequest.java b/gusto/src/main/java/com/umc/gusto/domain/review/model/request/CreateReviewRequest.java index b84639f9..6b9fc94a 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/model/request/CreateReviewRequest.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/model/request/CreateReviewRequest.java @@ -21,18 +21,8 @@ public class CreateReviewRequest { @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") Integer taste; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer spiciness; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer mood; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer toilet; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer parking; @Size(max=200, message = "내용은 200자를 초과할 수 없습니다.") String comment; + @NotNull(message = "전체공개, 나만보기 둘 중 하나는 선택해야 합니다.") + boolean publicCheck; } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/model/request/UpdateReviewRequest.java b/gusto/src/main/java/com/umc/gusto/domain/review/model/request/UpdateReviewRequest.java index 0d146ed9..8bb6c02e 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/model/request/UpdateReviewRequest.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/model/request/UpdateReviewRequest.java @@ -16,18 +16,7 @@ public class UpdateReviewRequest { @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") Integer taste; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer spiciness; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer mood; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer toilet; - @DecimalMin(value = "0", message = "점수가 0보다 작을 수 없습니다.") - @DecimalMax(value = "5", message = "점수가 5보다 클 수 없습니다.") - Integer parking; @Size(max=200, message = "내용은 200자를 초과할 수 없습니다.") String comment; + Boolean publicCheck; //Boolean box화한 기본 자료형을 사용하여 null이 들어올 수 있도록 한다. } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewDetailResponse.java b/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewDetailResponse.java index dceb6f26..5eafd86e 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewDetailResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewDetailResponse.java @@ -21,16 +21,9 @@ public class ReviewDetailResponse { List hashTags; Integer taste; @JsonInclude(JsonInclude.Include.NON_NULL) - Integer spiciness; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer mood; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer toilet; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer parking; - @JsonInclude(JsonInclude.Include.NON_NULL) String comment; Integer likeCnt; + boolean publicCheck; public static ReviewDetailResponse of(Review review, List hashTags){ return ReviewDetailResponse.builder() @@ -41,12 +34,9 @@ public static ReviewDetailResponse of(Review review, List hashTags){ .menuName(review.getMenuName()) .hashTags(hashTags) .taste(review.getTaste()) - .spiciness(review.getSpiciness()) - .mood(review.getMood()) - .toilet(review.getToilet()) - .parking(review.getParking()) .comment(review.getComment()) .likeCnt(review.getLiked()) + .publicCheck(review.getPublishReview().isCheck()) .build(); } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewResponse.java b/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewResponse.java deleted file mode 100644 index 290daa67..00000000 --- a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/ReviewResponse.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.umc.gusto.domain.review.model.response; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.umc.gusto.domain.review.entity.Review; -import lombok.Builder; -import lombok.Getter; - -import java.time.LocalDate; - -public class ReviewResponse { - @Builder - @Getter - public static class ReviewDetailDTO{ - Long storeId; - String storeName; - String nickName; - LocalDate visitedAt; - //TODO: img고려 - @JsonInclude(JsonInclude.Include.NON_NULL) - String menuName; - @JsonInclude(JsonInclude.Include.NON_NULL) - String hashTags; - Integer taste; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer spiciness; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer mood; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer toilet; - @JsonInclude(JsonInclude.Include.NON_NULL) - Integer parking; - @JsonInclude(JsonInclude.Include.NON_NULL) - String comment; - Integer likeCnt; - - public static ReviewDetailDTO of(Review review, String hashTags){ - return ReviewDetailDTO.builder() - .storeId(review.getStore().getStoreId()) - .storeName(review.getStore().getStoreName()) - .nickName(review.getUser().getNickname()) - .visitedAt(review.getVisitedAt()) -// .img() TODO: img 처리하기 - .menuName(review.getMenuName()) - .hashTags(hashTags) - .taste(review.getTaste()) - .spiciness(review.getSpiciness()) - .mood(review.getMood()) - .toilet(review.getToilet()) - .parking(review.getParking()) - .comment(review.getComment()) - .likeCnt(review.getLiked()) - .build(); - } - } -} diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/SearchFeedResponse.java b/gusto/src/main/java/com/umc/gusto/domain/review/model/response/SearchFeedResponse.java index 86977960..f85f6f46 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/model/response/SearchFeedResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/model/response/SearchFeedResponse.java @@ -5,14 +5,21 @@ import java.util.List; + + @Builder @Getter public class SearchFeedResponse { List reviews; + boolean hasNext; + Long cursorId; + - public static SearchFeedResponse of(List reviews){ + public static SearchFeedResponse of(List reviews, boolean hasNext, Long cursorId){ return SearchFeedResponse.builder() .reviews(reviews) + .hasNext(hasNext) + .cursorId(cursorId) .build(); } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/repository/ReviewRepository.java b/gusto/src/main/java/com/umc/gusto/domain/review/repository/ReviewRepository.java index 33d439ed..d40ec94f 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/repository/ReviewRepository.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/repository/ReviewRepository.java @@ -17,16 +17,16 @@ import java.util.UUID; public interface ReviewRepository extends JpaRepository { - @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND (r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' OR r.user = :user) AND r.store = :store ORDER BY r.liked DESC LIMIT 1") - Optional findFirstByStoreOrderByLikedDesc(@Param("user") User user, @Param("store") Store store); - @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND (r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' OR r.user = :user) AND r.store = :store ORDER BY r.liked DESC, r.reviewId DESC LIMIT 3") // - List findFirst3ByStoreOrderByLikedDesc(@Param("user") User user, @Param("store") Store store); - @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND (r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' OR r.user = :user) AND r.store = :store ORDER BY r.liked DESC, r.reviewId DESC LIMIT 4") // - List findFirst4ByStoreOrderByLikedDesc(@Param("store") Store store , @Param("user") User user); - @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND (r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' OR r.user = :user) AND r.store = :store ORDER BY r.visitedAt DESC, r.reviewId DESC") - Page findFirstReviewsByStore(@Param("user") User user, @Param("store") Store store, Pageable pageable); - @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND (r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' OR r.user = :user) AND r.store = :store AND r.visitedAt <= :visitedAt AND r.reviewId < :reviewId ORDER BY r.visitedAt DESC, r.reviewId DESC") - Page findReviewsAfterIdByStore(@Param("user") User user, @Param("store") Store store, @Param("visitedAt") LocalDate visitedAt, @Param("reviewId") Long reviewId, Pageable pageable); + @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' AND r.store = :store ORDER BY r.liked DESC LIMIT 1") + Optional findFirstByStoreOrderByLikedDesc(@Param("store") Store store); + @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' AND r.store = :store ORDER BY r.liked DESC, r.reviewId DESC LIMIT 3") // + List findFirst3ByStoreOrderByLikedDesc(@Param("store") Store store); + @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' AND r.store = :store ORDER BY r.liked DESC, r.reviewId DESC LIMIT 4") // + List findFirst4ByStoreOrderByLikedDesc(@Param("store") Store store); + @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC' AND r.store = :store ORDER BY r.visitedAt DESC, r.reviewId DESC") + Page findFirstReviewsByStore(@Param("store") Store store, Pageable pageable); + @Query("SELECT r FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.user.publishReview = 'PUBLIC' AND r.publishReview = 'PUBLIC'AND r.store = :store AND r.visitedAt <= :visitedAt AND r.reviewId < :reviewId ORDER BY r.visitedAt DESC, r.reviewId DESC") + Page findReviewsAfterIdByStore(@Param("store") Store store, @Param("visitedAt") LocalDate visitedAt, @Param("reviewId") Long reviewId, Pageable pageable); boolean existsByReviewIdAndUser(Long reviewId, User user); @Query("SELECT count(r.reviewId) FROM Review r WHERE r.user.memberStatus = 'ACTIVE' AND r.status = 'ACTIVE' AND r.store = :store AND r.user.nickname = :nickname") Integer countByStoreAndUserNickname(Store store, String nickname); // 방문횟수는 리뷰 공개여부과 상관 X @@ -35,19 +35,38 @@ public interface ReviewRepository extends JpaRepository { Optional findByReviewIdAndStatus(Long reviewId, BaseEntity.Status status); Optional> findAllByUserAndStatus(User user, BaseEntity.Status status, PageRequest pageRequest); - Optional> findAllByUserAndStatusAndReviewIdLessThanAndVisitedAtLessThanEqual(User user, BaseEntity.Status status, Long reviewId, LocalDate visitedAt,PageRequest pageRequest); List findByUserAndStatusAndVisitedAtBetween(User user, BaseEntity.Status status, LocalDate startDate, LocalDate lastDate); - @Query(value = "SELECT * FROM review r WHERE r.user_id <> :user AND r.status = 'ACTIVE' AND r.skip_check=false ORDER BY RAND() limit 33", nativeQuery = true) + @Query(value = "SELECT * FROM review r join user u on r.user_id = u.user_id WHERE r.user_id <> :user AND u.publish_review = 'PUBLIC' AND r.status = 'ACTIVE' AND r.publish_review = 'PUBLIC' AND r.skip_check=false ORDER BY RAND() limit 33", nativeQuery = true) List findRandomFeedByUser(@Param("user") UUID user); //WHERE r.user_id <> :userZ boolean existsByStoreAndUserNickname(Store store, String nickname); - //검색 기능 - @Query("SELECT r FROM Review r WHERE r.status = 'ACTIVE' AND r.skipCheck = false AND r.store.storeName like concat('%', :keyword, '%') OR r.comment like concat('%', :keyword, '%')") - List searchByStoreContains(String keyword, PageRequest pageRequest); //TODO: 후에 페이징 처리 하기 - @Query("SELECT t.review FROM Tagging t WHERE t.review.status = 'ACTIVE' AND t.review.skipCheck=false AND t.review.store.storeName like concat('%', :keyword, '%') AND t.hashTag.hasTagId = :hashTagId") - List searchByStoreAndHashTagContains(String keyword, Long hashTagId, PageRequest pageRequest); - @Query("SELECT t.review FROM Tagging t WHERE t.review.status = 'ACTIVE' AND t.review.skipCheck=false AND t.hashTag.hasTagId = :hashTagId") - List searchByHashTagContains(Long hashTagId, PageRequest pageRequest); + /* + 검색 관련 + */ + @Query("SELECT r FROM Review r WHERE r.status = 'ACTIVE' AND r.publishReview = 'PUBLIC' AND r.skipCheck = false " + + "AND r.reviewId < :cursorId AND r.store.storeName like concat('%', :keyword, '%') OR r.comment like concat('%', :keyword, '%')" + + "ORDER BY r.reviewId desc") + Page searchByStoreContains(String keyword, Long cursorId, PageRequest pageRequest); //TODO: 후에 페이징 처리 하기 + @Query("SELECT t.review FROM Tagging t WHERE t.review.status = 'ACTIVE' AND t.review.publishReview = 'PUBLIC' AND t.review.skipCheck=false " + + "AND t.review.reviewId < :cursorId AND t.review.store.storeName like concat('%', :keyword, '%') AND t.hashTag.hasTagId = :hashTagId" + + " ORDER BY t.review.reviewId desc") + Page searchByStoreAndHashTagContains(String keyword, Long hashTagId, Long cursorId, PageRequest pageRequest); + @Query("SELECT t.review FROM Tagging t WHERE t.review.status = 'ACTIVE' AND t.review.publishReview = 'PUBLIC' AND t.review.skipCheck=false " + + "AND t.review.reviewId < :cursorId AND t.hashTag.hasTagId = :hashTagId ORDER BY t.review.reviewId desc") + Page searchByHashTagContains(Long hashTagId, Long cursorId, PageRequest pageRequest); + + /* + 리뷰 모아보기 페이징 처리 + */ + @Query("select r from Review r where r.user = :user and r.status = 'ACTIVE' and r.publishReview = 'PUBLIC'") + Page pagingInstaViewNoCursor(User user, PageRequest pageRequest); + @Query("select r from Review r where r.user = :user and r.status = 'ACTIVE' and r.publishReview = 'PUBLIC' " + + "and r.visitedAt < :visitedAt or (r.visitedAt = :visitedAt and r.reviewId < :reviewId)") + Page pagingInstaView(User user, Long reviewId, LocalDate visitedAt,PageRequest pageRequest); + + @Query("select r from Review r where r.user = :user and r.status = 'ACTIVE'" + + "and r.visitedAt < :visitedAt or (r.visitedAt = :visitedAt and r.reviewId < :reviewId)") + Page pagingMyReview(User user, Long reviewId, LocalDate visitedAt,PageRequest pageRequest); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/CollectReviewServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/CollectReviewServiceImpl.java index 8ce58951..7d97255f 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/CollectReviewServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/CollectReviewServiceImpl.java @@ -6,10 +6,8 @@ import com.umc.gusto.domain.user.entity.User; import com.umc.gusto.domain.user.repository.UserRepository; import com.umc.gusto.global.common.BaseEntity; -import com.umc.gusto.global.common.PublishStatus; import com.umc.gusto.global.exception.Code; import com.umc.gusto.global.exception.customException.NotFoundException; -import com.umc.gusto.global.exception.customException.PrivateItemException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -34,7 +32,7 @@ public CollectReviewsResponse getReviewOfInstaView(User user, Long reviewId, int //다음에 조회될 리뷰가 있는지 확인하기 boolean checkNext = reviews.hasNext(); List basicViewResponse = reviews.map(BasicViewResponse::of).toList(); -// Long cursorId = basicViewResponse.get(basicViewResponse.size()-1).getReviewId(); + Long cursorId = null; if(!basicViewResponse.isEmpty()){ cursorId = basicViewResponse.get(basicViewResponse.size()-1).getReviewId(); @@ -42,6 +40,7 @@ public CollectReviewsResponse getReviewOfInstaView(User user, Long reviewId, int return CollectReviewsResponse.of(basicViewResponse, cursorId,checkNext); } + @Override public CollectReviewsOfCalResponse getReviewOfCalView(User user, Long reviewId, int size, LocalDate date) { //해당 달의 첫 날짜, 마지막 날짜 구하기 @@ -77,25 +76,38 @@ public CollectReviewsResponse getReviewOfTimeView(User user, Long reviewId, int @Override public CollectReviewsResponse getOthersReview(String nickName, Long reviewId, int size) { + Page reviews; User other = userRepository.findByNicknameAndMemberStatusIs(nickName, User.MemberStatus.ACTIVE).orElseThrow(()-> new NotFoundException(Code.USER_NOT_FOUND)); - //다른 유저의 공개 여부 확인 - if(!other.getPublishReview().equals(PublishStatus.PUBLIC)){ - throw new PrivateItemException(Code.NO_PUBLIC_REVIEW); + + //사용자가 리뷰 프로필에 표기를 했는지 체크 + if(!other.getPublishReview().isCheck()){ // 표기 X + return CollectReviewsResponse.builder().build(); } - //페이징해서 가져오기 - Page reviews = pagingReview(other, reviewId, size); + //사용자의 리뷰들 중 public인 것만 추출 + Sort sort = Sort.by("visitedAt").descending().and(Sort.by("reviewId").descending()); + PageRequest pageRequest = PageRequest.of(0, size, sort); - //다음에 조회될 리뷰가 있는지 확인하기 + if(reviewId == null){ + reviews = reviewRepository.pagingInstaViewNoCursor(other, pageRequest); + }else { + Review review = reviewRepository.findById(reviewId).orElseThrow(()->new NotFoundException(Code.REVIEW_NOT_FOUND)); + reviews = reviewRepository.pagingInstaView(other, reviewId, review.getVisitedAt(), pageRequest); + } + + //다음 조회 되는 리뷰가 있는지 확인 boolean checkNext = reviews.hasNext(); + + //response 형태로 변환 List basicViewResponse = reviews.map(BasicViewResponse::of).toList(); - Long cursorId = basicViewResponse.get(basicViewResponse.size()-1).getReviewId(); - return CollectReviewsResponse.of(basicViewResponse, cursorId, checkNext); + Long cursorId = basicViewResponse.isEmpty() ? null : basicViewResponse.get(basicViewResponse.size()-1).getReviewId(); + + return CollectReviewsResponse.of(basicViewResponse, cursorId,checkNext); } private Page pagingReview(User user, Long cursorId, int size){ //최신순 날짜로 정렬 - Sort sort = Sort.by("visitedAt").descending().and(Sort.by("createdAt").descending()); + Sort sort = Sort.by("visitedAt").descending().and(Sort.by("reviewId").descending()); PageRequest pageRequest = PageRequest.of(0, size, sort); Page reviews; @@ -105,7 +117,7 @@ private Page pagingReview(User user, Long cursorId, int size){ }else{ //최초가 아닌 경우 //커서 id를 기반으로 그보다 낮은 ID의 리뷰를 가져온다 => 최신 날짜가 이전의 데이터가 나타난다. Review review = reviewRepository.findById(cursorId).orElseThrow(()->new NotFoundException(Code.REVIEW_NOT_FOUND)); - reviews = reviewRepository.findAllByUserAndStatusAndReviewIdLessThanAndVisitedAtLessThanEqual(user, BaseEntity.Status.ACTIVE, cursorId, review.getVisitedAt(),pageRequest).orElseThrow(()-> new NotFoundException(Code.REVIEW_NOT_FOUND)); + reviews = reviewRepository.pagingMyReview(user, cursorId, review.getVisitedAt(),pageRequest); } if(reviews.isEmpty()){ diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedService.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedService.java index 757d0b87..a0797e0b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedService.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedService.java @@ -9,6 +9,6 @@ public interface FeedService { List getRandomFeed(User user); - SearchFeedResponse searchFeed(String keyword, List hashTags); + SearchFeedResponse searchFeed(String keyword, List hashTags, Long cursorId); FeedDetailResponse getFeedDetail(User user, Long reviewId); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedServiceImpl.java index d65ec47e..41540f48 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/FeedServiceImpl.java @@ -11,6 +11,7 @@ import com.umc.gusto.global.exception.customException.NotFoundException; import com.umc.gusto.global.exception.customException.PrivateItemException; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -31,26 +32,40 @@ public List getRandomFeed(User user) { } @Override - public SearchFeedResponse searchFeed(String keyword, List hashTags) { - List searchResult = new ArrayList<>(); + public SearchFeedResponse searchFeed(String keyword, List hashTags, Long cursor) { //TODO: 검색 결과가 랜덤으로 다시 정렬되야 할듯 & 자기 리뷰가 아니여야함 + Page searchResult = null; PageRequest pageRequest = PageRequest.of(0,33); + + if(cursor == null){ + cursor = Long.MAX_VALUE; + } + //맛집과 해시태그를 함께 검색하는 경우 if(keyword!=null && hashTags!=null){ for(Long hashTagId: hashTags){ - searchResult.addAll(reviewRepository.searchByStoreAndHashTagContains(keyword, hashTagId, pageRequest)); +// searchResult.addAll(reviewRepository.searchByStoreAndHashTagContains(keyword, hashTagId, pageRequest)); + searchResult = reviewRepository.searchByStoreAndHashTagContains(keyword, hashTagId, cursor, pageRequest); } } else if(keyword!=null){ //맛집을 검색하는 경우 - searchResult = reviewRepository.searchByStoreContains(keyword, pageRequest); + searchResult = reviewRepository.searchByStoreContains(keyword, cursor, pageRequest); } else if(hashTags!=null){ for(Long hashTagId: hashTags){ - searchResult.addAll(reviewRepository.searchByHashTagContains(hashTagId, pageRequest)); + searchResult = reviewRepository.searchByHashTagContains(hashTagId, cursor, pageRequest); } } + if(searchResult == null){ + return SearchFeedResponse.builder().build(); + } + + boolean checkNext = searchResult.hasNext(); List basicViewResponse = searchResult.stream().map(BasicViewResponse::of).toList(); - return SearchFeedResponse.of(basicViewResponse); + Long cursorId = basicViewResponse.isEmpty() ? null : basicViewResponse.get(basicViewResponse.size()-1).getReviewId(); + + return SearchFeedResponse.of(basicViewResponse, checkNext, cursorId); } + //TODO: 해당 함수도 이미 피드에서 보인 리뷰임. 그래서 디테일이 안보이면 안됨 사용자 공개 체크를 할 필요가 없음 @Override public FeedDetailResponse getFeedDetail(User user, Long reviewId) { //TOOD: reviewServiceImpl와 중복되는 코드 분리하기 diff --git a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java index be947273..492defc5 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/review/service/ReviewServiceImpl.java @@ -65,11 +65,8 @@ public void createReview(User user, List images, CreateReviewRequ .visitedAt(visitedAt) .menuName(createReviewRequest.getMenuName()) .taste(createReviewRequest.getTaste()) - .spiciness(createReviewRequest.getSpiciness()) - .mood(createReviewRequest.getMood()) - .toilet(createReviewRequest.getToilet()) - .parking(createReviewRequest.getParking()) .comment(createReviewRequest.getComment()) + .publishReview(PublishStatus.of(createReviewRequest.isPublicCheck())) .build(); //TODO: review 엔티티에서 이미지를 분리하거나 monogoDB를 쓰는게 나을 듯, 나머지 기능 개발 후 바꿀 예정 @@ -117,18 +114,6 @@ public void updateReview(Long reviewId, List images, UpdateReview if(updateReviewRequest.getTaste()!=null){ review.updateTaste(updateReviewRequest.getTaste()); } - if(updateReviewRequest.getSpiciness()!=null){ - review.updateSpiciness(updateReviewRequest.getSpiciness()); - } - if(updateReviewRequest.getMood()!=null){ - review.updateMood(updateReviewRequest.getMood()); - } - if(updateReviewRequest.getToilet()!=null){ - review.updateToilet(updateReviewRequest.getToilet()); - } - if(updateReviewRequest.getParking()!=null){ - review.updateParking(updateReviewRequest.getParking()); - } if(updateReviewRequest.getComment()!=null){ review.updateComment(updateReviewRequest.getComment()); } @@ -137,6 +122,10 @@ public void updateReview(Long reviewId, List images, UpdateReview updateImages(images, review); } + if(updateReviewRequest.getPublicCheck() != null){ + review.updatePublishReview(updateReviewRequest.getPublicCheck()); + } + reviewRepository.save(review); } @@ -153,6 +142,7 @@ public void deleteReview(User user, Long reviewId) { public ReviewDetailResponse getReview(Long reviewId) { Review review = reviewRepository.findByReviewIdAndStatus(reviewId, BaseEntity.Status.ACTIVE).orElseThrow(()->new NotFoundException(Code.REVIEW_NOT_FOUND)); //TODO: 후에 각 리뷰마다의 공개, 비공개를 확인해서 주는거로 수정하기 + //TODO: 해당 함수는 사용자의 공개를 체크하면 안 될 것 같은데?? 리뷰 하나를 조회하는 것으로 분명 앞에서 리뷰가 보였으니까 해당 함수를 사용할 것임 if(!review.getUser().getPublishReview().equals(PublishStatus.PUBLIC)){ throw new PrivateItemException(Code.NO_PUBLIC_REVIEW); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/route/model/response/RouteRouteListResponse.java b/gusto/src/main/java/com/umc/gusto/domain/route/model/response/RouteRouteListResponse.java index 21266a9f..3acd282b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/route/model/response/RouteRouteListResponse.java +++ b/gusto/src/main/java/com/umc/gusto/domain/route/model/response/RouteRouteListResponse.java @@ -1,5 +1,6 @@ package com.umc.gusto.domain.route.model.response; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -14,5 +15,7 @@ public class RouteRouteListResponse { private Long routeId; private String routeName; + @JsonInclude(JsonInclude.Include.NON_NULL) + private boolean publishRoute; private List routes; } diff --git a/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteListServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteListServiceImpl.java index 3f55fc1c..baec17dd 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteListServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteListServiceImpl.java @@ -163,6 +163,7 @@ public RouteRouteListResponse getRouteListDetail(Long routeId, User user, Long g return RouteRouteListResponse.builder() .routeId(route.getRouteId()) .routeName(route.getRouteName()) + .publishRoute(route.getPublishRoute() == PublishStatus.PUBLIC ? true : false) .routes(routeLists) .build(); diff --git a/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteServiceImpl.java index a35359d4..fce6624c 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/route/service/RouteServiceImpl.java @@ -142,7 +142,7 @@ public RoutePagingResponse getRoute(User user, Long routeId) { .map(route -> RouteResponse.builder() .routeId(route.getRouteId()) .routeName(route.getRouteName()) - .publishRoute(route.getPublishRoute() == PublishStatus.PUBLIC) + .publishRoute(route.getPublishRoute() == PublishStatus.PUBLIC ? true : false) .numStore(routeListRepository.countRouteListByRoute(route)) .build()) .collect(Collectors.toList()); diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/controller/StoreController.java b/gusto/src/main/java/com/umc/gusto/domain/store/controller/StoreController.java index 4152617f..6b31de93 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/controller/StoreController.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/controller/StoreController.java @@ -29,7 +29,7 @@ public class StoreController { public ResponseEntity> getStores( @AuthenticationPrincipal AuthUser authUser, @RequestParam(name = "storeId") List storeIds) { - User user = authUser.getUser(); + User user = authUser != null ? authUser.getUser() : null; List getStores = storeService.getStores(user, storeIds); return ResponseEntity.status(HttpStatus.OK).body(getStores); } @@ -44,7 +44,7 @@ public ResponseEntity getStoreDetail( @PathVariable Long storeId, @RequestParam(name = "visitedAt", required = false) LocalDate visitedAt, @RequestParam(name = "reviewId", required = false) Long reviewId){ - User user = authUser.getUser(); + User user = authUser != null ? authUser.getUser() : null; // 상점 세부 정보 가져오기 GetStoreDetailResponse getStoreDetail = storeService.getStoreDetail(user, storeId, visitedAt, reviewId); return ResponseEntity.status(HttpStatus.OK).body(getStoreDetail); @@ -117,7 +117,7 @@ public ResponseEntity> getUnvisitedPinStoresByCategoryAndLoc * 맛집 검색 엔진 */ @GetMapping("/search") - public ResponseEntity> searchStore(@AuthenticationPrincipal AuthUser authUser,@RequestParam(name = "keyword") String keyword){ - return ResponseEntity.status(HttpStatus.OK).body(storeService.searchStore(authUser.getUser(),keyword)); + public ResponseEntity> searchStore(@RequestParam(name = "keyword") String keyword){ + return ResponseEntity.status(HttpStatus.OK).body(storeService.searchStore(keyword)); } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreService.java b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreService.java index 9ffa1cc2..18230f93 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreService.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreService.java @@ -16,5 +16,5 @@ public interface StoreService { List getPinStoresByCategoryAndLocation(User user, Long myCategoryId, String townName); Map getVisitedPinStores(User user, Long myCategoryId, String townName, Long lastStoreId, int size); Map getUnvisitedPinStores(User user, Long myCategoryId, String townName, Long lastStoreId, int size); - List searchStore(User user,String keyword); + List searchStore(String keyword); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java index 408be3dd..18e04d14 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/store/service/StoreServiceImpl.java @@ -38,7 +38,15 @@ public List getStores(User user, List storeIds) { for (Long storeId : storeIds) { Store store = storeRepository.findById(storeId) .orElseThrow(() -> new GeneralException(Code.STORE_NOT_FOUND)); - Long pinId = pinRepository.findByUserAndStoreStoreId(user, storeId); + + Long pinId = null; + boolean isPinned = false; + if (user != null) { + pinId = pinRepository.findByUserAndStoreStoreId(user, storeId); + isPinned = pinRepository.existsByUserAndStoreStoreId(user, storeId); + } + + List openingHoursList = openingHoursRepository.findByStoreStoreId(storeId); Map businessDays = new LinkedHashMap<>(); @@ -50,12 +58,11 @@ public List getStores(User user, List storeIds) { businessDays.put(openingHours.getBusinessDay(), timing); } - List top3Reviews = reviewRepository.findFirst3ByStoreOrderByLikedDesc(user,store); + List top3Reviews = reviewRepository.findFirst3ByStoreOrderByLikedDesc(store); List reviewImg = top3Reviews.stream() .map(review -> Optional.ofNullable(review.getImg1()).orElse("")) .collect(Collectors.toList()); - boolean isPinned = pinRepository.existsByUserAndStoreStoreId(user, storeId); responses.add(GetStoreResponse.builder() @@ -82,9 +89,15 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate // 가게별 기본 카테고리 값 // Category category = storeRepository.findCategoryByStoreId(storeId) // .orElseThrow(() -> new GeneralException(Code.CATEGORY_NOT_FOUND)); - Long pinId = pinRepository.findByUserAndStoreStoreId(user, storeId); - List top4Reviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store,user); + Long pinId = null; + boolean isPinned = false; + if (user != null) { + pinId = pinRepository.findByUserAndStoreStoreId(user, storeId); + isPinned = pinRepository.existsByUserAndStoreStoreId(user, storeId); + } + + List top4Reviews = reviewRepository.findFirst4ByStoreOrderByLikedDesc(store); List reviewImg = top4Reviews.stream() .map(review -> Optional.ofNullable(review.getImg1()).orElse("")) @@ -96,10 +109,10 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate if (reviewId != null && visitedAt != null) { pageSize = PAGE_SIZE; - reviews = reviewRepository.findReviewsAfterIdByStore(user,store, visitedAt, reviewId, Pageable.ofSize(pageSize)); + reviews = reviewRepository.findReviewsAfterIdByStore(store, visitedAt, reviewId, Pageable.ofSize(pageSize)); } else { pageSize = PAGE_SIZE_FIRST; - reviews = reviewRepository.findFirstReviewsByStore(user,store, Pageable.ofSize(pageSize)); + reviews = reviewRepository.findFirstReviewsByStore(store, Pageable.ofSize(pageSize)); } List getReviews = reviews.stream() @@ -120,8 +133,6 @@ public GetStoreDetailResponse getStoreDetail(User user, Long storeId, LocalDate }) .toList(); - boolean isPinned = pinRepository.existsByUserAndStoreStoreId(user, storeId); - return GetStoreDetailResponse.builder() .pinId(pinId) .storeId(storeId) @@ -194,7 +205,7 @@ public List getPinStoresByCategoryAndLocation(User user, Lo for (Pin pin : pins){ Store store = pin.getStore(); - Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(user,store); + Optional topReviewOptional = reviewRepository.findFirstByStoreOrderByLikedDesc(store); String reviewImg = topReviewOptional.map(Review::getImg1).orElse(""); boolean hasVisited = reviewRepository.existsByStoreAndUserNickname(store, user.getNickname()); @@ -253,7 +264,7 @@ public Map getPinStoresInfo(User user, Long myCategoryId, String boolean hasVisited = reviewRepository.existsByStoreAndUserNickname(store, user.getNickname()); if (hasVisited == visited) { - List top3Reviews = reviewRepository.findFirst3ByStoreOrderByLikedDesc(user,store); + List top3Reviews = reviewRepository.findFirst3ByStoreOrderByLikedDesc(store); List reviewImg = top3Reviews.stream() .map(review -> Optional.ofNullable(review.getImg1()).orElse("")) .collect(Collectors.toList()); @@ -289,12 +300,12 @@ public Map getUnvisitedPinStores(User user, Long myCategoryId, S } @Override - public List searchStore(User user,String keyword) { + public List searchStore(String keyword) { List searchResult = storeRepository.findTop5ByStoreNameContains(keyword); return searchResult.stream() .map(result -> { - Optional review = reviewRepository.findFirstByStoreOrderByLikedDesc(user,result); + Optional review = reviewRepository.findFirstByStoreOrderByLikedDesc(result); String reviewImg = review.map(Review::getImg1).orElse(""); return GetStoreInfoResponse.builder() .storeId(result.getStoreId()) diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/controller/UserController.java b/gusto/src/main/java/com/umc/gusto/domain/user/controller/UserController.java index 83385178..89ff1024 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/controller/UserController.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/controller/UserController.java @@ -17,6 +17,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.Map; + @RestController @RequiredArgsConstructor @RequestMapping("/users") @@ -271,4 +273,62 @@ public ResponseEntity followerList(@AuthenticationPrincipal Auth return ResponseEntity.ok() .body(pagingResponse); } + + /** + * 소셜 연동 해제 + * [DELETE] /users/auth/social-account + * @param - + * @return - + */ + @DeleteMapping("/auth/social-account") + public ResponseEntity disconnectSocialAccount(@AuthenticationPrincipal AuthUser authUser, + @RequestBody SignInRequest signInRequest) { + + userService.disconnectSocialAccount(authUser.getUser(), signInRequest); + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .build(); + } + + /** + * 소셜 연동 추가 + * [POST] /users/auth/social-account + * @param - + * @return - + */ + @PostMapping("/auth/social-account") + public ResponseEntity connectSocialAccount(@AuthenticationPrincipal AuthUser authUser, + @RequestBody SignInRequest signInRequest) { + userService.connectSocialAccount(authUser.getUser(), signInRequest); + + return ResponseEntity.status(HttpStatus.CREATED) + .build(); + } + + /** + * 연결된 소셜 계정 목록 + * [POST] /users/social-accounts + * @param - + * @return - + */ + @GetMapping("/social-accounts") + public ResponseEntity socialAccountList(@AuthenticationPrincipal AuthUser authUser) { + Map accountList = userService.getAccountList(authUser.getUser()); + + return ResponseEntity.ok() + .body(accountList); + } + + /** + * 회원 탈퇴 + * [DELETE] /users/my + * @param - + * @return - + */ + @DeleteMapping("/my") + public ResponseEntity disconnectSocialAccount(@AuthenticationPrincipal AuthUser authUser) { + userService.withdrawalUser(authUser.getUser()); + + return ResponseEntity.status(HttpStatus.NO_CONTENT) + .build(); + } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/entity/User.java b/gusto/src/main/java/com/umc/gusto/domain/user/entity/User.java index c89f4499..7fc4d933 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/entity/User.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/entity/User.java @@ -128,4 +128,8 @@ public void updateCountReview(boolean plus){ if(plus) this.reviewCnt++; else this.reviewCnt--; } + + public void updateMemberStatus(MemberStatus memberStatus) { + this.memberStatus = memberStatus; + } } diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/model/request/PublishingInfoRequest.java b/gusto/src/main/java/com/umc/gusto/domain/user/model/request/PublishingInfoRequest.java index 8c49bbee..912b945e 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/model/request/PublishingInfoRequest.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/model/request/PublishingInfoRequest.java @@ -1,5 +1,8 @@ package com.umc.gusto.domain.user.model.request; +import lombok.Getter; + +@Getter public class PublishingInfoRequest { private boolean publishReview; private boolean publishCategory; diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/repository/SocialRepository.java b/gusto/src/main/java/com/umc/gusto/domain/user/repository/SocialRepository.java index 89487102..f0a7c4cb 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/repository/SocialRepository.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/repository/SocialRepository.java @@ -1,10 +1,18 @@ package com.umc.gusto.domain.user.repository; import com.umc.gusto.domain.user.entity.Social; +import com.umc.gusto.domain.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface SocialRepository extends JpaRepository { Optional findBySocialTypeAndProviderId(Social.SocialType socialType, String providerId); + + Integer countSocialsByUser(User user); + + Boolean existsByUserAndSocialType(User user, Social.SocialType socialType); + + List findByUser(User user); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/service/UserService.java b/gusto/src/main/java/com/umc/gusto/domain/user/service/UserService.java index 65a91152..988ce44b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/service/UserService.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/service/UserService.java @@ -9,6 +9,8 @@ import com.umc.gusto.global.auth.model.Tokens; import org.springframework.web.multipart.MultipartFile; +import java.util.Map; + public interface UserService { // 회원 가입 @@ -56,4 +58,16 @@ public interface UserService { // 팔로워 목록 PagingResponse getFollwerList(User user, Long followId); + + // 소셜 연동 해제 + void disconnectSocialAccount(User user, SignInRequest signInRequest); + + // 소셜 연동 추가 + void connectSocialAccount(User user, SignInRequest signInRequest); + + // 연결된 소셜 목록 + Map getAccountList(User user); + + // 회원 탈퇴 + void withdrawalUser(User user); } diff --git a/gusto/src/main/java/com/umc/gusto/domain/user/service/UserServiceImpl.java b/gusto/src/main/java/com/umc/gusto/domain/user/service/UserServiceImpl.java index 9b922357..02c8d08b 100644 --- a/gusto/src/main/java/com/umc/gusto/domain/user/service/UserServiceImpl.java +++ b/gusto/src/main/java/com/umc/gusto/domain/user/service/UserServiceImpl.java @@ -33,8 +33,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -54,7 +53,7 @@ public class UserServiceImpl implements UserService{ private static final int MAX_NICKNAME_NUMBER = 999; private static final int MIN_NICKNAME_NUMBER = 1; private static final int FOLLOW_LIST_PAGE = 30; - + private static final Social.SocialType[] AUTH_SERVERS = Social.SocialType.values(); @Value("${default.img.url}") private String DEFAULT_PROFILE_IMG; @@ -62,7 +61,7 @@ public class UserServiceImpl implements UserService{ @Override @Transactional public Tokens createUser(MultipartFile multipartFile, SignUpRequest request) { - socialService.loadUserInfo(request.getProvider(), request.getProviderId(), request.getAccessToken()); + socialService.checkUserInfo(request.getProvider(), request.getProviderId(), request.getAccessToken()); // 이미 가입된 계정이 존재함 socialRepository.findBySocialTypeAndProviderId(Social.SocialType.valueOf(request.getProvider()), request.getProviderId()) @@ -157,7 +156,7 @@ public NicknameResponse generateRandomNickname() { @Override public Tokens signIn(SignInRequest signInRequest) { - socialService.loadUserInfo(signInRequest.getProvider(), signInRequest.getProviderId(), signInRequest.getAccessToken()); + socialService.checkUserInfo(signInRequest.getProvider(), signInRequest.getProviderId(), signInRequest.getAccessToken()); // social 정보 확인 Social social = socialRepository.findBySocialTypeAndProviderId(Social.SocialType.valueOf(signInRequest.getProvider()), signInRequest.getProviderId()) @@ -394,4 +393,75 @@ public PagingResponse getFollwerList(User user, Long followId) { .result(result) .build(); } + + @Override + @Transactional + public void disconnectSocialAccount(User user, SignInRequest signInRequest) { + socialService.checkUserInfo(signInRequest.getProvider(), signInRequest.getProviderId(), signInRequest.getAccessToken()); + + Social social = socialRepository.findBySocialTypeAndProviderId(Social.SocialType.valueOf(signInRequest.getProvider()), signInRequest.getProviderId()) + .orElseThrow(() -> new GeneralException(Code.SOCIAL_ACCOUNT_NOT_FOUND)); + + Integer num = socialRepository.countSocialsByUser(user); + if(num == 1) { + throw new GeneralException(Code.NEED_LEAST_ONE_SOCIAL_ACCOUNT); + } + + socialService.disconnectAccount(signInRequest.getProvider(), signInRequest.getAccessToken()); + socialRepository.delete(social); + } + + @Override + @Transactional + public void connectSocialAccount(User user, SignInRequest signInRequest) { + socialService.checkUserInfo(signInRequest.getProvider(), signInRequest.getProviderId(), signInRequest.getAccessToken()); + + if(socialRepository.existsByUserAndSocialType(user, Social.SocialType.valueOf(signInRequest.getProvider()))) { + throw new GeneralException(Code.ALREADY_EXIST_SOCIAL_CONNECT); + } + + Social social = Social.builder() + .user(user) + .socialType(Social.SocialType.valueOf(signInRequest.getProvider())) + .providerId(signInRequest.getProviderId()) + .build(); + + socialRepository.save(social); + } + + @Override + @Transactional(readOnly = true) + public Map getAccountList(User user) { + List socials = socialRepository.findByUser(user); + + Set servers = socials.stream() + .map(social -> String.valueOf(social.getSocialType())) + .collect(Collectors.toSet()); + + Map result = new HashMap<>(); + + for(int i = 0; i < AUTH_SERVERS.length; i++) { + if(servers.contains(AUTH_SERVERS[i].name())) { + result.put(AUTH_SERVERS[i].name(), true); + } else { + result.put(AUTH_SERVERS[i].name(), false); + } + } + + return result; + } + + @Override + @Transactional + public void withdrawalUser(User user) { + user.updateMemberStatus(User.MemberStatus.INACTIVE); + + List socials = socialRepository.findByUser(user); + + for(Social social : socials) { + socialRepository.delete(social); + } + + userRepository.save(user); + } } diff --git a/gusto/src/main/java/com/umc/gusto/global/auth/service/SocialService.java b/gusto/src/main/java/com/umc/gusto/global/auth/service/SocialService.java index f610c03e..fdf799eb 100644 --- a/gusto/src/main/java/com/umc/gusto/global/auth/service/SocialService.java +++ b/gusto/src/main/java/com/umc/gusto/global/auth/service/SocialService.java @@ -5,6 +5,7 @@ import com.umc.gusto.global.exception.GeneralException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; @@ -19,7 +20,12 @@ public class SocialService { private final RestClient restClient = RestClient.create(); private static final String AUTH_TYPE = "Bearer "; - public void loadUserInfo(String provider, String providerId, String accessToken) { + @Value("${spring.security.oauth2.client.registration.naver.client-id}") + private static String NAVER_CLIENT_ID; + @Value("${spring.security.oauth2.client.registration.naver.client-secret}") + private static String NAVER_CLIENT_SECRET; + + public void checkUserInfo(String provider, String providerId, String accessToken) { // TODO: ACCESS TOKEN 복호화 String header = AUTH_TYPE + accessToken; @@ -75,4 +81,61 @@ public void loadUserInfo(String provider, String providerId, String accessToken) throw new GeneralException(Code.UNMATCHED_AUTH_INFO); } } + + + public void disconnectAccount(String provider, String accessToken) { + // TODO: ACCESS TOKEN 복호화 + + if(provider.equals("NAVER")) { + StringBuilder uri = new StringBuilder("https://nid.naver.com/oauth2.0/token?grant_type=delete&"); + uri.append("client_id=").append(NAVER_CLIENT_ID).append("&") + .append("client_secret=").append(NAVER_CLIENT_SECRET).append("&") + .append("access_token=").append(accessToken); + + Map result = restClient.get() + .uri(uri.toString()) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_NOT_FOUND_TOKEN); + })) + .onStatus(HttpStatusCode::is5xxServerError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_FIND_ERROR); + })) + .body(Map.class); + + String resultCode = String.valueOf(result.get("result")); + + log.info(resultCode); + } else if(provider.equals("GOOGLE")) { + StringBuilder uri = new StringBuilder("https://oauth2.googleapis.com/revoke?"); + uri.append("access_token=").append(accessToken); + + Map result = restClient.get() + .uri(uri.toString()) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_NOT_FOUND_TOKEN); + })) + .onStatus(HttpStatusCode::is5xxServerError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_FIND_ERROR); + })) + .body(Map.class); + + log.info(result.toString()); + } else { + String header = AUTH_TYPE + accessToken; + + String result = restClient.get() + .uri("https://kapi.kakao.com/v1/user/unlink") + .header("Authorization", header) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_NOT_FOUND_TOKEN); + })) + .onStatus(HttpStatusCode::is5xxServerError, ((request, response) -> { + throw new GeneralException(Code.OAUTH_FIND_ERROR); + })) + .body(String.class); + } + } } diff --git a/gusto/src/main/java/com/umc/gusto/global/common/PublishStatus.java b/gusto/src/main/java/com/umc/gusto/global/common/PublishStatus.java index 6a4cf8e6..e99bedab 100644 --- a/gusto/src/main/java/com/umc/gusto/global/common/PublishStatus.java +++ b/gusto/src/main/java/com/umc/gusto/global/common/PublishStatus.java @@ -1,5 +1,19 @@ package com.umc.gusto.global.common; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public enum PublishStatus { - PUBLIC, PRIVATE + PUBLIC(true), + PRIVATE(false); + + private final boolean check; + + public boolean isCheck() { + return check; + } + + public static PublishStatus of(boolean check){ + return check ? PUBLIC : PRIVATE; + } } diff --git a/gusto/src/main/java/com/umc/gusto/global/exception/Code.java b/gusto/src/main/java/com/umc/gusto/global/exception/Code.java index 0ad9fc52..ad2b1139 100644 --- a/gusto/src/main/java/com/umc/gusto/global/exception/Code.java +++ b/gusto/src/main/java/com/umc/gusto/global/exception/Code.java @@ -78,6 +78,9 @@ public enum Code { // AUTH 관련 에러 +7 UNMATCHED_AUTH_INFO(HttpStatus.FORBIDDEN, 403701, "AccessToken과 providerId가 일치하지 않습니다."), + NEED_LEAST_ONE_SOCIAL_ACCOUNT(HttpStatus.FORBIDDEN, 403702, "최소 1개의 소셜 계정이 연동되어야 합니다."), + SOCIAL_ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, 404701, "해당 소셜 계정이 연결되어 있지 않습니다."), + ALREADY_EXIST_SOCIAL_CONNECT(HttpStatus.CONFLICT, 409701, "해당 소셜 서비스에 연결된 계정이 이미 존재합니다."), OAUTH_FIND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 500701, "소셜 인증 서버에서 에러가 발생했습니다."), OAUTH_NOT_FOUND_TOKEN(HttpStatus.NOT_FOUND, 404702, "유효한 소셜 서버 Access Token이 아닙니다."),