diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java new file mode 100644 index 0000000..95c51b4 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuApi.java @@ -0,0 +1,158 @@ +package com.example.Centralthon.domain.menu.web.controller; + +import com.example.Centralthon.domain.menu.web.dto.MenuDetailsRes; +import com.example.Centralthon.domain.menu.web.dto.MenuIdsReq; +import com.example.Centralthon.domain.menu.web.dto.NearbyMenusRes; +import com.example.Centralthon.domain.menu.web.dto.StoresByMenuRes; +import com.example.Centralthon.global.response.SuccessResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +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.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@Tag(name = "Menus", description = "반찬 관련 API") +public interface MenuApi { + @Operation( + summary = "맞춤 추천용 메뉴 목록 조회", + description ="사용자 위치 기준 2km 이내에 가게에서 판매중인 반찬 매물을 반환합니다.
"+ + "중복을 제외하고 { 반찬명, 카테고리 }를 반환합니다.") + @ApiResponse( + responseCode = "200", + description = "맞춤 추천용 메뉴 목록 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-15 04:22:54", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "name": "진미채 볶음", + "category" : "STIR_FRY" + }, + { + "name": "두부 조림", + "category" : "BRAISED" + } + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> nearbyMenus( + @Parameter(name = "lat", description = "사용자 위도", example = "37.468355", required = true) + @RequestParam("lat") Double lat, + @Parameter(name = "lng", description = "사용자 경도", example = "127.039073", required = true) + @RequestParam("lng") Double lng); + + @Operation( + summary = "특정 메뉴 판매 가게 조회", + description = "반찬명을 기준으로 해당 메뉴를 판매하는 가게를 조회합니다. 공백까지 포함하여 동일 여부를 판단합니다.
"+ + "{ 메뉴Id값, 가게명, 사용자-가게 거리(km), 할인가, 수량}을 반환합니다.") + @ApiResponse( + responseCode = "200", + description = "특정 메뉴 판매 가게 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-15 04:22:54", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "menuId": 15, + "storeName": "초록찬 비건키친", + "distance": 1.7047542239779305, + "salePrice": 4000, + "quantity": 7 + }, + { + "menuId": 1, + "storeName": "우찬이네 반찬", + "distance": 1.0348092111962985, + "salePrice": 4200, + "quantity": 10 + } + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> storesByMenu( + @Parameter(name = "name", description = "메뉴 이름(공백 포함하여 완전일치)", example = "두부 조림", required = true) + @RequestParam("name") String name, + @Parameter(name = "lat", description = "사용자 위도", example = "37.468355", required = true) + @RequestParam("lat") Double lat, + @Parameter(name = "lng", description = "사용자 경도", example = "127.039073", required = true) + @RequestParam("lng") Double lng); + + @Operation( + summary = "메뉴 상세 조회", + description = "사용자가 추가한 각 메뉴들의 상세 정보를 반환합니다.
"+ + "{메뉴Id값, 메뉴 이름, 카테고리, 원가, 할인가, 할인율, 가게명}을 반환합니다." + ) + @ApiResponse( + responseCode = "200", + description = "메뉴 상세 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-15 04:22:54", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "menuId": 1, + "name": "진미채 볶음", + "category" : "STIR_FRY", + "costPrice" : 3000, + "salePrice" : 2400, + "salePercent" : 20, + "storeName" : "우찬이네 밥상" + }, + { + "menuId": 4, + "name": "두부 조림", + "category" : "BRAISED", + "costPrice" : 5000, + "salePrice" : 4000, + "salePercent" : 20, + "storeName" : "희망 식당" + } + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> details(@RequestBody @Valid MenuIdsReq menus); +} diff --git a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java index b442449..3598235 100644 --- a/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java +++ b/src/main/java/com/example/Centralthon/domain/menu/web/controller/MenuController.java @@ -3,6 +3,7 @@ import com.example.Centralthon.domain.menu.service.MenuService; import com.example.Centralthon.domain.menu.web.dto.*; +import com.example.Centralthon.domain.order.web.controller.OrderApi; import com.example.Centralthon.global.response.SuccessResponse; import jakarta.validation.Valid; @@ -21,11 +22,12 @@ @RestController @RequestMapping("/api/menus") @RequiredArgsConstructor -public class MenuController { +public class MenuController implements MenuApi { private final MenuService menuService; //맞춤 추천용 메뉴 목록 조회 @GetMapping("") + @Override public ResponseEntity>> nearbyMenus( @RequestParam("lat") Double lat, @RequestParam("lng") Double lng) { @@ -37,6 +39,7 @@ public ResponseEntity>> nearbyMenus( //특정 메뉴를 판매 하는 가게 조회 @GetMapping("/stores") + @Override public ResponseEntity>> storesByMenu( @RequestParam("name") String name, @RequestParam("lat") Double lat, @@ -49,6 +52,7 @@ public ResponseEntity>> storesByMenu( //메뉴 상세 조회 @PostMapping("/details") + @Override public ResponseEntity>> details(@RequestBody @Valid MenuIdsReq menus){ List menuList = menuService.details(menus); diff --git a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreApi.java b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreApi.java new file mode 100644 index 0000000..92f0098 --- /dev/null +++ b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreApi.java @@ -0,0 +1,123 @@ +package com.example.Centralthon.domain.store.web.controller; + +import com.example.Centralthon.domain.order.web.dto.CompleteOrderReq; +import com.example.Centralthon.domain.order.web.dto.CreateOrderReq; +import com.example.Centralthon.domain.order.web.dto.CreateOrderRes; +import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; +import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; +import com.example.Centralthon.global.response.SuccessResponse; +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.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.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.List; + +@Tag(name = "Stores", description = "가게 관련 API") +public interface StoreApi { + @Operation( + summary = "근처 가게 위치 목록 조회", + description = "사용자 위치 기준 2km 반경 내에 가게 목록을 조회합니다.
가게들의 기본키와 좌표값(위도, 경도)을 반환합니다." + ) + @ApiResponse( + responseCode = "200", + description = "근처 가게 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-14 15:54:04", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": [ + { + "storeId": 1, + "lat": 37.59, + "lng": 127.0164 + }, + { + "storeId": 2, + "lat": 37.577, + "lng": 127.0204 + } + ], + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity>> nearbyStores( + @Parameter(name = "lat", description = "사용자 위도", example = "37.468355", required = true) + @RequestParam("lat") Double lat, + @Parameter(name = "lng", description = "사용자 경도", example = "127.039073", required = true) + @RequestParam("lng") Double lng); + + @Operation( + summary = "가게에서 판매 중인 메뉴 조회", + description ="현재 재고가 있고 마감 기한을 넘지 않은 메뉴들의 목록을 반환합니다. distacne = 사용자와 가게간의 거리(km)
" + + "{ 메뉴 Id값, 이름, 카테고리, 원가, 할인가, 할인율, 수량} 을 반환합니다.") + @ApiResponse( + responseCode = "200", + description = "판매 메뉴 목록 조회 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SuccessResponse.class), + examples = @ExampleObject( + name = "SUCCESS_200", + value = """ + { + "timestamp": "2025-08-15 04:45:14", + "code": "SUCCESS_200", + "httpStatus": 200, + "message": "호출에 성공하였습니다.", + "data": { + "name": "오색퓨전찬", + "category": "FUSION_SIDE_DISH", + "distance": 1.0419556581490452, + "menus": [ + { + "menuId": 4, + "name": "진미채 볶음", + "category": "STIR_FRY", + "costPrice": 5000, + "salePrice": 4500, + "salePercent": 10, + "quantity": 7 + }, + { + "menuId": 6, + "name": "순두부찌개", + "category": "SOUP", + "costPrice": 6200, + "salePrice": 5900, + "salePercent": 5, + "quantity": 4 + } + ] + }, + "isSuccess": true + } + """ + ) + ) + ) + ResponseEntity> getStoreMenus( + @Parameter(name = "storeId", in = ParameterIn.PATH, description = "가게 ID", example = "1", required = true) + @PathVariable Long storeId, + @Parameter(name = "lat", in = ParameterIn.QUERY, description = "사용자 위도", example = "37.468355", required = true) + @RequestParam("lat") Double lat, + @Parameter(name = "lng", in = ParameterIn.QUERY, description = "사용자 경도", example = "127.039073", required = true) + @RequestParam("lng") Double lng); +} diff --git a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java index c5bc02e..6ffff81 100644 --- a/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java +++ b/src/main/java/com/example/Centralthon/domain/store/web/controller/StoreController.java @@ -1,5 +1,6 @@ package com.example.Centralthon.domain.store.web.controller; +import com.example.Centralthon.domain.order.web.controller.OrderApi; import com.example.Centralthon.domain.store.service.StoreService; import com.example.Centralthon.domain.store.web.dto.NearbyStoresRes; import com.example.Centralthon.domain.store.web.dto.StoreMenusRes; @@ -14,11 +15,12 @@ @RestController @RequestMapping("/api/stores") @RequiredArgsConstructor -public class StoreController { +public class StoreController implements StoreApi { private final StoreService storeService; // 근처 가게 위치 목록 조회 @GetMapping("") + @Override public ResponseEntity>> nearbyStores( @RequestParam("lat") Double lat, @RequestParam("lng") Double lng){ @@ -30,6 +32,7 @@ public ResponseEntity>> nearbyStores( // 가게 판매 메뉴 목록 조회 @GetMapping("/{storeId}/menus") + @Override public ResponseEntity> getStoreMenus( @PathVariable Long storeId, @RequestParam("lat") Double lat, diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a57e3d2..a24dd91 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,7 +15,7 @@ spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true # Swagger -springdoc.swagger-ui.path=/swagger-ui.html +springdoc.swagger-ui.path=/api/swagger-ui.html springdoc.api-docs.path=/api-docs springdoc.group-configs[0].group=default springdoc.group-configs[0].paths-to-match=/**