From 00e16da5fdc2da239aa7054dd8fccd0aa4241bf9 Mon Sep 17 00:00:00 2001 From: ChangyeolKim Date: Fri, 17 Jul 2026 00:13:23 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=9E=A5=EC=86=8C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EB=93=B1=EB=A1=9D/=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/controller/PlaceController.java | 28 +++++----- .../place/exception/PlaceErrorCode.java | 34 ++++++++++++ .../place/repository/PlaceLikeRepository.java | 15 +++++ .../domain/place/service/PlaceService.java | 13 ++++- .../place/service/PlaceServiceImpl.java | 55 +++++++++++++++++++ 5 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java create mode 100644 src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java diff --git a/src/main/java/com/yeogido/backend/domain/place/controller/PlaceController.java b/src/main/java/com/yeogido/backend/domain/place/controller/PlaceController.java index 95fd93b..f42b7a1 100644 --- a/src/main/java/com/yeogido/backend/domain/place/controller/PlaceController.java +++ b/src/main/java/com/yeogido/backend/domain/place/controller/PlaceController.java @@ -1,32 +1,34 @@ package com.yeogido.backend.domain.place.controller; -import com.yeogido.backend.domain.place.dto.response.PlaceResponse; -import com.yeogido.backend.global.common.response.ApiResponse; +import com.yeogido.backend.domain.place.service.PlaceService; import com.yeogido.backend.global.common.code.SuccessCode; +import com.yeogido.backend.global.common.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @Tag(name = "Place", description = "장소 관련 API") @RestController +@RequiredArgsConstructor @RequestMapping("/api/v1/places") public class PlaceController { + private final PlaceService placeService; + @Operation( summary = "장소 좋아요 등록", description = "사용자가 특정 장소에 좋아요를 등록합니다." ) @PostMapping("/{placeId}/likes") - public ApiResponse createPlaceLike( + public ApiResponse createPlaceLike( @Parameter(description = "장소 ID", example = "1") @PathVariable Long placeId ) { - PlaceResponse.LikeCreate response = PlaceResponse.LikeCreate.builder() - .placeId(placeId) - .isLiked(true) - .build(); - return ApiResponse.onSuccess(SuccessCode.CREATED, response); + placeService.createPlaceLike(placeId); + + return ApiResponse.onSuccess(SuccessCode.CREATED); } @Operation( @@ -34,14 +36,12 @@ public ApiResponse createPlaceLike( description = "사용자가 특정 장소에 등록한 좋아요를 취소합니다" ) @DeleteMapping("/{placeId}/likes") - public ApiResponse deletePlaceLike( + public ApiResponse deletePlaceLike( @Parameter(description = "장소 ID", example = "1") @PathVariable Long placeId ) { - PlaceResponse.LikeDelete response = PlaceResponse.LikeDelete.builder() - .placeId(placeId) - .isLiked(false) - .build(); - return ApiResponse.onSuccess(SuccessCode.NO_CONTENT, response); + placeService.deletePlaceLike(placeId); + + return ApiResponse.onSuccess(SuccessCode.OK); } } diff --git a/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java b/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java new file mode 100644 index 0000000..221a742 --- /dev/null +++ b/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java @@ -0,0 +1,34 @@ +package com.yeogido.backend.domain.place.exception; + +import com.yeogido.backend.global.exception.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor + +public enum PlaceErrorCode implements ErrorCode { + + PLACE_NOT_FOUND( + HttpStatus.NOT_FOUND, + "PLACE4041", + "장소를 찾을 수 없습니다" + ), + + PLACE_LIKE_NOT_FOUND( + HttpStatus.NOT_FOUND, + "PLACE4042", + "장소 좋아요 정보를 찾을 수 없습니다" + ), + + PLACE_LIKE_ALREADY_EXIST( + HttpStatus.CONFLICT, + "PLACE4091", + "이미 좋아요를 누른 장소입니다" + ); + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java b/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java new file mode 100644 index 0000000..8ea7ec1 --- /dev/null +++ b/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java @@ -0,0 +1,15 @@ +package com.yeogido.backend.domain.place.repository; + +import com.yeogido.backend.domain.place.entity.PlaceLike; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface PlaceLikeRepository extends JpaRepository { + + // 특정 사용자가 해당 장소에 이미 좋아요를 등록했는지 확인 + boolean existsByUserIdAndPlaceId(Long userId, Long placeId); + + // 특정 사용자가 장소에 등록한 좋아요 데이터 단건 조회 (좋아요 취소에 사용) + Optional findByUserIdAndPlaceId(Long userId, Long placeId); +} \ No newline at end of file diff --git a/src/main/java/com/yeogido/backend/domain/place/service/PlaceService.java b/src/main/java/com/yeogido/backend/domain/place/service/PlaceService.java index 4cbb96d..5c0937f 100644 --- a/src/main/java/com/yeogido/backend/domain/place/service/PlaceService.java +++ b/src/main/java/com/yeogido/backend/domain/place/service/PlaceService.java @@ -8,7 +8,16 @@ public interface PlaceService { - Map getPlaceMap(List items); + void createPlaceLike(Long placeId); - Place getOrCreatePlace(CourseReqDTO.CourseItemCreateReq item, Map placeMap); + void deletePlaceLike(Long placeId); + + Map getPlaceMap( + List items + ); + + Place getOrCreatePlace( + CourseReqDTO.CourseItemCreateReq item, + Map placeMap + ); } diff --git a/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java b/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java index b01cc3f..68f0274 100644 --- a/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java +++ b/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java @@ -4,11 +4,16 @@ import com.yeogido.backend.domain.course.dto.request.CourseReqDTO; import com.yeogido.backend.domain.course.enums.CourseItemType; import com.yeogido.backend.domain.place.entity.Place; +import com.yeogido.backend.domain.place.entity.PlaceLike; import com.yeogido.backend.domain.place.enums.PlaceSource; +import com.yeogido.backend.domain.place.exception.PlaceErrorCode; +import com.yeogido.backend.domain.place.repository.PlaceLikeRepository; import com.yeogido.backend.domain.place.repository.PlaceRepository; import com.yeogido.backend.domain.region.entity.Region; import com.yeogido.backend.domain.region.exception.RegionErrorCode; import com.yeogido.backend.domain.region.repository.RegionRepository; +import com.yeogido.backend.domain.user.entity.User; +import com.yeogido.backend.domain.user.repository.UserRepository; import com.yeogido.backend.global.exception.GeneralException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,9 +32,50 @@ @Transactional(readOnly = true) public class PlaceServiceImpl implements PlaceService { + // TODO: 인증 연동 후 현재 로그인 사용자 ID로 변경 + private static final Long MOCK_USER_ID = 1L; + private final PlaceRepository placeRepository; + private final PlaceLikeRepository placeLikeRepository; + private final UserRepository userRepository; private final RegionRepository regionRepository; + @Override + @Transactional() + public void createPlaceLike(Long placeId) { + Place place = getPlace(placeId); + User user = userRepository.getReferenceById(MOCK_USER_ID); + + if (placeLikeRepository.existsByUserIdAndPlaceId(user.getId(), placeId)) { + throw new GeneralException( + PlaceErrorCode.PLACE_LIKE_ALREADY_EXIST + ); + } + + PlaceLike placeLike = PlaceLike.builder() + .place(place) + .user(user) + .build(); + + placeLikeRepository.save(placeLike); + } + + @Override + @Transactional() + public void deletePlaceLike(Long placeId) { + getPlace(placeId); + User user = userRepository.getReferenceById(MOCK_USER_ID); + + PlaceLike placeLike = placeLikeRepository.findByUserIdAndPlaceId( + user.getId(), + placeId + ).orElseThrow(() -> new GeneralException( + PlaceErrorCode.PLACE_LIKE_NOT_FOUND + )); + + placeLikeRepository.delete(placeLike); + } + @Override public Map getPlaceMap(List items) { Set externalPlaceIds = items.stream() @@ -82,4 +128,13 @@ private Region findRegionByAddress(String roadAddress, String lotAddress) { return regionRepository.findByParentAndName(region, subRegionName) .orElseThrow(() -> new GeneralException(RegionErrorCode.REGION_NOT_FOUND)); } + + private Place getPlace(Long placeId) { + return placeRepository.findById(placeId) + .orElseThrow( + () -> new GeneralException( + PlaceErrorCode.PLACE_NOT_FOUND + ) + ); + } } From 9cd7a30e5cb146d18d435bf5ab44d6dc11483056 Mon Sep 17 00:00:00 2001 From: ChangyeolKim Date: Sun, 19 Jul 2026 14:31:35 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EC=9E=A5=EC=86=8C=20=EC=A2=8B?= =?UTF-8?q?=EC=95=84=EC=9A=94=20=EB=93=B1=EB=A1=9D/=EC=B7=A8=EC=86=8C=20?= =?UTF-8?q?=EB=A9=B1=EB=93=B1=20=EC=B2=98=EB=A6=AC=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20#64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/exception/PlaceErrorCode.java | 12 ---------- .../place/repository/PlaceLikeRepository.java | 4 ++-- .../place/service/PlaceServiceImpl.java | 24 +++++++------------ 3 files changed, 11 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java b/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java index 221a742..ae6c46d 100644 --- a/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java +++ b/src/main/java/com/yeogido/backend/domain/place/exception/PlaceErrorCode.java @@ -14,18 +14,6 @@ public enum PlaceErrorCode implements ErrorCode { HttpStatus.NOT_FOUND, "PLACE4041", "장소를 찾을 수 없습니다" - ), - - PLACE_LIKE_NOT_FOUND( - HttpStatus.NOT_FOUND, - "PLACE4042", - "장소 좋아요 정보를 찾을 수 없습니다" - ), - - PLACE_LIKE_ALREADY_EXIST( - HttpStatus.CONFLICT, - "PLACE4091", - "이미 좋아요를 누른 장소입니다" ); private final HttpStatus httpStatus; diff --git a/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java b/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java index 8ea7ec1..135e15c 100644 --- a/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java +++ b/src/main/java/com/yeogido/backend/domain/place/repository/PlaceLikeRepository.java @@ -7,9 +7,9 @@ public interface PlaceLikeRepository extends JpaRepository { - // 특정 사용자가 해당 장소에 이미 좋아요를 등록했는지 확인 + // 멱등한 좋아요 등록을 위해 기존 좋아요 존재 여부 확인 boolean existsByUserIdAndPlaceId(Long userId, Long placeId); - // 특정 사용자가 장소에 등록한 좋아요 데이터 단건 조회 (좋아요 취소에 사용) + // 좋아요가 존재하는 경우에만 삭제하기 위한 단건 조회 Optional findByUserIdAndPlaceId(Long userId, Long placeId); } \ No newline at end of file diff --git a/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java b/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java index 68f0274..6de0d68 100644 --- a/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java +++ b/src/main/java/com/yeogido/backend/domain/place/service/PlaceServiceImpl.java @@ -41,17 +41,16 @@ public class PlaceServiceImpl implements PlaceService { private final RegionRepository regionRepository; @Override - @Transactional() + @Transactional public void createPlaceLike(Long placeId) { Place place = getPlace(placeId); - User user = userRepository.getReferenceById(MOCK_USER_ID); - if (placeLikeRepository.existsByUserIdAndPlaceId(user.getId(), placeId)) { - throw new GeneralException( - PlaceErrorCode.PLACE_LIKE_ALREADY_EXIST - ); + if (placeLikeRepository.existsByUserIdAndPlaceId(MOCK_USER_ID, placeId)) { + return; } + User user = userRepository.getReferenceById(MOCK_USER_ID); + PlaceLike placeLike = PlaceLike.builder() .place(place) .user(user) @@ -61,19 +60,14 @@ public void createPlaceLike(Long placeId) { } @Override - @Transactional() + @Transactional public void deletePlaceLike(Long placeId) { getPlace(placeId); - User user = userRepository.getReferenceById(MOCK_USER_ID); - PlaceLike placeLike = placeLikeRepository.findByUserIdAndPlaceId( - user.getId(), + placeLikeRepository.findByUserIdAndPlaceId( + MOCK_USER_ID, placeId - ).orElseThrow(() -> new GeneralException( - PlaceErrorCode.PLACE_LIKE_NOT_FOUND - )); - - placeLikeRepository.delete(placeLike); + ).ifPresent(placeLikeRepository::delete); } @Override