diff --git a/src/main/java/com/wayble/server/review/controller/ReviewController.java b/src/main/java/com/wayble/server/review/controller/ReviewController.java index 2fde91d2..5c460247 100644 --- a/src/main/java/com/wayble/server/review/controller/ReviewController.java +++ b/src/main/java/com/wayble/server/review/controller/ReviewController.java @@ -10,8 +10,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; -import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @@ -37,12 +38,10 @@ public class ReviewController { }) public CommonResponse registerReview( @PathVariable Long waybleZoneId, - @RequestBody @Valid ReviewRegisterDto dto, - - // TODO: 로그인 구현 후 Authorization 헤더 필수로 변경 필요 - @RequestHeader(value = "Authorization", required = false) String authorizationHeader + @RequestBody @Valid ReviewRegisterDto dto ) { - reviewService.registerReview(waybleZoneId, dto, authorizationHeader); + Long userId = extractUserId(); // 토큰에서 유저 ID 추출 + reviewService.registerReview(waybleZoneId, userId, dto); return CommonResponse.success("리뷰가 등록되었습니다."); } @@ -58,4 +57,26 @@ public CommonResponse> getReviews( ) { return CommonResponse.success(reviewService.getReviews(waybleZoneId, sort)); } + + private Long extractUserId() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { throw new IllegalStateException("인증 정보가 없습니다."); } + + Object p = auth.getPrincipal(); + if (p instanceof Long l) { return l; } + if (p instanceof Integer i) { return i.longValue(); } + if (p instanceof String s) { + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new IllegalStateException("principal에서 userId 파싱 실패"); + } + } + try { + return Long.parseLong(auth.getName()); + } + catch (Exception e) { + throw new IllegalStateException("인증 정보에서 userId를 추출할 수 없습니다."); + } + } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java b/src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java index 001c925d..1ef72082 100644 --- a/src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java +++ b/src/main/java/com/wayble/server/review/dto/ReviewRegisterDto.java @@ -7,11 +7,6 @@ @Schema(description = "리뷰 등록 요청 DTO") public record ReviewRegisterDto( - - @Schema(description = "작성자 ID", example = "1") - @NotNull(message = "작성자 ID는 필수입니다.") - Long userId, - @Schema(description = "리뷰 내용", example = "뷰가 좋고 접근성이 좋은 카페예요.") @NotBlank(message = "리뷰 내용은 비어 있을 수 없습니다.") String content, diff --git a/src/main/java/com/wayble/server/review/service/ReviewService.java b/src/main/java/com/wayble/server/review/service/ReviewService.java index 2e6c501d..faec11e8 100644 --- a/src/main/java/com/wayble/server/review/service/ReviewService.java +++ b/src/main/java/com/wayble/server/review/service/ReviewService.java @@ -30,11 +30,11 @@ public class ReviewService { private final UserRepository userRepository; @Transactional - public void registerReview(Long zoneId, ReviewRegisterDto dto, String token) { + public void registerReview(Long zoneId, Long userId, ReviewRegisterDto dto) { WaybleZone zone = waybleZoneRepository.findById(zoneId) .orElseThrow(() -> new ApplicationException(WaybleZoneErrorCase.WAYBLE_ZONE_NOT_FOUND)); - User user = userRepository.findById(dto.userId()) + User user = userRepository.findById(userId) .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); Review review = Review.of(user, zone, dto.content(), dto.rating()); @@ -44,14 +44,13 @@ public void registerReview(Long zoneId, ReviewRegisterDto dto, String token) { zone.updateRating(newRating); zone.addReviewCount(1); - if (dto.images() != null) { + if (dto.images() != null && !dto.images().isEmpty()) { for (String imageUrl : dto.images()) { reviewImageRepository.save(ReviewImage.of(review, imageUrl)); } } - waybleZoneRepository.save(zone); - // visitDate 및 facilities 저장은 필요시 추가 구현 + waybleZoneRepository.save(zone); } @Transactional(readOnly = true) diff --git a/src/main/java/com/wayble/server/user/controller/UserPlaceController.java b/src/main/java/com/wayble/server/user/controller/UserPlaceController.java index d17a09ea..ea1e6a64 100644 --- a/src/main/java/com/wayble/server/user/controller/UserPlaceController.java +++ b/src/main/java/com/wayble/server/user/controller/UserPlaceController.java @@ -1,16 +1,18 @@ package com.wayble.server.user.controller; -import com.wayble.server.common.exception.ApplicationException; + import com.wayble.server.common.response.CommonResponse; -import com.wayble.server.user.dto.UserPlaceListResponseDto; +import com.wayble.server.user.dto.UserPlaceRemoveRequestDto; import com.wayble.server.user.dto.UserPlaceRequestDto; -import com.wayble.server.user.exception.UserErrorCase; +import com.wayble.server.user.dto.UserPlaceSummaryDto; import com.wayble.server.user.service.UserPlaceService; +import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @@ -41,19 +43,80 @@ public CommonResponse saveUserPlace( } @GetMapping + @Operation(summary = "내 장소 리스트 요약 조회", description = "장소 관련 목록(리스트)만 반환합니다(개수 포함).") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "유저를 찾을 수 없음"), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + }) + public CommonResponse> getMyPlaceSummaries( + @RequestParam(name = "sort", defaultValue = "latest") String sort + ) { + Long userId = extractUserId(); + List summaries = userPlaceService.getMyPlaceSummaries(userId, sort); + return CommonResponse.success(summaries); + } + + + @GetMapping("/zones") + @Operation(summary = "특정 장소 내 웨이블존 목록 조회(페이징)", + description = "placeId로 해당 장소 내부의 웨이블존 카드 목록을 반환합니다. (page는 1부터 시작.)") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "유저/장소를 찾을 수 없음"), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + }) + public CommonResponse> getZonesInPlace( + @RequestParam Long placeId, + @RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "20") Integer size + ) { + Long userId = extractUserId(); + Page zones = userPlaceService.getZonesInPlace(userId, placeId, page, size); + return CommonResponse.success(zones); + } + + @DeleteMapping @Operation( - summary = "내가 저장한 장소 목록 조회", - description = "유저가 저장한 모든 장소 및 해당 웨이블존 정보를 조회합니다." + summary = "장소에서 웨이블존 제거", + description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." ) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "장소 목록 조회 성공"), - @ApiResponse(responseCode = "404", description = "해당 유저를 찾을 수 없음"), + @ApiResponse(responseCode = "200", description = "제거 성공"), + @ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"), @ApiResponse(responseCode = "403", description = "권한이 없습니다.") }) - public CommonResponse> getUserPlaces( - ) { - Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - List places = userPlaceService.getUserPlaces(userId); - return CommonResponse.success(places); + public CommonResponse removeZoneFromPlace(@RequestBody @Valid UserPlaceRemoveRequestDto request) { + Long userId = extractUserId(); + userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); + return CommonResponse.success("제거되었습니다."); + } + + + // SecurityContext에서 userId 추출하는 로직 + private Long extractUserId() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) { + throw new IllegalStateException("인증 정보가 없습니다."); + } + + Object p = auth.getPrincipal(); + + if (p instanceof Long l) { return l; } + if (p instanceof Integer i) { return i.longValue(); } + if (p instanceof String s) { + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + // 숫자 변환 실패 시 출력 + System.err.println("Principal 문자열을 Long으로 변환할 수 없습니다: " + s); + } + } + + try { + return Long.parseLong(auth.getName()); + } catch (Exception e) { + throw new IllegalStateException("인증 정보에서 userId를 추출할 수 없습니다. Principal=" + p, e); + } } } diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java new file mode 100644 index 00000000..754a95a9 --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceRemoveRequestDto.java @@ -0,0 +1,8 @@ +package com.wayble.server.user.dto; + +import jakarta.validation.constraints.NotNull; + +public record UserPlaceRemoveRequestDto( + @NotNull Long placeId, + @NotNull Long waybleZoneId +) {} diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java index 6643f396..a265e39f 100644 --- a/src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceRequestDto.java @@ -4,5 +4,6 @@ public record UserPlaceRequestDto( @NotNull Long waybleZoneId, - @NotNull String title + @NotNull String title, + String color ) {} diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java new file mode 100644 index 00000000..32df29cf --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceSummaryDto.java @@ -0,0 +1,11 @@ +package com.wayble.server.user.dto; + +import lombok.Builder; + +@Builder +public record UserPlaceSummaryDto( + Long placeId, + String title, + String color, + int savedCount +) {} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java new file mode 100644 index 00000000..62fcf761 --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceZonesResponseDto.java @@ -0,0 +1,15 @@ +package com.wayble.server.user.dto; + +import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; +import lombok.Builder; + +import java.util.List; + +@Builder +public record UserPlaceZonesResponseDto( + Long placeId, + String title, + String color, + int savedCount, + List zones +) {} \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/entity/UserPlace.java b/src/main/java/com/wayble/server/user/entity/UserPlace.java index d174c59d..19baa852 100644 --- a/src/main/java/com/wayble/server/user/entity/UserPlace.java +++ b/src/main/java/com/wayble/server/user/entity/UserPlace.java @@ -6,6 +6,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; @Entity @@ -13,6 +15,8 @@ @Builder @AllArgsConstructor @NoArgsConstructor +@SQLDelete(sql = "UPDATE user_place SET deleted_at = NOW() WHERE id = ?") +@SQLRestriction("deleted_at IS NULL") @Table(name = "user_place") // 유져가 저장한 장소 public class UserPlace extends BaseEntity { @@ -23,7 +27,21 @@ public class UserPlace extends BaseEntity { @Column(name = "title", nullable = false) private String title; + @Column(length = 20) @Builder.Default + private String color = "GRAY"; // 배지/아이콘 색 (정확히 무슨 색이 있는지 몰라서 일단 자유 문자열 + 기본: 회색) + + @Column(name = "saved_count", nullable = false, columnDefinition = "int default 0") + @Builder.Default + private int savedCount = 0; // 리스트에 담긴 웨이블존 수 + + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) private User user; + + public void increaseCount() { this.savedCount++; } + public void decreaseCount() { if (this.savedCount > 0) this.savedCount--; } + + public void updateTitle(String title) { this.title = title; } + public void updateColor(String color) { this.color = color; } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java index 762393b9..031808bb 100644 --- a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java +++ b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java @@ -20,7 +20,9 @@ public enum UserErrorCase implements ErrorCase { INVALID_BIRTH_DATE(400, 1010, "생년월일 형식이 올바르지 않습니다."), USER_INFO_NOT_EXISTS(404,1011, "유저 정보가 존재하지 않습니다."), NICKNAME_REQUIRED(400, 1012,"nickname 파라미터는 필수입니다."), - NICKNAME_DUPLICATED(409,1013, "이미 사용 중인 닉네임입니다."); + NICKNAME_DUPLICATED(409,1013, "이미 사용 중인 닉네임입니다."), + PLACE_NOT_FOUND(404, 1014, "저장된 장소를 찾을 수 없습니다."), + PLACE_MAPPING_NOT_FOUND(404, 1015, "해당 장소에 해당 웨이블존이 없습니다."); private final Integer httpStatusCode; private final Integer errorCode; diff --git a/src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java b/src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java index 8c14f4be..50b51e76 100644 --- a/src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserPlaceRepository.java @@ -3,8 +3,12 @@ import com.wayble.server.user.entity.UserPlace; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface UserPlaceRepository extends JpaRepository { Optional findByUser_IdAndTitle(Long userId, String title); + List findAllByUser_IdOrderByCreatedAtDesc(Long userId); // 사용자 리스트 전체(최신순) + Optional findByIdAndUser_Id(Long placeId, Long userId); + List findAllByUser_IdOrderByTitleAsc(Long userId); } diff --git a/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java b/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java index bf217c1e..ebfa40e1 100644 --- a/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java +++ b/src/main/java/com/wayble/server/user/repository/UserPlaceWaybleZoneMappingRepository.java @@ -1,8 +1,13 @@ package com.wayble.server.user.repository; import com.wayble.server.user.entity.UserPlaceWaybleZoneMapping; +import com.wayble.server.wayblezone.entity.WaybleZone; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; @@ -11,4 +16,28 @@ public interface UserPlaceWaybleZoneMappingRepository extends JpaRepository findAllByUserPlace_User_Id(Long userId); + boolean existsByUserPlace_IdAndWaybleZone_Id(Long placeId, Long zoneId); + void deleteByUserPlace_IdAndWaybleZone_Id(Long placeId, Long zoneId); + + // 리스트 내부 웨이블존 조회 (페이징 포함) + @Query( + value = """ + select m.waybleZone + from UserPlaceWaybleZoneMapping m + where m.userPlace.id = :placeId + order by m.id desc + """, + countQuery = """ + select count(m) + from UserPlaceWaybleZoneMapping m + where m.userPlace.id = :placeId + """ + ) + Page findZonesByPlaceId(@Param("placeId") Long placeId, Pageable pageable); + + + + + + } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/user/service/UserPlaceService.java b/src/main/java/com/wayble/server/user/service/UserPlaceService.java index 8200715e..194f8525 100644 --- a/src/main/java/com/wayble/server/user/service/UserPlaceService.java +++ b/src/main/java/com/wayble/server/user/service/UserPlaceService.java @@ -2,8 +2,8 @@ import com.wayble.server.common.exception.ApplicationException; -import com.wayble.server.user.dto.UserPlaceListResponseDto; import com.wayble.server.user.dto.UserPlaceRequestDto; +import com.wayble.server.user.dto.UserPlaceSummaryDto; import com.wayble.server.user.entity.User; import com.wayble.server.user.entity.UserPlace; import com.wayble.server.user.entity.UserPlaceWaybleZoneMapping; @@ -11,9 +11,14 @@ import com.wayble.server.user.repository.UserPlaceRepository; import com.wayble.server.user.repository.UserPlaceWaybleZoneMappingRepository; import com.wayble.server.user.repository.UserRepository; +import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; import com.wayble.server.wayblezone.entity.WaybleZone; import com.wayble.server.wayblezone.repository.WaybleZoneRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -39,58 +44,94 @@ public void saveUserPlace(Long userId, UserPlaceRequestDto request) { .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); // 중복 저장 확인 - boolean alreadySaved = mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, request.waybleZoneId()); - if (alreadySaved) { + boolean duplicated = mappingRepository.existsByUserPlace_User_IdAndWaybleZone_Id(userId, request.waybleZoneId()); + if (duplicated) { throw new ApplicationException(UserErrorCase.PLACE_ALREADY_SAVED); } + String color = (request.color() == null || request.color().isBlank()) ? "GRAY" : request.color(); + UserPlace userPlace = userPlaceRepository.findByUser_IdAndTitle(userId, request.title()) + .orElseGet(() -> userPlaceRepository.save( + UserPlace.builder() + .title(request.title()) + .color(color) + .user(user) + .build() + )); + + mappingRepository.save(UserPlaceWaybleZoneMapping.builder() + .userPlace(userPlace) + .waybleZone(waybleZone) + .build()); + + userPlace.increaseCount(); + userPlaceRepository.save(userPlace); + waybleZone.addLikes(1); + waybleZoneRepository.save(waybleZone); + } - // 저장 - UserPlace userPlace = userPlaceRepository.save( - UserPlace.builder() - .title(request.title()) - .user(user) - .build() - ); + @Transactional(readOnly = true) + public List getMyPlaceSummaries(Long userId, String sort) { + userRepository.findById(userId) + .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); + + List places = "name".equalsIgnoreCase(sort) || "title".equalsIgnoreCase(sort) + ? userPlaceRepository.findAllByUser_IdOrderByTitleAsc(userId) + : userPlaceRepository.findAllByUser_IdOrderByCreatedAtDesc(userId); + + return places.stream() + .map(p -> UserPlaceSummaryDto.builder() + .placeId(p.getId()) + .title(p.getTitle()) + .color(p.getColor()) + .savedCount(p.getSavedCount()) + .build()) + .toList(); + } - mappingRepository.save( - UserPlaceWaybleZoneMapping.builder() - .userPlace(userPlace) - .waybleZone(waybleZone) + @Transactional(readOnly = true) + public Page getZonesInPlace(Long userId, Long placeId, int page, int size) { + UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) + .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); + + int zeroBased = Math.max(0, page - 1); + + Pageable pageable = PageRequest.of(zeroBased, size, Sort.by(Sort.Direction.DESC, "id")); + Page zones = mappingRepository.findZonesByPlaceId(place.getId(), pageable); + + return zones.map(z -> + WaybleZoneListResponseDto.builder() + .waybleZoneId(z.getId()) + .name(z.getZoneName()) + .category(z.getZoneType().toString()) + .address(z.getAddress().toFullAddress()) + .rating(z.getRating()) + .reviewCount(z.getReviewCount()) + .imageUrl(z.getMainImageUrl()) + .contactNumber(z.getContactNumber()) + .facilities(null) .build() ); } - @Transactional(readOnly = true) - public List getUserPlaces(Long userId) { - // 유저 존재 여부 확인 - User user = userRepository.findById(userId) - .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); + @Transactional + public void removeZoneFromPlace(Long userId, Long placeId, Long waybleZoneId) { + UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) + .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); + + if (!mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId)) { + throw new ApplicationException(UserErrorCase.PLACE_MAPPING_NOT_FOUND); + } + + mappingRepository.deleteByUserPlace_IdAndWaybleZone_Id(placeId, waybleZoneId); + + place.decreaseCount(); + userPlaceRepository.save(place); - List mappings = mappingRepository.findAllByUserPlace_User_Id(userId); - - return mappings.stream().map(mapping -> { - UserPlace userPlace = mapping.getUserPlace(); - WaybleZone waybleZone = mapping.getWaybleZone(); - - // 웨이블존 대표 이미지 가져오기 - String imageUrl = waybleZone.getMainImageUrl(); - - return UserPlaceListResponseDto.builder() - .placeId(userPlace.getId()) - .title(userPlace.getTitle()) - .waybleZone( - UserPlaceListResponseDto.WaybleZoneDto.builder() - .waybleZoneId(waybleZone.getId()) - .name(waybleZone.getZoneName()) - .category(waybleZone.getZoneType().toString()) - .rating(waybleZone.getRating()) - .address(waybleZone.getAddress().toFullAddress()) - .imageUrl(imageUrl) - .build() - ) - .build(); - }).toList(); + waybleZoneRepository.findById(waybleZoneId).ifPresent(z -> { + z.addLikes(-1); + waybleZoneRepository.save(z); + }); } } \ No newline at end of file diff --git a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java index aeefbf45..203a6eda 100644 --- a/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java +++ b/src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java @@ -100,6 +100,7 @@ public void addReviewCount(long count) { public void addLikes(long count) { this.likes += count; + if (this.likes < 0) this.likes = 0; this.markAsModified(); // 변경 시 자동으로 수정 시간 갱신 }