diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/code/SuccessCode.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/code/SuccessCode.java index 9f5f985..b32e6d5 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/code/SuccessCode.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/code/SuccessCode.java @@ -19,6 +19,7 @@ public enum SuccessCode implements SuccessResultCode { ACTIVITY_TRACK_GET_SUCCESS(HttpStatus.OK, "활동 경로 데이터 조회에 성공했습니다."), ARCHIVE_ACTIVITY_LIST_GET_SUCCESS(HttpStatus.OK, "특정 날짜의 활동 목록 조회에 성공했습니다."), IMAGE_UPLOAD_URL_CREATED(HttpStatus.OK, "이미지 업로드 URL 발급에 성공했습니다."), + ROUTE_LIST_UPDATE_SUCCESS(HttpStatus.OK, "루트 목록 수정에 성공했습니다."), ROUTE_LIST_GET_SUCCESS(HttpStatus.OK, "루트 목록 조회에 성공했습니다."), TIMELINE_DELETED(HttpStatus.OK, "타임라인 삭제에 성공했습니다."), TIMELINE_LIST_GET_SUCCESS(HttpStatus.OK, "타임라인 목록 조회에 성공했습니다."), diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteController.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteController.java index 24b66b0..2c2e9de 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteController.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteController.java @@ -3,7 +3,8 @@ import java.util.List; import org.sopt.routee.activity.internal.code.SuccessCode; -import org.sopt.routee.activity.internal.controller.dto.request.CreateRoutesRequest; +import org.sopt.routee.activity.internal.controller.dto.request.RoutesCreateRequest; +import org.sopt.routee.activity.internal.controller.dto.request.RoutesUpdateRequest; import org.sopt.routee.activity.internal.controller.dto.response.RouteListResponse; import org.sopt.routee.activity.internal.service.RouteService; import org.sopt.routee.activity.internal.service.dto.result.RouteResult; @@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,7 +33,7 @@ public class RouteController implements RouteControllerDocs { @PostMapping("/{activityId}/route") public ResponseEntity> createRoutes( @PathVariable(name = "activityId") Long activityId, - @Valid @RequestBody CreateRoutesRequest request + @Valid @RequestBody RoutesCreateRequest request ) { List results = routeService.createRoutes(activityId, request.toCommands()); @@ -39,6 +41,16 @@ public ResponseEntity> createRoutes( .body(ApiResponse.success(SuccessCode.ROUTE_LIST_CREATE_SUCCESS, RouteListResponse.of(activityId, results))); } + @PutMapping("/{activityId}/route") + public ResponseEntity> updateRoutes( + @PathVariable(name = "activityId") Long activityId, + @Valid @RequestBody RoutesUpdateRequest request + ) { + List results = routeService.updateRoutes(activityId, request.toCommands()); + + return ResponseEntity.ok(ApiResponse.success(SuccessCode.ROUTE_LIST_UPDATE_SUCCESS, RouteListResponse.of(activityId, results))); + } + @GetMapping("/{activityId}/route") public ResponseEntity> getRoutes( @PathVariable(name = "activityId") Long activityId diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteControllerDocs.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteControllerDocs.java index c13f9dd..47cf34e 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteControllerDocs.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/RouteControllerDocs.java @@ -1,6 +1,7 @@ package org.sopt.routee.activity.internal.controller; -import org.sopt.routee.activity.internal.controller.dto.request.CreateRoutesRequest; +import org.sopt.routee.activity.internal.controller.dto.request.RoutesCreateRequest; +import org.sopt.routee.activity.internal.controller.dto.request.RoutesUpdateRequest; import org.sopt.routee.activity.internal.controller.dto.response.RouteListResponse; import org.sopt.routee.response.FailureResponse; import org.sopt.routee.response.SuccessResponse; @@ -49,7 +50,23 @@ public interface RouteControllerDocs { }) ResponseEntity> createRoutes( @PathVariable(name = "activityId") Long activityId, - @Valid @RequestBody CreateRoutesRequest request + @Valid @RequestBody RoutesCreateRequest request + ); + + @Operation(summary = "루트 목록 수정", description = "활동 기록의 기존 루트 목록을 요청한 루트 목록 전체로 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "루트 목록 수정 성공", + content = @Content(schema = @Schema(implementation = RouteListResponse.class))), + @ApiResponse(responseCode = "400", description = "요청 값이 올바르지 않음", + content = @Content(schema = @Schema(implementation = FailureResponse.class))), + @ApiResponse(responseCode = "401", description = "인증 실패", + content = @Content(schema = @Schema(implementation = FailureResponse.class))), + @ApiResponse(responseCode = "404", description = "활동 기록이 존재하지 않음", + content = @Content(schema = @Schema(implementation = FailureResponse.class))) + }) + ResponseEntity> updateRoutes( + @PathVariable(name = "activityId") Long activityId, + @Valid @RequestBody RoutesUpdateRequest request ); @Operation(summary = "루트 목록 조회", description = "활동 기록에 속하는 루트 목록을 조회합니다.") diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/CreateRoutesRequest.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesCreateRequest.java similarity index 94% rename from routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/CreateRoutesRequest.java rename to routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesCreateRequest.java index 05ccaa3..4b11341 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/CreateRoutesRequest.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesCreateRequest.java @@ -8,7 +8,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; -public record CreateRoutesRequest( +public record RoutesCreateRequest( @Valid @NotEmpty(message = "루트 목록 생성 시 최소 1개 이상의 루트를 입력해야 합니다.") @Size(max = 20, message = "루트는 최대 20개까지 등록할 수 있습니다.") diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java new file mode 100644 index 0000000..31eded3 --- /dev/null +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java @@ -0,0 +1,22 @@ +package org.sopt.routee.activity.internal.controller.dto.request; + +import java.util.List; + +import org.sopt.routee.activity.internal.service.dto.command.UpdateRouteCommand; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public record RoutesUpdateRequest( + @Valid + @NotNull(message = "routes는 필수입니다.") + @Size(max = 20, message = "루트는 최대 20개까지 등록할 수 있습니다.") + List<@NotNull(message = "루트는 null일 수 없습니다.") RouteRequest> routes +) { + public List toCommands() { + return routes.stream() + .map(route -> new UpdateRouteCommand(route.name(), route.sequence())) + .toList(); + } +} diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/mapper/RouteMapper.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/mapper/RouteMapper.java index 019ff13..26320dd 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/mapper/RouteMapper.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/mapper/RouteMapper.java @@ -3,6 +3,7 @@ import org.sopt.routee.activity.internal.entity.activity.Activity; import org.sopt.routee.activity.internal.entity.route.Route; import org.sopt.routee.activity.internal.service.dto.command.CreateRouteCommand; +import org.sopt.routee.activity.internal.service.dto.command.UpdateRouteCommand; import org.sopt.routee.activity.internal.service.dto.result.RouteResult; public class RouteMapper { @@ -15,6 +16,14 @@ public static Route toEntity(CreateRouteCommand command, Activity activity) { .build(); } + public static Route toEntity(UpdateRouteCommand command, Activity activity) { + return Route.builder() + .name(command.name()) + .sequence(command.sequence()) + .activity(activity) + .build(); + } + public static RouteResult toResult(Route route) { return new RouteResult(route.getId(), route.getName(), route.getSequence()); } diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/repository/RouteRepository.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/repository/RouteRepository.java index 7d05dd8..dd79b2a 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/repository/RouteRepository.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/repository/RouteRepository.java @@ -14,6 +14,10 @@ public interface RouteRepository extends JpaRepository { boolean existsByActivityId(Long activityId); + @Modifying + @Query("DELETE FROM Route r WHERE r.activity.id = :activityId") + void deleteByActivityId(@Param("activityId") Long activityId); + @Modifying @Query("DELETE FROM Route r WHERE r.activity.memberId = :memberId") void deleteByMemberId(@Param("memberId") Long memberId); diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/RouteService.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/RouteService.java index 2886182..f474ffc 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/RouteService.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/RouteService.java @@ -1,5 +1,6 @@ package org.sopt.routee.activity.internal.service; +import java.util.Comparator; import java.util.List; import org.sopt.routee.activity.internal.entity.activity.Activity; @@ -10,6 +11,7 @@ import org.sopt.routee.activity.internal.repository.ActivityRepository; import org.sopt.routee.activity.internal.repository.RouteRepository; import org.sopt.routee.activity.internal.service.dto.command.CreateRouteCommand; +import org.sopt.routee.activity.internal.service.dto.command.UpdateRouteCommand; import org.sopt.routee.activity.internal.service.dto.result.RouteResult; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,6 +44,38 @@ public List createRoutes(Long activityId, List .toList(); } + @Transactional + public List updateRoutes(Long activityId, List commands) { + if (!activityRepository.existsById(activityId)) { + throw new ActivityNotFoundException(); + } + + List sortedCommands = commands.stream() + .sorted(Comparator.comparingInt(UpdateRouteCommand::sequence)) + .toList(); + + List existingRoutes = routeRepository.findByActivityIdOrderBySequenceAsc(activityId); + List existingCommands = existingRoutes.stream() + .map(route -> new UpdateRouteCommand(route.getName(), route.getSequence())) + .toList(); + if (existingCommands.equals(sortedCommands)) { + return existingRoutes.stream() + .map(RouteMapper::toResult) + .toList(); + } + + routeRepository.deleteByActivityId(activityId); + Activity activity = activityRepository.getReferenceById(activityId); + + List routes = sortedCommands.stream() + .map(command -> RouteMapper.toEntity(command, activity)) + .toList(); + + return routeRepository.saveAll(routes).stream() + .map(RouteMapper::toResult) + .toList(); + } + @Transactional(readOnly = true) public List getRoutes(Long activityId) { if (!activityRepository.existsById(activityId)) { diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/dto/command/UpdateRouteCommand.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/dto/command/UpdateRouteCommand.java new file mode 100644 index 0000000..c5168c4 --- /dev/null +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/service/dto/command/UpdateRouteCommand.java @@ -0,0 +1,7 @@ +package org.sopt.routee.activity.internal.service.dto.command; + +public record UpdateRouteCommand( + String name, + int sequence +) { +}