From 3a936626a1274787a0d652154b88dd4afb23be14 Mon Sep 17 00:00:00 2001 From: damiiya Date: Wed, 14 Jan 2026 15:29:02 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=EC=99=84=EB=A3=8C=EB=90=9C=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?dto/service/repository/controller=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteCompletedController.java | 44 +++++++++++++++++ .../response/CompletedRouteListResponse.java | 15 ++++++ .../CompletedRouteSummaryResponse.java | 16 ++++++ .../route/repository/RouteRepository.java | 7 +++ .../service/RouteCompletedQueryService.java | 49 +++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 src/main/java/com/earseo/route/controller/RouteCompletedController.java create mode 100644 src/main/java/com/earseo/route/dto/response/CompletedRouteListResponse.java create mode 100644 src/main/java/com/earseo/route/dto/response/CompletedRouteSummaryResponse.java create mode 100644 src/main/java/com/earseo/route/service/RouteCompletedQueryService.java diff --git a/src/main/java/com/earseo/route/controller/RouteCompletedController.java b/src/main/java/com/earseo/route/controller/RouteCompletedController.java new file mode 100644 index 0000000..908fa46 --- /dev/null +++ b/src/main/java/com/earseo/route/controller/RouteCompletedController.java @@ -0,0 +1,44 @@ +package com.earseo.route.controller; + +import com.earseo.route.common.BaseResponse; +import com.earseo.route.dto.response.CompletedRouteListResponse; +import com.earseo.route.service.RouteCompletedQueryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/user/route") +@RequiredArgsConstructor +public class RouteCompletedController { + + private final RouteCompletedQueryService routeCompletedQueryService; + + @Operation( + summary = "[완료된 경로 기록 관리] 완료된 경로 리스트 조회", + description = """ + 정상 종료(COMPLETED)된 경로를 최신 생성순(createdAt DESC)으로 조회합니다. + 무한 스크롤을 위해 page/size 기반 페이징을 사용합니다. + """ + ) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "완료된 경로 리스트 조회 성공", + content = @Content(schema = @Schema(implementation = CompletedRouteListResponse.class)) + ) + }) + @GetMapping("/completed") + public ResponseEntity> getCompletedRoutes ( + @RequestHeader("X-USER-ID") Long userId, + Pageable pageable + ) { + return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.getCompletedRoutes(userId, pageable))); + } +} diff --git a/src/main/java/com/earseo/route/dto/response/CompletedRouteListResponse.java b/src/main/java/com/earseo/route/dto/response/CompletedRouteListResponse.java new file mode 100644 index 0000000..b8cc2a5 --- /dev/null +++ b/src/main/java/com/earseo/route/dto/response/CompletedRouteListResponse.java @@ -0,0 +1,15 @@ +package com.earseo.route.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +@Schema(description = "완료된 경로 리스트 응답") +public record CompletedRouteListResponse( + @Schema(description = "완료된 경로 리스트") + List routes, + + @Schema(description = "다음 페이지 존재 여부", example = "true") + boolean hasNext +) { +} diff --git a/src/main/java/com/earseo/route/dto/response/CompletedRouteSummaryResponse.java b/src/main/java/com/earseo/route/dto/response/CompletedRouteSummaryResponse.java new file mode 100644 index 0000000..93922ce --- /dev/null +++ b/src/main/java/com/earseo/route/dto/response/CompletedRouteSummaryResponse.java @@ -0,0 +1,16 @@ +package com.earseo.route.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "완료된 경로 리스트 요약 정보") +public record CompletedRouteSummaryResponse( + @Schema(description = "경로 ID", example = "12") + Long routeId, + + @Schema(description = "경로 이름", example = "경복궁-종묘") + String name, + + @Schema(description = "완료 날짜 (yyyy.MM.dd)", example = "2025.12.10") + String completedAt +) { +} diff --git a/src/main/java/com/earseo/route/repository/RouteRepository.java b/src/main/java/com/earseo/route/repository/RouteRepository.java index ac7e56c..968f9c5 100644 --- a/src/main/java/com/earseo/route/repository/RouteRepository.java +++ b/src/main/java/com/earseo/route/repository/RouteRepository.java @@ -3,6 +3,8 @@ import com.earseo.route.entity.Route; import com.earseo.route.entity.RouteStatus; import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -12,4 +14,9 @@ public interface RouteRepository extends JpaRepository { @EntityGraph(attributePaths = "items") Optional findWithItemsByMemberIdAndStatus(Long memberId, RouteStatus status); + Page findByMemberIdAndStatusOrderByCreatedAtDesc( + Long memberId, + RouteStatus status, + Pageable pageable + ); } diff --git a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java new file mode 100644 index 0000000..0392565 --- /dev/null +++ b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java @@ -0,0 +1,49 @@ +package com.earseo.route.service; + +import com.earseo.route.dto.response.CompletedRouteListResponse; +import com.earseo.route.dto.response.CompletedRouteSummaryResponse; +import com.earseo.route.entity.Route; +import com.earseo.route.entity.RouteStatus; +import com.earseo.route.repository.RouteRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class RouteCompletedQueryService { + + private static final int DEFAULT_PAGE_SIZE = 15; + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd"); + + private final RouteRepository routeRepository; + + public CompletedRouteListResponse getCompletedRoutes( + Long memberId, + Pageable pageable + ) { + Slice slice = routeRepository.findByMemberIdAndStatusOrderByCreatedAtDesc( + memberId, + RouteStatus.COMPLETED, + pageable + ); + + List routes = slice.getContent().stream().map(this::toResponse).toList(); + + return new CompletedRouteListResponse(routes, slice.hasNext()); + } + + private CompletedRouteSummaryResponse toResponse(Route route) { + return new CompletedRouteSummaryResponse( + route.getId(), + route.getName(), + route.getCreatedAt().format(DATE_FORMATTER) + ); + } +} From b2e2ef4a86d633215d5642105c290426e4ee4ac0 Mon Sep 17 00:00:00 2001 From: haribonyam Date: Sun, 18 Jan 2026 16:43:39 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[#5]=20feat:=20=EC=99=84=EB=A3=8C=EB=90=9C?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteCompletedController.java | 27 ++++++++++++++--- .../CompletedRouteDetailResponse.java | 15 ++++++++++ .../response/CompletedRouteItemResponse.java | 21 ++++++++++++++ .../route/repository/RouteRepository.java | 16 ++++++++++ .../service/RouteCompletedQueryService.java | 29 ++++++++++++++++--- 5 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java create mode 100644 src/main/java/com/earseo/route/dto/response/CompletedRouteItemResponse.java diff --git a/src/main/java/com/earseo/route/controller/RouteCompletedController.java b/src/main/java/com/earseo/route/controller/RouteCompletedController.java index 908fa46..9069b81 100644 --- a/src/main/java/com/earseo/route/controller/RouteCompletedController.java +++ b/src/main/java/com/earseo/route/controller/RouteCompletedController.java @@ -1,6 +1,7 @@ package com.earseo.route.controller; import com.earseo.route.common.BaseResponse; +import com.earseo.route.dto.response.CompletedRouteDetailResponse; import com.earseo.route.dto.response.CompletedRouteListResponse; import com.earseo.route.service.RouteCompletedQueryService; import io.swagger.v3.oas.annotations.Operation; @@ -10,6 +11,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -35,10 +37,27 @@ public class RouteCompletedController { ) }) @GetMapping("/completed") - public ResponseEntity> getCompletedRoutes ( - @RequestHeader("X-USER-ID") Long userId, - Pageable pageable - ) { + public ResponseEntity> getCompletedRoutes (@RequestHeader("X-USER-ID") Long userId, + @PageableDefault(size = 10, page = 0) Pageable pageable) { return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.getCompletedRoutes(userId, pageable))); } + + @Operation( + summary = "[완료된 경로 기록 관리] 완료된 경로 상세정보 조회", + description = """ + 완료된 경로의 상세정보를 사용자 ID, 완료된 경로의 ID 기반으로 조회합니다. + """ + ) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "완료된 경로 상세정보 조회 성공", + content = @Content(schema = @Schema(implementation = CompletedRouteDetailResponse.class)) + ) + }) + @GetMapping("/completed/detail/{routeId}") + public ResponseEntity> getCompletedRouteDetail(@RequestHeader("X-USER-ID") Long userId, + @PathVariable("routeId") Long routeId) { + return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.getCompletedRouteDetail(userId, routeId))); + } } diff --git a/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java b/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java new file mode 100644 index 0000000..b66b96c --- /dev/null +++ b/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java @@ -0,0 +1,15 @@ +package com.earseo.route.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.List; + +@Schema(description = "완료된 경로 상세정보 응답") +public record CompletedRouteDetailResponse( + @Schema(description = "완료된 경로 리스트 요약 정보") + CompletedRouteSummaryResponse route, + + @Schema(description = "다음 페이지 존재 여부", example = "true") + List items +) { +} diff --git a/src/main/java/com/earseo/route/dto/response/CompletedRouteItemResponse.java b/src/main/java/com/earseo/route/dto/response/CompletedRouteItemResponse.java new file mode 100644 index 0000000..d3c5742 --- /dev/null +++ b/src/main/java/com/earseo/route/dto/response/CompletedRouteItemResponse.java @@ -0,0 +1,21 @@ +package com.earseo.route.dto.response; + + +import com.earseo.route.entity.RouteRefType; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "완료된 경로를 구성하는 개별 지점(SIGHT / STORY_SPOT)의 정보") +public record CompletedRouteItemResponse( + @Schema(description = "아이템 타입(SIGHT / STORY_SPOT)", example = "SIGHT") + RouteRefType itemType, + + @Schema(description = "참조 ID(관광지 / 스토리 스팟의 ID", example = "12") + String itemId, + + @Schema(description = "노출용 이름", example = "경복궁") + String itemName, + + @Schema(description = "대표 이미지 URL", example = "https://cdn.example.com/image/spot/1.jpg") + String itemImageUrl +) { +} diff --git a/src/main/java/com/earseo/route/repository/RouteRepository.java b/src/main/java/com/earseo/route/repository/RouteRepository.java index 968f9c5..818c535 100644 --- a/src/main/java/com/earseo/route/repository/RouteRepository.java +++ b/src/main/java/com/earseo/route/repository/RouteRepository.java @@ -6,6 +6,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.Optional; @@ -14,9 +16,23 @@ public interface RouteRepository extends JpaRepository { @EntityGraph(attributePaths = "items") Optional findWithItemsByMemberIdAndStatus(Long memberId, RouteStatus status); + Page findByMemberIdAndStatusOrderByCreatedAtDesc( Long memberId, RouteStatus status, Pageable pageable ); + + @Query(""" + select distinct r + from Route r + left join fetch r.items ri + where r.id = :routeId + and r.memberId = :memberId + order by ri.createdAt asc + """) + Optional findCompletedRouteDetail( + @Param("routeId") Long routeId, + @Param("memberId") Long memberId + ); } diff --git a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java index 0392565..cf44c5a 100644 --- a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java +++ b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java @@ -1,8 +1,13 @@ package com.earseo.route.service; +import com.earseo.route.common.exception.BaseException; +import com.earseo.route.common.exception.RouteError; +import com.earseo.route.dto.response.CompletedRouteDetailResponse; +import com.earseo.route.dto.response.CompletedRouteItemResponse; import com.earseo.route.dto.response.CompletedRouteListResponse; import com.earseo.route.dto.response.CompletedRouteSummaryResponse; import com.earseo.route.entity.Route; +import com.earseo.route.entity.RouteItem; import com.earseo.route.entity.RouteStatus; import com.earseo.route.repository.RouteRepository; import lombok.RequiredArgsConstructor; @@ -24,10 +29,7 @@ public class RouteCompletedQueryService { private final RouteRepository routeRepository; - public CompletedRouteListResponse getCompletedRoutes( - Long memberId, - Pageable pageable - ) { + public CompletedRouteListResponse getCompletedRoutes(Long memberId, Pageable pageable) { Slice slice = routeRepository.findByMemberIdAndStatusOrderByCreatedAtDesc( memberId, RouteStatus.COMPLETED, @@ -46,4 +48,23 @@ private CompletedRouteSummaryResponse toResponse(Route route) { route.getCreatedAt().format(DATE_FORMATTER) ); } + + private CompletedRouteItemResponse toRouteItemResponse(RouteItem routeItem) { + return new CompletedRouteItemResponse( + routeItem.getRefType(), + routeItem.getRefId(), + routeItem.getName(), + routeItem.getImageUrl() + ); + } + + @Transactional + public CompletedRouteDetailResponse getCompletedRouteDetail(Long userId, Long routeId) { + Route route = routeRepository.findCompletedRouteDetail(userId, routeId) + .orElseThrow(() -> new BaseException(RouteError.ROUTE_NOT_FOUND)); + + List routeItems = route.getItems(); + + return new CompletedRouteDetailResponse(toResponse(route), routeItems.stream().map(this::toRouteItemResponse).toList()); + } } From 7e1c97d08b1680433adbcc82cf8cf979dbe2f1d1 Mon Sep 17 00:00:00 2001 From: haribonyam Date: Sun, 18 Jan 2026 17:08:04 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[#5]=20feat:=20=EC=99=84=EB=A3=8C=EB=90=9C?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteCompletedController.java | 9 +++++++++ .../dto/request/ModifyCompletedRouteRequest.java | 13 +++++++++++++ .../response/CompletedRouteDetailResponse.java | 4 ++-- src/main/java/com/earseo/route/entity/Route.java | 5 +++++ .../service/RouteCompletedQueryService.java | 16 ++++++++++++++-- 5 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/earseo/route/dto/request/ModifyCompletedRouteRequest.java diff --git a/src/main/java/com/earseo/route/controller/RouteCompletedController.java b/src/main/java/com/earseo/route/controller/RouteCompletedController.java index 9069b81..d10f4df 100644 --- a/src/main/java/com/earseo/route/controller/RouteCompletedController.java +++ b/src/main/java/com/earseo/route/controller/RouteCompletedController.java @@ -1,14 +1,17 @@ package com.earseo.route.controller; import com.earseo.route.common.BaseResponse; +import com.earseo.route.dto.request.ModifyCompletedRouteRequest; import com.earseo.route.dto.response.CompletedRouteDetailResponse; import com.earseo.route.dto.response.CompletedRouteListResponse; +import com.earseo.route.dto.response.CompletedRouteSummaryResponse; import com.earseo.route.service.RouteCompletedQueryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; 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.Pageable; import org.springframework.data.web.PageableDefault; @@ -60,4 +63,10 @@ public ResponseEntity> getCompletedRo @PathVariable("routeId") Long routeId) { return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.getCompletedRouteDetail(userId, routeId))); } + + @PutMapping("/completed/detail/{routeId}") + public ResponseEntity> modifyCompletedRouteName(@Valid @RequestBody ModifyCompletedRouteRequest modifyCompletedRoute, + @RequestHeader("X-USER-ID") Long userId, @PathVariable("routeId") Long routeId) { + return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.modifyCompletedRouteName(modifyCompletedRoute, userId, routeId))); + } } diff --git a/src/main/java/com/earseo/route/dto/request/ModifyCompletedRouteRequest.java b/src/main/java/com/earseo/route/dto/request/ModifyCompletedRouteRequest.java new file mode 100644 index 0000000..80a3790 --- /dev/null +++ b/src/main/java/com/earseo/route/dto/request/ModifyCompletedRouteRequest.java @@ -0,0 +1,13 @@ +package com.earseo.route.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Schema(description = "완료된 경로 수정 요청 DTO") +public record ModifyCompletedRouteRequest( + @NotBlank(message = "경로의 이름은 필수입니다") + @Schema(description = "변경할 완료된 경로 이름", example = "나의 여행 1") + String name + +) {} diff --git a/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java b/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java index b66b96c..0eed95b 100644 --- a/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java +++ b/src/main/java/com/earseo/route/dto/response/CompletedRouteDetailResponse.java @@ -6,10 +6,10 @@ @Schema(description = "완료된 경로 상세정보 응답") public record CompletedRouteDetailResponse( - @Schema(description = "완료된 경로 리스트 요약 정보") + @Schema(description = "완료된 경로 요약 정보") CompletedRouteSummaryResponse route, - @Schema(description = "다음 페이지 존재 여부", example = "true") + @Schema(description = "완료된 경로의 관광지 및 이야기 스팟 리스트") List items ) { } diff --git a/src/main/java/com/earseo/route/entity/Route.java b/src/main/java/com/earseo/route/entity/Route.java index b1a0e27..2b81bed 100644 --- a/src/main/java/com/earseo/route/entity/Route.java +++ b/src/main/java/com/earseo/route/entity/Route.java @@ -84,4 +84,9 @@ protected void onCreate() { protected void onUpdate() { this.updatedAt = LocalDateTime.now(); } + + public void modifyName(String name){ + this.updatedAt = LocalDateTime.now(); + this.name = name; + } } diff --git a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java index cf44c5a..f85ae93 100644 --- a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java +++ b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java @@ -2,6 +2,7 @@ import com.earseo.route.common.exception.BaseException; import com.earseo.route.common.exception.RouteError; +import com.earseo.route.dto.request.ModifyCompletedRouteRequest; import com.earseo.route.dto.response.CompletedRouteDetailResponse; import com.earseo.route.dto.response.CompletedRouteItemResponse; import com.earseo.route.dto.response.CompletedRouteListResponse; @@ -59,12 +60,23 @@ private CompletedRouteItemResponse toRouteItemResponse(RouteItem routeItem) { } @Transactional - public CompletedRouteDetailResponse getCompletedRouteDetail(Long userId, Long routeId) { - Route route = routeRepository.findCompletedRouteDetail(userId, routeId) + public CompletedRouteDetailResponse getCompletedRouteDetail(Long memberId, Long routeId) { + Route route = routeRepository.findCompletedRouteDetail(memberId, routeId) .orElseThrow(() -> new BaseException(RouteError.ROUTE_NOT_FOUND)); List routeItems = route.getItems(); return new CompletedRouteDetailResponse(toResponse(route), routeItems.stream().map(this::toRouteItemResponse).toList()); } + + @Transactional + public CompletedRouteSummaryResponse modifyCompletedRouteName(ModifyCompletedRouteRequest modifyCompletedRoute, Long memberId, Long routeId) { + Route route = routeRepository.findCompletedRouteDetail(memberId, routeId) + .orElseThrow(() -> new BaseException(RouteError.ROUTE_NOT_FOUND)); + + route.modifyName(modifyCompletedRoute.name()); + routeRepository.save(route); + + return new CompletedRouteSummaryResponse(route.getId(), route.getName(), route.getCreatedAt().format(DATE_FORMATTER)); + } } From b2507d96bf783b8c322ad4fda56866083cc7a11d Mon Sep 17 00:00:00 2001 From: haribonyam Date: Sun, 18 Jan 2026 17:25:01 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[#5]=20feat:=20=EC=99=84=EB=A3=8C=EB=90=9C?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteCompletedController.java | 33 ++++++++++++++++++- .../service/RouteCompletedQueryService.java | 8 +++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/earseo/route/controller/RouteCompletedController.java b/src/main/java/com/earseo/route/controller/RouteCompletedController.java index d10f4df..de20365 100644 --- a/src/main/java/com/earseo/route/controller/RouteCompletedController.java +++ b/src/main/java/com/earseo/route/controller/RouteCompletedController.java @@ -64,9 +64,40 @@ public ResponseEntity> getCompletedRo return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.getCompletedRouteDetail(userId, routeId))); } - @PutMapping("/completed/detail/{routeId}") + @Operation( + summary = "[완료된 경로 기록 관리] 완료된 경로 이름 변경", + description = """ + 완료된 경로의 이름을 변경합니다. + """ + ) + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "완료된 경로 이름 변경 성공", + content = @Content(schema = @Schema(implementation = CompletedRouteSummaryResponse.class)) + ) + }) + @PutMapping("/completed/{routeId}") public ResponseEntity> modifyCompletedRouteName(@Valid @RequestBody ModifyCompletedRouteRequest modifyCompletedRoute, @RequestHeader("X-USER-ID") Long userId, @PathVariable("routeId") Long routeId) { return ResponseEntity.ok(BaseResponse.ok(routeCompletedQueryService.modifyCompletedRouteName(modifyCompletedRoute, userId, routeId))); } + + @Operation( + summary = "[완료된 경로 기록 관리] 완료된 경로 삭제", + description = """ + 완료된 경로를 삭제합니다. + """ + ) + @ApiResponses({ + @ApiResponse( + responseCode = "204", + description = "완료된 경로 삭제 성공" + ) + }) + @DeleteMapping("/completed/{routeId}") + public ResponseEntity deleteRoute(@PathVariable("routeId") Long routeId, @RequestHeader("X-USER-ID") Long userId) { + routeCompletedQueryService.deleteRoute(routeId, userId); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java index f85ae93..7accfd4 100644 --- a/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java +++ b/src/main/java/com/earseo/route/service/RouteCompletedQueryService.java @@ -79,4 +79,12 @@ public CompletedRouteSummaryResponse modifyCompletedRouteName(ModifyCompletedRou return new CompletedRouteSummaryResponse(route.getId(), route.getName(), route.getCreatedAt().format(DATE_FORMATTER)); } + + @Transactional + public void deleteRoute(Long routeId, Long memberId) { + Route route = routeRepository.findCompletedRouteDetail(memberId, routeId) + .orElseThrow(() -> new BaseException(RouteError.ROUTE_NOT_FOUND)); + + routeRepository.delete(route); + } } From d512939e9611c81693865f6fbc40dfe08b2d9c15 Mon Sep 17 00:00:00 2001 From: haribonyam Date: Thu, 22 Jan 2026 15:35:54 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[#5]=20feat:=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=97=90=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RouteCompletedController.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/main/java/com/earseo/route/controller/RouteCompletedController.java b/src/main/java/com/earseo/route/controller/RouteCompletedController.java index de20365..ab86f39 100644 --- a/src/main/java/com/earseo/route/controller/RouteCompletedController.java +++ b/src/main/java/com/earseo/route/controller/RouteCompletedController.java @@ -8,6 +8,7 @@ import com.earseo.route.service.RouteCompletedQueryService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -15,6 +16,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -56,6 +58,22 @@ public ResponseEntity> getCompletedRout responseCode = "200", description = "완료된 경로 상세정보 조회 성공", content = @Content(schema = @Schema(implementation = CompletedRouteDetailResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "사용자 정보와 완료된 경로 검증 실패 및 조회 실패", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = @ExampleObject( + value = """ + { + "status": "RUT002", + "message": "존재하지 않는 경로입니다.", + "data": null + } + """ + ) + ) ) }) @GetMapping("/completed/detail/{routeId}") @@ -75,6 +93,22 @@ public ResponseEntity> getCompletedRo responseCode = "200", description = "완료된 경로 이름 변경 성공", content = @Content(schema = @Schema(implementation = CompletedRouteSummaryResponse.class)) + ), + @ApiResponse( + responseCode = "404", + description = "사용자 정보와 완료된 경로 검증 실패 및 조회 실패", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = @ExampleObject( + value = """ + { + "status": "RUT002", + "message": "존재하지 않는 경로입니다.", + "data": null + } + """ + ) + ) ) }) @PutMapping("/completed/{routeId}") @@ -93,6 +127,22 @@ public ResponseEntity> modifyComplet @ApiResponse( responseCode = "204", description = "완료된 경로 삭제 성공" + ), + @ApiResponse( + responseCode = "404", + description = "사용자 정보와 완료된 경로 검증 실패 및 조회 실패", + content = @Content( + mediaType = MediaType.APPLICATION_JSON_VALUE, + examples = @ExampleObject( + value = """ + { + "status": "RUT002", + "message": "존재하지 않는 경로입니다.", + "data": null + } + """ + ) + ) ) }) @DeleteMapping("/completed/{routeId}")