From 24c368727ce83088989c8c92907adb07785af61c Mon Sep 17 00:00:00 2001 From: youtheyeon Date: Tue, 6 Oct 2026 23:55:51 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=ED=99=9C=EB=8F=99=20=EB=A3=A8?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=A9=EB=A1=9D=20=EC=88=98=EC=A0=95=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/internal/code/SuccessCode.java | 1 + .../internal/controller/RouteController.java | 12 +++++++ .../controller/RouteControllerDocs.java | 17 ++++++++++ .../dto/request/UpdateRoutesRequest.java | 22 +++++++++++++ .../activity/internal/mapper/RouteMapper.java | 9 ++++++ .../internal/repository/RouteRepository.java | 4 +++ .../internal/service/RouteService.java | 32 +++++++++++++++++++ .../dto/command/UpdateRouteCommand.java | 7 ++++ 8 files changed, 104 insertions(+) create mode 100644 routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.java create mode 100644 routee-activity/src/main/java/org/sopt/routee/activity/internal/service/dto/command/UpdateRouteCommand.java 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 9f5f985e..b32e6d59 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 24b66b0f..6228fdaa 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 @@ -4,6 +4,7 @@ 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.UpdateRoutesRequest; 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; @@ -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 UpdateRoutesRequest 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 c13f9dd5..94734397 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.UpdateRoutesRequest; import org.sopt.routee.activity.internal.controller.dto.response.RouteListResponse; import org.sopt.routee.response.FailureResponse; import org.sopt.routee.response.SuccessResponse; @@ -52,6 +53,22 @@ ResponseEntity> createRoutes( @Valid @RequestBody CreateRoutesRequest 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 UpdateRoutesRequest request + ); + @Operation(summary = "루트 목록 조회", description = "활동 기록에 속하는 루트 목록을 조회합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "루트 목록 조회 성공", diff --git a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.java new file mode 100644 index 00000000..d56b4466 --- /dev/null +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.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 UpdateRoutesRequest( + @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 019ff13a..26320dd1 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 7d05dd88..dd79b2aa 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 28861828..aba6cdb2 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,36 @@ public List createRoutes(Long activityId, List .toList(); } + @Transactional + public List updateRoutes(Long activityId, List commands) { + Activity activity = activityRepository.findById(activityId) + .orElseThrow(ActivityNotFoundException::new); + + 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); + + 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 00000000..c5168c4f --- /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 +) { +} From 5ae8b9aa496910dc63aa02304b6c93ed0caefb80 Mon Sep 17 00:00:00 2001 From: youtheyeon Date: Tue, 6 Oct 2026 23:56:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EB=A3=A8=ED=8A=B8=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20DTO=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../activity/internal/controller/RouteController.java | 8 ++++---- .../activity/internal/controller/RouteControllerDocs.java | 8 ++++---- ...{CreateRoutesRequest.java => RoutesCreateRequest.java} | 2 +- ...{UpdateRoutesRequest.java => RoutesUpdateRequest.java} | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/{CreateRoutesRequest.java => RoutesCreateRequest.java} (94%) rename routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/{UpdateRoutesRequest.java => RoutesUpdateRequest.java} (95%) 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 6228fdaa..2c2e9ded 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,8 +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.UpdateRoutesRequest; +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; @@ -33,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()); @@ -44,7 +44,7 @@ public ResponseEntity> createRoutes( @PutMapping("/{activityId}/route") public ResponseEntity> updateRoutes( @PathVariable(name = "activityId") Long activityId, - @Valid @RequestBody UpdateRoutesRequest request + @Valid @RequestBody RoutesUpdateRequest request ) { List results = routeService.updateRoutes(activityId, request.toCommands()); 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 94734397..47cf34e9 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,7 +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.UpdateRoutesRequest; +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; @@ -50,7 +50,7 @@ public interface RouteControllerDocs { }) ResponseEntity> createRoutes( @PathVariable(name = "activityId") Long activityId, - @Valid @RequestBody CreateRoutesRequest request + @Valid @RequestBody RoutesCreateRequest request ); @Operation(summary = "루트 목록 수정", description = "활동 기록의 기존 루트 목록을 요청한 루트 목록 전체로 수정합니다.") @@ -66,7 +66,7 @@ ResponseEntity> createRoutes( }) ResponseEntity> updateRoutes( @PathVariable(name = "activityId") Long activityId, - @Valid @RequestBody UpdateRoutesRequest request + @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 05ccaa30..4b11341d 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/UpdateRoutesRequest.java b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java similarity index 95% rename from routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.java rename to routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java index d56b4466..31eded3e 100644 --- a/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/UpdateRoutesRequest.java +++ b/routee-activity/src/main/java/org/sopt/routee/activity/internal/controller/dto/request/RoutesUpdateRequest.java @@ -8,7 +8,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -public record UpdateRoutesRequest( +public record RoutesUpdateRequest( @Valid @NotNull(message = "routes는 필수입니다.") @Size(max = 20, message = "루트는 최대 20개까지 등록할 수 있습니다.") From 0b37120853028695088e1121805ba588e69b684b Mon Sep 17 00:00:00 2001 From: youtheyeon Date: Wed, 7 Oct 2026 17:14:51 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=ED=99=9C=EB=8F=99=20=EC=A1=B4=EC=9E=AC?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=EC=9D=84=20existsById=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/routee/activity/internal/service/RouteService.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 aba6cdb2..f474ffcb 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 @@ -46,8 +46,9 @@ public List createRoutes(Long activityId, List @Transactional public List updateRoutes(Long activityId, List commands) { - Activity activity = activityRepository.findById(activityId) - .orElseThrow(ActivityNotFoundException::new); + if (!activityRepository.existsById(activityId)) { + throw new ActivityNotFoundException(); + } List sortedCommands = commands.stream() .sorted(Comparator.comparingInt(UpdateRouteCommand::sequence)) @@ -64,6 +65,7 @@ public List updateRoutes(Long activityId, List } routeRepository.deleteByActivityId(activityId); + Activity activity = activityRepository.getReferenceById(activityId); List routes = sortedCommands.stream() .map(command -> RouteMapper.toEntity(command, activity))