Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> saveUserPlace(
public CommonResponse<UserPlaceCreateResponseDto> 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
Expand All @@ -57,6 +61,38 @@ public CommonResponse<List<UserPlaceSummaryDto>> getMyPlaceSummaries(
return CommonResponse.success(summaries);
}

@DeleteMapping
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 엔드포인트에 /zones가 붙어야 할 것 같아요!
현재 상태면 아예 장소 리스트를 삭제하는 걸로 오해할 것 같습니다!

@Operation(
summary = "장소에서 웨이블존 제거",
description = "RequestBody로 placeId, waybleZoneId를 받아 지정한 장소에서 웨이블존을 제거합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "제거 성공"),
@ApiResponse(responseCode = "404", description = "장소 또는 매핑 정보를 찾을 수 없음"),
@ApiResponse(responseCode = "403", description = "권한이 없습니다.")
})
public CommonResponse<String> 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<String> 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 = "특정 장소 내 웨이블존 목록 조회(페이징)",
Expand All @@ -75,22 +111,4 @@ public CommonResponse<Page<WaybleZoneListResponseDto>> getZonesInPlace(
Page<WaybleZoneListResponseDto> 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<String> removeZoneFromPlace(
@Parameter(hidden = true) @CurrentUser Long userId,
@RequestBody @Valid UserPlaceRemoveRequestDto request
) {
userPlaceService.removeZoneFromPlace(userId, request.placeId(), request.waybleZoneId());
return CommonResponse.success("제거되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 플래이스 리스트에 동시에 여러 개의 웨이블존을 추가하는 경우는 없을 것 같습니다!
반대로 하나의 웨이블존을 동시에 여러 개의 플레이스 리스트에 넣는 기능은 현재 화면이 구현되어 있는 상태입니다!

따라서 placeId는 리스트로, waybleZoneId는 단일 Long 값으로 받는게 좋을 것 같다는 생각입니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋㅋㅋㅋ 반대로 했네요 ㅠ.ㅠ 수정했습니다

@NotEmpty List<Long> waybleZoneIds
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wayble.server.user.dto;

import jakarta.validation.constraints.NotBlank;

public record UserPlaceCreateRequestDto(
@NotBlank(message = "제목은 필수입니다.") String title,
String color
) {}
Original file line number Diff line number Diff line change
@@ -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
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 46 additions & 29 deletions src/main/java/com/wayble/server/user/service/UserPlaceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<Long> waybleZoneIds) {
UserPlace place = userPlaceRepository.findByIdAndUser_Id(placeId, userId)
.orElseThrow(() -> new ApplicationException(UserErrorCase.PLACE_NOT_FOUND));

Set<Long> 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<UserPlaceSummaryDto> getMyPlaceSummaries(Long userId, String sort) {
userRepository.findById(userId)
Expand Down