From 8503d61dc04fe81cf3d2b935d9144dd076d26f3c Mon Sep 17 00:00:00 2001 From: seungin Date: Tue, 19 Aug 2025 11:38:39 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[refactor]=20Auth=20errorCode=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wayble/server/auth/exception/AuthErrorCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java b/src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java index ae589b4..ad552a3 100644 --- a/src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java +++ b/src/main/java/com/wayble/server/auth/exception/AuthErrorCase.java @@ -7,7 +7,7 @@ @Getter @RequiredArgsConstructor public enum AuthErrorCase implements ErrorCase { - UNAUTHORIZED(401, 1001, "인증 정보가 없거나 userId를 추출할 수 없습니다."); + UNAUTHORIZED(401, 7001, "인증 정보가 없거나 userId를 추출할 수 없습니다."); private final Integer httpStatusCode; private final Integer errorCode; From 3e37fa96765b37cb00c3783a5045aa3d65889757 Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 01:53:52 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[feat]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A0=80=EC=9E=A5=ED=95=A0=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wayble/server/user/dto/UserPlaceCreateRequestDto.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java new file mode 100644 index 0000000..816c20d --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceCreateRequestDto.java @@ -0,0 +1,8 @@ +package com.wayble.server.user.dto; + +import jakarta.validation.constraints.NotBlank; + +public record UserPlaceCreateRequestDto( + @NotBlank(message = "제목은 필수입니다.") String title, + String color +) {} From 6bcd32bac6af6330a1eec86ab73f11235d18e8f7 Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 01:54:50 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[feat]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A0=80=EC=9E=A5=ED=95=A0=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EA=B4=80=EB=A0=A8=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20Dto=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/user/dto/UserPlaceCreateResponseDto.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java new file mode 100644 index 0000000..a8be930 --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceCreateResponseDto.java @@ -0,0 +1,11 @@ +package com.wayble.server.user.dto; + +import lombok.Builder; + +@Builder +public record UserPlaceCreateResponseDto( + Long placeId, + String title, + String color, + String message +) {} From a9b7c9a7d50cff004b946d7463dd7c3c3f553463 Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 01:56:03 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[feat]=20=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EC=9B=A8=EC=9D=B4=EB=B8=94=EC=A1=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20DTO=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/user/dto/UserPlaceAddZonesRequestDto.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java new file mode 100644 index 0000000..34d62e3 --- /dev/null +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java @@ -0,0 +1,10 @@ +package com.wayble.server.user.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record UserPlaceAddZonesRequestDto( + @NotNull Long placeId, + @NotEmpty List waybleZoneId +) {} From aec33b8be8e34d7c2bdea20ec4b62d278f012661 Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 01:57:10 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[refactor]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=B6=94=EA=B0=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=97=90=EB=9F=AC=20=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wayble/server/user/exception/UserErrorCase.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 031808b..1d28320 100644 --- a/src/main/java/com/wayble/server/user/exception/UserErrorCase.java +++ b/src/main/java/com/wayble/server/user/exception/UserErrorCase.java @@ -22,7 +22,8 @@ public enum UserErrorCase implements ErrorCase { NICKNAME_REQUIRED(400, 1012,"nickname 파라미터는 필수입니다."), NICKNAME_DUPLICATED(409,1013, "이미 사용 중인 닉네임입니다."), PLACE_NOT_FOUND(404, 1014, "저장된 장소를 찾을 수 없습니다."), - PLACE_MAPPING_NOT_FOUND(404, 1015, "해당 장소에 해당 웨이블존이 없습니다."); + PLACE_MAPPING_NOT_FOUND(404, 1015, "해당 장소에 해당 웨이블존이 없습니다."), + PLACE_TITLE_DUPLICATED(400, 1016, "동일한 이름의 리스트가 이미 있습니다."); private final Integer httpStatusCode; private final Integer errorCode; From c288b5bddb590d5540d5d2784f84becee1692737 Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 02:09:40 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[refactor]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A0=80=EC=9E=A5=20=EA=B4=80=EB=A0=A8=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/user/service/UserPlaceService.java | 75 ++++++++++++------- 1 file changed, 46 insertions(+), 29 deletions(-) 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 194f852..c5292a8 100644 --- a/src/main/java/com/wayble/server/user/service/UserPlaceService.java +++ b/src/main/java/com/wayble/server/user/service/UserPlaceService.java @@ -2,6 +2,7 @@ import com.wayble.server.common.exception.ApplicationException; +import com.wayble.server.user.dto.UserPlaceCreateRequestDto; import com.wayble.server.user.dto.UserPlaceRequestDto; import com.wayble.server.user.dto.UserPlaceSummaryDto; import com.wayble.server.user.entity.User; @@ -22,7 +23,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; @Service @RequiredArgsConstructor @@ -34,43 +37,57 @@ public class UserPlaceService { private final UserPlaceWaybleZoneMappingRepository mappingRepository; @Transactional - public void saveUserPlace(Long userId, UserPlaceRequestDto request) { - // 유저 존재 확인 + public Long createPlaceList(Long userId, UserPlaceCreateRequestDto request) { User user = userRepository.findById(userId) .orElseThrow(() -> new ApplicationException(UserErrorCase.USER_NOT_FOUND)); - // 웨이블존 존재 확인 - WaybleZone waybleZone = waybleZoneRepository.findById(request.waybleZoneId()) - .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); + userPlaceRepository.findByUser_IdAndTitle(userId, request.title()) + .ifPresent(p -> { throw new ApplicationException(UserErrorCase.PLACE_TITLE_DUPLICATED); }); - // 중복 저장 확인 - 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 saved = userPlaceRepository.save( + UserPlace.builder() + .title(request.title()) + .color(color) + .user(user) + .savedCount(0) + .build() + ); + return saved.getId(); + } + + @Transactional + public void addZonesToPlace(Long userId, Long placeId, List waybleZoneIds) { + UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId) + .orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND)); + + Set uniqueIds = new LinkedHashSet<>(waybleZoneIds); + + int added = 0; + for (Long zoneId : uniqueIds) { + WaybleZone zone = waybleZoneRepository.findById(zoneId) + .orElseThrow(() -> new ApplicationException(UserErrorCase.WAYBLE_ZONE_NOT_FOUND)); + + boolean exists = mappingRepository.existsByUserPlace_IdAndWaybleZone_Id(placeId, zoneId); + if (exists) continue; + + mappingRepository.save(UserPlaceWaybleZoneMapping.builder() + .userPlace(place) + .waybleZone(zone) + .build()); + + place.increaseCount(); + zone.addLikes(1); + waybleZoneRepository.save(zone); + + added++; } - 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); + if (added > 0) userPlaceRepository.save(place); } + @Transactional(readOnly = true) public List getMyPlaceSummaries(Long userId, String sort) { userRepository.findById(userId) From 2fbb794a49f179c1f05e57f7ef517d20fccb718d Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 02:10:16 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[refactor]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A0=80=EC=9E=A5=20=EA=B4=80=EB=A0=A8=20Dto=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java b/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java index 34d62e3..bd1524c 100644 --- a/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java +++ b/src/main/java/com/wayble/server/user/dto/UserPlaceAddZonesRequestDto.java @@ -6,5 +6,5 @@ public record UserPlaceAddZonesRequestDto( @NotNull Long placeId, - @NotEmpty List waybleZoneId + @NotEmpty List waybleZoneIds ) {} From 9700c53d87db110aeaafaa8d619e9ada40d172ab Mon Sep 17 00:00:00 2001 From: seungin Date: Wed, 20 Aug 2025 02:15:05 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[fix]=20=EC=9B=A8=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=A1=B4=20=EC=A0=80=EC=9E=A5=20=EA=B4=80=EB=A0=A8=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=A1=9C=EC=A7=81=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserPlaceController.java | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) 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 8401614..f949312 100644 --- a/src/main/java/com/wayble/server/user/controller/UserPlaceController.java +++ b/src/main/java/com/wayble/server/user/controller/UserPlaceController.java @@ -3,9 +3,7 @@ import com.wayble.server.auth.resolver.CurrentUser; import com.wayble.server.common.response.CommonResponse; -import com.wayble.server.user.dto.UserPlaceRemoveRequestDto; -import com.wayble.server.user.dto.UserPlaceRequestDto; -import com.wayble.server.user.dto.UserPlaceSummaryDto; +import com.wayble.server.user.dto.*; import com.wayble.server.user.service.UserPlaceService; import com.wayble.server.wayblezone.dto.WaybleZoneListResponseDto; import io.swagger.v3.oas.annotations.Operation; @@ -27,19 +25,25 @@ public class UserPlaceController { private final UserPlaceService userPlaceService; @PostMapping - @Operation(summary = "유저 장소 저장", description = "유저가 웨이블존을 장소로 저장합니다.") + @Operation(summary = "웨이블존 저장할 리스트 생성", + description = "제목과 색상을 받아 웨이블존을 저장할 리스트를 생성합니다.") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "장소 저장 성공"), - @ApiResponse(responseCode = "400", description = "이미 저장한 장소입니다."), - @ApiResponse(responseCode = "404", description = "해당 유저 또는 웨이블존이 존재하지 않음"), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + @ApiResponse(responseCode = "200", description = "리스트 생성 성공"), + @ApiResponse(responseCode = "400", description = "동일한 리스트명이 이미 존재") }) - public CommonResponse saveUserPlace( + public CommonResponse createPlaceList( @Parameter(hidden = true) @CurrentUser Long userId, - @RequestBody @Valid UserPlaceRequestDto request + @RequestBody @Valid UserPlaceCreateRequestDto request ) { - userPlaceService.saveUserPlace(userId, request); // userId 파라미터로 넘김 - return CommonResponse.success("장소가 저장되었습니다."); + Long placeId = userPlaceService.createPlaceList(userId, request); + return CommonResponse.success( + UserPlaceCreateResponseDto.builder() + .placeId(placeId) + .title(request.title()) + .color((request.color() == null || request.color().isBlank()) ? "GRAY" : request.color()) + .message("리스트가 생성되었습니다.") + .build() + ); } @GetMapping @@ -57,6 +61,38 @@ public CommonResponse> getMyPlaceSummaries( return CommonResponse.success(summaries); } + @DeleteMapping + @Operation( + summary = "장소에서 웨이블존 제거", + description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "제거 성공"), + @ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"), + @ApiResponse(responseCode = "403", description = "권한이 없습니다.") + }) + public CommonResponse removeZoneFromPlace( + @Parameter(hidden = true) @CurrentUser Long userId, + @RequestBody @Valid UserPlaceRemoveRequestDto request + ) { + userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); + return CommonResponse.success("제거되었습니다."); + } + + @PostMapping("/zones") + @Operation(summary = "리스트에 웨이블존 추가", + description = "placeId와 waybleZoneId 배열을 받아 여러 웨이블존을 리스트에 추가합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "웨이블존 추가 성공"), + @ApiResponse(responseCode = "404", description = "유저/리스트/웨이블존을 찾을 수 없음") + }) + public CommonResponse addZonesToPlace( + @Parameter(hidden = true) @CurrentUser Long userId, + @RequestBody @Valid UserPlaceAddZonesRequestDto request + ) { + userPlaceService.addZonesToPlace(userId, request.placeId(), request.waybleZoneIds()); + return CommonResponse.success("리스트에 웨이블존이 추가되었습니다."); + } @GetMapping("/zones") @Operation(summary = "특정 장소 내 웨이블존 목록 조회(페이징)", @@ -75,22 +111,4 @@ public CommonResponse> getZonesInPlace( Page zones = userPlaceService.getZonesInPlace(userId, placeId, page, size); return CommonResponse.success(zones); } - - @DeleteMapping - @Operation( - summary = "장소에서 웨이블존 제거", - description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다." - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "제거 성공"), - @ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"), - @ApiResponse(responseCode = "403", description = "권한이 없습니다.") - }) - public CommonResponse removeZoneFromPlace( - @Parameter(hidden = true) @CurrentUser Long userId, - @RequestBody @Valid UserPlaceRemoveRequestDto request - ) { - userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId()); - return CommonResponse.success("제거되었습니다."); - } }