From 5ef6c4a34c554cc90ce60b846295b2b216634f1d Mon Sep 17 00:00:00 2001 From: khs96523 Date: Sat, 8 Nov 2025 19:10:55 +0900 Subject: [PATCH] =?UTF-8?q?DOCS:=20Store,=20Order,=20Review=20Swagger=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See also: #135 --- .../order/presentation/webapi/OrderApi.java | 107 +++++++++++++++++- .../review/presentation/webapi/ReviewApi.java | 16 ++- .../store/presentation/webapi/StoreApi.java | 84 ++++++++++++-- 3 files changed, 193 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sparta/deliveryi/order/presentation/webapi/OrderApi.java b/src/main/java/com/sparta/deliveryi/order/presentation/webapi/OrderApi.java index 46c6783..51f7682 100644 --- a/src/main/java/com/sparta/deliveryi/order/presentation/webapi/OrderApi.java +++ b/src/main/java/com/sparta/deliveryi/order/presentation/webapi/OrderApi.java @@ -6,6 +6,10 @@ import com.sparta.deliveryi.order.domain.service.OrderCreator; import com.sparta.deliveryi.order.domain.service.OrderFinder; import com.sparta.deliveryi.order.domain.service.OrderManager; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -24,6 +28,11 @@ import static com.sparta.deliveryi.global.presentation.dto.ApiResponse.successWithDataOnly; import static org.springframework.http.ResponseEntity.ok; +@Tag(name = "주문 API", description = """ +주문 요청(Order Requested)부터 주문 완료(Order Completed)까지의 주문 처리 단계 관련 기능 제공. +- 주문 생성 / 조회 / 취소 +- 주문 수락 / 거절 / 조리 완료 / 배달 시작 / 주문 완료 +""") @RestController @RequiredArgsConstructor public class OrderApi { @@ -36,6 +45,13 @@ public class OrderApi { private final OrderApplication orderApplication; + @Operation( + summary = "주문 요청 (Order Requested)", + description = """ + 고객이 신규 주문을 생성합니다. + 주문 생성 시 상태는 기본적으로 '주문 요청(Order Requested)'으로 설정됩니다. + """ + ) @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders") public ResponseEntity> create(@RequestBody @Valid OrderCreateRequest createRequest) { @@ -44,10 +60,16 @@ public ResponseEntity> create(@RequestBody @Val return ok(ApiResponse.successWithDataOnly(OrderCreateResponse.from(order))); } + @Operation( + summary = "주문 검색", + description = "상점 및 주문자 기준으로 주문 목록을 조회합니다." + ) @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @GetMapping("/v1/orders") public ResponseEntity>> search( + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.QUERY, required = true) @RequestParam UUID storeId, + @Parameter(name = "ordererId", description = "주문자 ID (UUID)", in = ParameterIn.QUERY, required = true) @RequestParam UUID ordererId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { @@ -59,10 +81,17 @@ public ResponseEntity>> search( return ok(successWithDataOnly(responses)); } + @Operation( + summary = "배달 주소 변경", + description = """ + 주문 수락(Order Accepted) 상태 이전의 주문에 대해 배달 주소를 수정합니다. + """ + ) @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PutMapping("/v1/orders/{orderId}/delivery-address") public ResponseEntity> changeDeliveryAddress( @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) @PathVariable UUID orderId, @RequestBody @Valid ChangeDeliveryAddressRequest changeRequest ) { @@ -72,9 +101,20 @@ public ResponseEntity> changeDelivery return ok(ApiResponse.successWithDataOnly(ChangeDeliveryAddressResponse.from(order))); } + @Operation( + summary = "주문 수락 (Order Accepted)", + description = """ + 주문을 수락합니다. + 주문 상태가 '주문 수락(Order Accepted)'으로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/accept") - public ResponseEntity> accept(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> accept( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderApplication.accept(orderId, requestId); @@ -82,9 +122,20 @@ public ResponseEntity> accept(@AuthenticationPrincipal Jwt jwt return ok(success()); } + @Operation( + summary = "주문 거절 (Order Rejected)", + description = """ + 주문을 거절합니다. + 주문 상태가 '주문 거절(Order Rejected)'로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/reject") - public ResponseEntity> reject(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> reject( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderApplication.reject(orderId, requestId); @@ -92,9 +143,20 @@ public ResponseEntity> reject(@AuthenticationPrincipal Jwt jwt return ok(success()); } + @Operation( + summary = "주문 취소 (Order Canceled)", + description = """ + 주문을 취소합니다. + 주문 상태가 '주문 취소(Order Canceled)'로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/cancel") - public ResponseEntity> cancel(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> cancel( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderManager.cancel(OrderId.of(orderId), requestId); @@ -102,9 +164,20 @@ public ResponseEntity> cancel(@AuthenticationPrincipal Jwt jwt return ok(success()); } + @Operation( + summary = "조리 완료 (Ready To Served)", + description = """ + 점주(OWNER) 또는 관리자(MANAGER/MASTER)가 조리가 완료되었음을 알립니다. + 주문 상태가 '주문 수락(Order Accepted)' → '조리 완료(Ready To Served)'로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/complete-cooking") - public ResponseEntity> completeCooking(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> completeCooking( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderApplication.completeCooking(orderId, requestId); @@ -112,9 +185,20 @@ public ResponseEntity> completeCooking(@AuthenticationPrincipa return ok(success()); } + @Operation( + summary = "배달 중 (Delivering)", + description = """ + 점주(OWNER) 또는 관리자(MANAGER/MASTER)가 배달을 시작합니다. + 주문 상태가 '조리 완료(Ready To Served)' → '배달 중(Delivering)'으로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/delivery") - public ResponseEntity> delivery(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> delivery( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderApplication.delivery(orderId, requestId); @@ -122,9 +206,20 @@ public ResponseEntity> delivery(@AuthenticationPrincipal Jwt j return ok(success()); } + @Operation( + summary = "주문 완료 (Order Completed)", + description = """ + 배달이 완료되어 주문을 마무리합니다. + 주문 상태가 '배달 중(Delivering)' → '주문 완료(Order Completed)'으로 변경됩니다. + """ + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/orders/{orderId}/complete") - public ResponseEntity> complete(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID orderId) { + public ResponseEntity> complete( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "orderId", description = "주문 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID orderId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); orderApplication.complete(orderId, requestId); diff --git a/src/main/java/com/sparta/deliveryi/review/presentation/webapi/ReviewApi.java b/src/main/java/com/sparta/deliveryi/review/presentation/webapi/ReviewApi.java index 3d3750c..2a70202 100644 --- a/src/main/java/com/sparta/deliveryi/review/presentation/webapi/ReviewApi.java +++ b/src/main/java/com/sparta/deliveryi/review/presentation/webapi/ReviewApi.java @@ -5,6 +5,9 @@ import com.sparta.deliveryi.review.domain.Review; import com.sparta.deliveryi.review.domain.ReviewRegisterRequest; import com.sparta.deliveryi.review.domain.ReviewUpdateRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -22,12 +25,14 @@ import static com.sparta.deliveryi.global.presentation.dto.ApiResponse.successWithDataOnly; import static org.springframework.http.ResponseEntity.ok; +@Tag(name = "리뷰 API", description = "리뷰 등록, 조회, 수정, 삭제 관련 API 제공") @RestController @RequiredArgsConstructor public class ReviewApi { private final ReviewApplication reviewApplication; + @Operation(summary = "리뷰 등록", description = "가게에 대한 리뷰를 등록합니다.") @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/reviews") public ResponseEntity> register( @@ -41,12 +46,16 @@ public ResponseEntity> register( return ok(successWithDataOnly(ReviewRegisterResponse.from(review))); } + @Operation(summary = "리뷰 목록 조회", description = "가게 및 작성자 기준으로 검색하거나 키워드로 필터링합니다.") @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @GetMapping("/v1/reviews") public ResponseEntity>> search( @AuthenticationPrincipal Jwt jwt, + @Parameter(description = "스토어 ID", required = true) @RequestParam UUID storeId, + @Parameter(description = "리뷰 작성자 ID", required = true) @RequestParam UUID reviewerId, + @Parameter(description = "리뷰 내용 검색 키워드") @RequestParam String keyword, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { @@ -59,10 +68,12 @@ public ResponseEntity>> search( return ok(successWithDataOnly(responses)); } + @Operation(summary = "리뷰 수정", description = "작성한 리뷰를 수정합니다.") @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PutMapping("/v1/reviews/{reviewId}") public ResponseEntity> update( @AuthenticationPrincipal Jwt jwt, + @Parameter(description = "리뷰 ID", required = true) @PathVariable Long reviewId, @RequestBody @Valid ReviewUpdateRequest updateRequest ) { @@ -73,11 +84,14 @@ public ResponseEntity> update( return ok(successWithDataOnly(ReviewUpdateResponse.from(order))); } + @Operation(summary = "리뷰 삭제", description = "사용자가 작성한 리뷰를 삭제합니다.") @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @DeleteMapping("/v1/reviews/{reviewId}") public ResponseEntity> remove( @AuthenticationPrincipal Jwt jwt, - @PathVariable Long reviewId) { + @Parameter(description = "리뷰 ID", required = true) + @PathVariable Long reviewId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); Review review = reviewApplication.remove(reviewId, requestId); diff --git a/src/main/java/com/sparta/deliveryi/store/presentation/webapi/StoreApi.java b/src/main/java/com/sparta/deliveryi/store/presentation/webapi/StoreApi.java index cdffa4f..46f95a8 100644 --- a/src/main/java/com/sparta/deliveryi/store/presentation/webapi/StoreApi.java +++ b/src/main/java/com/sparta/deliveryi/store/presentation/webapi/StoreApi.java @@ -6,6 +6,10 @@ import com.sparta.deliveryi.store.domain.StoreInfoUpdateRequest; import com.sparta.deliveryi.store.domain.StoreRegisterRequest; import com.sparta.deliveryi.store.domain.service.StoreRegister; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -24,6 +28,7 @@ import static com.sparta.deliveryi.global.presentation.dto.ApiResponse.successWithDataOnly; import static org.springframework.http.ResponseEntity.ok; +@Tag(name = "가게 API", description = "가게 등록, 수정, 삭제, 검색 및 상태 변경 기능 제공") @RestController @RequiredArgsConstructor public class StoreApi { @@ -32,6 +37,11 @@ public class StoreApi { private final StoreApplication storeApplication; + @Operation( + summary = "가게 등록", + description = "신규 가게를 등록합니다." + ) + @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/stores") public ResponseEntity> register(@RequestBody @Valid StoreRegisterRequest registerRequest) { Store store = storeRegister.register(registerRequest); @@ -39,11 +49,17 @@ public ResponseEntity> register(@RequestBody return ok(successWithDataOnly(StoreRegisterResponse.from(store))); } + @Operation( + summary = "가게 검색", + description = "가게를 검색할 수 있습니다. keyword는 가게명, 가게 분류 또는 설명을 기준으로 검색됩니다." + ) @PreAuthorize("hasAnyRole('CUSTOMER', 'OWNER', 'MANAGER', 'MASTER')") @GetMapping("/v1/reviews") public ResponseEntity>> search( @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "ownerId", description = "가게 주인 ID (UUID)", in = ParameterIn.QUERY, example = "550e8400-e29b-41d4-a716-446655440000") @RequestParam UUID ownerId, + @Parameter(name = "keyword", description = "검색 키워드 (가게명, 설명 등)", in = ParameterIn.QUERY, example = "치킨") @RequestParam String keyword, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { @@ -56,9 +72,17 @@ public ResponseEntity>> search( return ok(successWithDataOnly(responses)); } + @Operation( + summary = "가게 정보 수정", + description = "OWNER, MANAGER, MASTER 권한 사용자는 가게 정보를 수정할 수 있습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PutMapping("/v1/stores/{storeId}") - public ResponseEntity> updateInfo(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId, @RequestBody @Valid StoreInfoUpdateRequest updateRequest) { + public ResponseEntity> updateInfo( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId, + @RequestBody @Valid StoreInfoUpdateRequest updateRequest) { UUID requestId = UUID.fromString(jwt.getSubject()); Store store = storeApplication.updateInfo(storeId, updateRequest, requestId); @@ -66,9 +90,17 @@ public ResponseEntity> updateInfo(@Authenti return ok(successWithDataOnly(StoreUpdateInfoResponse.from(store))); } + @Operation( + summary = "가게 삭제", + description = "OWNER, MANAGER, MASTER 권한 사용자는 가게를 삭제할 수 있습니다. (Soft Delete)" + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @DeleteMapping("/v1/stores/{storeId}") - public ResponseEntity> remove(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId) { + public ResponseEntity> remove( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); Store store = storeApplication.remove(storeId, requestId); @@ -76,9 +108,17 @@ public ResponseEntity> remove(@AuthenticationPr return ok(successWithDataOnly(StoreRemoveResponse.from(store))); } + @Operation( + summary = "가게 등록 승인", + description = "MANAGER 또는 MASTER가 가게 등록 요청을 승인합니다." + ) @PreAuthorize("hasAnyRole('MANAGER', 'MASTER')") @PostMapping("/v1/stores/{storeId}/accept") - public ResponseEntity> acceptRegister(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId) { + public ResponseEntity> acceptRegister( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); storeRegister.acceptRegisterRequest(storeId, requestId); @@ -86,17 +126,31 @@ public ResponseEntity> acceptRegister(@AuthenticationPrincipal return ok(success()); } + @Operation( + summary = "가게 등록 거절", + description = "MANAGER 또는 MASTER가 가게 등록 요청을 반려합니다." + ) @PreAuthorize("hasAnyRole('MANAGER', 'MASTER')") @PostMapping("/v1/stores/{storeId}/reject") - public ResponseEntity> rejectRegister(@PathVariable UUID storeId) { + public ResponseEntity> rejectRegister( + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId + ) { storeRegister.rejectRegisterRequest(storeId); return ok(success()); } + @Operation( + summary = "가게 영업 시작", + description = "OWNER, MANAGER, MASTER 권한 사용자가 영업 시작할 수 있습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/stores/{storeId}/open") - public ResponseEntity> open(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId) { + public ResponseEntity> open( + @AuthenticationPrincipal Jwt jwt, + @PathVariable UUID storeId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); storeApplication.open(storeId, requestId); @@ -104,9 +158,17 @@ public ResponseEntity> open(@AuthenticationPrincipal Jwt jwt, return ok(success()); } + @Operation( + summary = "가게 영업 종료", + description = "OWNER, MANAGER, MASTER 권한 사용자가 영업 종료할 수 있습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/stores/{storeId}/close") - public ResponseEntity> close(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId) { + public ResponseEntity> close( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId + ) { UUID requestId = UUID.fromString(jwt.getSubject()); storeApplication.open(storeId, requestId); @@ -114,9 +176,17 @@ public ResponseEntity> close(@AuthenticationPrincipal Jwt jwt, return ok(success()); } + @Operation( + summary = "가게 인수인계", + description = "OWNER, MANAGER, MASTER 권한 사용자가 가게 소유권을 다른 OWNER에게 이전할 수 있습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MANAGER', 'MASTER')") @PostMapping("/v1/stores/{storeId}/transfer") - public ResponseEntity> transfer(@AuthenticationPrincipal Jwt jwt, @PathVariable UUID storeId, @RequestBody @Valid StoreTransferRequest transferRequest) { + public ResponseEntity> transfer( + @AuthenticationPrincipal Jwt jwt, + @Parameter(name = "storeId", description = "가게 ID (UUID)", in = ParameterIn.PATH, required = true) + @PathVariable UUID storeId, + @RequestBody @Valid StoreTransferRequest transferRequest) { UUID requestId = UUID.fromString(jwt.getSubject()); storeApplication.transfer(storeId, transferRequest.newOwnerId(), requestId);