diff --git a/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java b/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java index d425ae70..e70f81d2 100644 --- a/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java +++ b/src/main/java/ssu/eatssu/domain/auth/presentation/OAuthController.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.auth.presentation; -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 io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,6 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.auth.dto.*; +import ssu.eatssu.domain.auth.presentation.docs.OAuthControllerDocs; import ssu.eatssu.domain.auth.service.OAuthService; import ssu.eatssu.domain.user.dto.Tokens; import ssu.eatssu.global.handler.response.BaseResponse; @@ -24,19 +19,12 @@ @RestController @RequestMapping("/oauths") @RequiredArgsConstructor -@Tag(name = "Oauth", description = "Oauth API") -public class OAuthController { +public class OAuthController implements OAuthControllerDocs { private final OAuthService oauthService; // TODO : 로그인 & 회원 가입 마이그레이션 이후에 지울 것. - @Operation(summary = "카카오 회원가입, 로그인 [인증 토큰 필요 X]", description = """ - 카카오 회원가입, 로그인 API 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") - }) + @Override @PostMapping("/kakao") public BaseResponse kakaoLogin(@Valid @RequestBody KakaoLoginRequest request) { long startTime = System.currentTimeMillis(); @@ -48,13 +36,7 @@ public BaseResponse kakaoLogin(@Valid @RequestBody KakaoLoginRequest req return BaseResponse.success(tokens); } - @Operation(summary = "카카오 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """ - 카카오 회원가입, 로그인 V2 API 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") - }) + @Override @PostMapping("/v2/kakao") public BaseResponse kakaoLoginV2(@Valid @RequestBody KakaoLoginRequestV2 request) { long startTime = System.currentTimeMillis(); @@ -67,46 +49,28 @@ public BaseResponse kakaoLoginV2(@Valid @RequestBody KakaoLoginRequestV2 } // TODO : 로그인 & 회원 가입 마이그레이션 이후에 지울 것. - @Operation(summary = "애플 회원가입, 로그인 [인증 토큰 필요 X]", description = """ - 애플 로그인, 회원가입 API 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공") - }) + @Override @PostMapping("/apple") public BaseResponse appleLogin(@Valid @RequestBody AppleLoginRequest request) { Tokens tokens = oauthService.appleLogin(request); return BaseResponse.success(tokens); } - @Operation(summary = "애플 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """ - 애플 로그인, 회원가입 API V2 입니다.

- 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공") - }) + @Override @PostMapping("/v2/apple") public BaseResponse appleLoginV2(@Valid @RequestBody AppleLoginRequestV2 request) { Tokens tokens = oauthService.appleLoginV2(request); return BaseResponse.success(tokens); } - @Operation(summary = "토큰 재발급", description = "accessToken, refreshToken 재발급 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "토큰 재발급 성공") - }) + @Override @PostMapping("/reissue/token") public BaseResponse refreshToken() { Tokens tokens = oauthService.refreshTokens(getLoginUser()); return BaseResponse.success(tokens); } - @Operation(summary = "유효한 토큰 확인 [인증 토큰 필요 X]", description = "해당 토큰이 유효하면 true 반환하는, 유효하지 않은 false 반환하는 API 입니다") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유효한 토큰인지 확인 성공") - }) + @Override @PostMapping("/valid/token") public BaseResponse validToken(@Valid @RequestBody ValidRequest request) { Boolean response = oauthService.validToken(request); diff --git a/src/main/java/ssu/eatssu/domain/auth/presentation/docs/OAuthControllerDocs.java b/src/main/java/ssu/eatssu/domain/auth/presentation/docs/OAuthControllerDocs.java new file mode 100644 index 00000000..213af66a --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/auth/presentation/docs/OAuthControllerDocs.java @@ -0,0 +1,65 @@ +package ssu.eatssu.domain.auth.presentation.docs; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.auth.dto.AppleLoginRequest; +import ssu.eatssu.domain.auth.dto.AppleLoginRequestV2; +import ssu.eatssu.domain.auth.dto.KakaoLoginRequest; +import ssu.eatssu.domain.auth.dto.KakaoLoginRequestV2; +import ssu.eatssu.domain.auth.dto.ValidRequest; +import ssu.eatssu.domain.user.dto.Tokens; +import ssu.eatssu.global.handler.response.BaseResponse; + +@Tag(name = "Oauth", description = "Oauth API") +public interface OAuthControllerDocs { + + @Operation(summary = "카카오 회원가입, 로그인 [인증 토큰 필요 X]", description = """ + 카카오 회원가입, 로그인 API 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") + }) + BaseResponse kakaoLogin(KakaoLoginRequest request); + + @Operation(summary = "카카오 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """ + 카카오 회원가입, 로그인 V2 API 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "카카오 회원가입/로그인 성공") + }) + BaseResponse kakaoLoginV2(KakaoLoginRequestV2 request); + + @Operation(summary = "애플 회원가입, 로그인 [인증 토큰 필요 X]", description = """ + 애플 로그인, 회원가입 API 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공") + }) + BaseResponse appleLogin(AppleLoginRequest request); + + @Operation(summary = "애플 회원가입, 로그인 V2 [인증 토큰 필요 X]", description = """ + 애플 로그인, 회원가입 API V2 입니다.

+ 가입된 회원일 경우 카카오 로그인, 미가입 회원일 경우 회원가입 후 자동 로그인됩니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "애플 회원가입/로그인 성공") + }) + BaseResponse appleLoginV2(AppleLoginRequestV2 request); + + @Operation(summary = "토큰 재발급", description = "accessToken, refreshToken 재발급 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "토큰 재발급 성공") + }) + BaseResponse refreshToken(); + + @Operation(summary = "유효한 토큰 확인 [인증 토큰 필요 X]", description = "해당 토큰이 유효하면 true 반환하는, 유효하지 않은 false 반환하는 API 입니다") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유효한 토큰인지 확인 성공") + }) + BaseResponse validToken(ValidRequest request); +} diff --git a/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java b/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java index 081a050b..99fa6b28 100644 --- a/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java +++ b/src/main/java/ssu/eatssu/domain/inquiry/presentation/InquiryController.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.inquiry.presentation; -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 io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PostMapping; @@ -15,6 +9,7 @@ import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.inquiry.dto.CreateInquiryRequest; import ssu.eatssu.domain.inquiry.entity.Inquiry; +import ssu.eatssu.domain.inquiry.presentation.docs.InquiryControllerDocs; import ssu.eatssu.domain.inquiry.service.InquiryService; import ssu.eatssu.domain.slack.entity.SlackChannel; import ssu.eatssu.domain.slack.entity.SlackMessageFormat; @@ -28,17 +23,12 @@ @RestController @RequiredArgsConstructor @RequestMapping("/inquiries") -@Tag(name = "Inquiry", description = "문의 API") -public class InquiryController { +public class InquiryController implements InquiryControllerDocs { private final SlackService slackService; private final InquiryService inquiryService; - @Operation(summary = "문의 작성", description = "문의를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "문의 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping("/") public BaseResponse writeInquiry(@RequestBody CreateInquiryRequest createInquiryRequest, @AuthenticationPrincipal CustomUserDetails customUserDetails) { diff --git a/src/main/java/ssu/eatssu/domain/inquiry/presentation/docs/InquiryControllerDocs.java b/src/main/java/ssu/eatssu/domain/inquiry/presentation/docs/InquiryControllerDocs.java new file mode 100644 index 00000000..9a81493d --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/inquiry/presentation/docs/InquiryControllerDocs.java @@ -0,0 +1,24 @@ +package ssu.eatssu.domain.inquiry.presentation.docs; + +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 io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.inquiry.dto.CreateInquiryRequest; +import ssu.eatssu.global.handler.response.BaseResponse; + +@Tag(name = "Inquiry", description = "문의 API") +public interface InquiryControllerDocs { + + @Operation(summary = "문의 작성", description = "문의를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "문의 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse writeInquiry(CreateInquiryRequest createInquiryRequest, + CustomUserDetails customUserDetails); +} diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java index d2206241..4b6bcc77 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MealController.java @@ -1,12 +1,5 @@ package ssu.eatssu.domain.menu.presentation.rest; -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.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.DeleteMapping; @@ -22,6 +15,7 @@ import ssu.eatssu.domain.menu.presentation.dto.request.MealCreateWithPriceRequest; import ssu.eatssu.domain.menu.presentation.dto.response.MealDetailResponse; import ssu.eatssu.domain.menu.presentation.dto.response.MenusInMealResponse; +import ssu.eatssu.domain.menu.presentation.rest.docs.MealControllerDocs; import ssu.eatssu.domain.menu.service.MealService; import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.restaurant.entity.RestaurantType; @@ -36,27 +30,16 @@ @RestController @RequiredArgsConstructor @RequestMapping("/meals") -@Tag(name = "Meal", description = "식단 API") -public class MealController { +public class MealController implements MealControllerDocs { private final MealService mealService; - @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ - 식단을 추가하는 API 입니다.

- 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

- 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 추가 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping("") public BaseResponse createMeal( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, + @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @RequestParam("restaurant") Restaurant restaurant, + @RequestParam("time") TimePart timePart, @RequestBody CreateMealRequest mealCreateRequest) { if (RestaurantType.isFixedType(restaurant)) { throw new BaseException(NOT_SUPPORT_RESTAURANT); @@ -66,23 +49,12 @@ public BaseResponse createMeal( return BaseResponse.success(); } - @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ - 식단을 추가하는 API 입니다.

- 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

- 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) - 가격을 외부에서 입력받습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 추가 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping("/with-price") public BaseResponse createMealWithPrice( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart, + @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @RequestParam("restaurant") Restaurant restaurant, + @RequestParam("time") TimePart timePart, @RequestBody MealCreateWithPriceRequest request) { if (RestaurantType.isFixedType(restaurant)) { throw new BaseException(NOT_SUPPORT_RESTAURANT); @@ -92,50 +64,29 @@ public BaseResponse createMealWithPrice( return BaseResponse.success(); } - @Operation(summary = "변동 메뉴 식단 리스트 조회 [인증 토큰 필요 X]", description = """ - 변동 메뉴 식단 리스트를 조회하는 API 입니다. - 변동 메뉴 식당 (학생 식당, 도담, 기숙사 식당) 의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단 목록을 조회합니다. - 일반적으로 학생식당과 도담의 경우 식단 여러 개가 조회되고 기숙사식당은 한 개만 조회됩니다.) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "지원 하지 않는 식당 (고정 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재 하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("") public BaseResponse> getMealDetail( - @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, - @Parameter(description = "식당 이름") @RequestParam("restaurant") Restaurant restaurant, - @Parameter(description = "시간대") @RequestParam("time") TimePart timePart) { + @RequestParam("date") @DateTimeFormat(pattern = "yyyyMMdd") Date date, + @RequestParam("restaurant") Restaurant restaurant, + @RequestParam("time") TimePart timePart) { return BaseResponse.success( mealService.getMealDetailsByDateAndRestaurantAndTimePart(date, restaurant, timePart)); } - @Operation(summary = "식단 삭제 [인증 토큰 필요 X]", description = "식단을 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "식단 삭제 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @DeleteMapping("/{mealId}") public BaseResponse deleteMeal( - @Parameter(description = "mealId") @PathVariable("mealId") Long mealId) { + @PathVariable("mealId") Long mealId) { mealService.deleteByMealId(mealId); return BaseResponse.success(); } - @Operation(summary = "메뉴 정보 리스트 조회 [인증 토큰 필요 X]", description = """ - 메뉴 정보 리스트를 조회하는 API 입니다.

- 식단식별자(mealId)로 해당 식단에 속하는 메뉴 정보 목록을 조회합니다.") - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "메뉴 정보 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/{mealId}/menus-info") - public BaseResponse getMenusInMeal(@Parameter(description = "mealId") - @PathVariable("mealId") Long mealId) { + public BaseResponse getMenusInMeal(@PathVariable("mealId") Long mealId) { return BaseResponse.success(mealService.getMenusInMealByMealId(mealId)); } } diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java index 008cf8ca..4eaa67e6 100644 --- a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/MenuController.java @@ -1,17 +1,12 @@ package ssu.eatssu.domain.menu.presentation.rest; -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 io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ssu.eatssu.domain.menu.presentation.dto.response.MenuRestaurantResponse; +import ssu.eatssu.domain.menu.presentation.rest.docs.MenuControllerDocs; import ssu.eatssu.domain.menu.service.MenuService; import ssu.eatssu.domain.restaurant.entity.Restaurant; import ssu.eatssu.domain.user.entity.Language; @@ -20,23 +15,11 @@ @RestController @RequiredArgsConstructor @RequestMapping("/menus") -@Tag(name = "Menu", description = "메뉴 API") -public class MenuController { +public class MenuController implements MenuControllerDocs { private final MenuService menuService; - @Operation(summary = "고정 메뉴 리스트 조회 [인증 토큰 필요 X]", - description = - """ - 고정 메뉴 리스트를 조회하는 API 입니다. - 메뉴가 고정된 식당(푸드코트, 스낵코너, 더 키친)의 메뉴 리스트를 조회합니다. - """ - ) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "메뉴 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(변동 메뉴 식당)", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping public BaseResponse getMenus( @RequestParam("restaurant") Restaurant restaurant, diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MealControllerDocs.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MealControllerDocs.java new file mode 100644 index 00000000..7d60a1ea --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MealControllerDocs.java @@ -0,0 +1,100 @@ +package ssu.eatssu.domain.menu.presentation.rest.docs; + +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.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.menu.entity.constants.TimePart; +import ssu.eatssu.domain.menu.presentation.dto.request.CreateMealRequest; +import ssu.eatssu.domain.menu.presentation.dto.request.MealCreateWithPriceRequest; +import ssu.eatssu.domain.menu.presentation.dto.response.MealDetailResponse; +import ssu.eatssu.domain.menu.presentation.dto.response.MenusInMealResponse; +import ssu.eatssu.domain.restaurant.entity.Restaurant; +import ssu.eatssu.global.handler.response.BaseResponse; + +import java.util.Date; +import java.util.List; + +@Tag(name = "Meal", description = "식단 API") +public interface MealControllerDocs { + + @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ + 식단을 추가하는 API 입니다.

+ 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

+ 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 추가 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = + @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse createMeal( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) Date date, + @Parameter(description = "식당이름") Restaurant restaurant, + @Parameter(description = "시간대") TimePart timePart, + CreateMealRequest mealCreateRequest); + + @Operation(summary = "식단 추가 [인증 토큰 필요 X]", description = """ + 식단을 추가하는 API 입니다.

+ 변동메뉴 식당(학생식당, 도담, 기숙사 식당)의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단을 추가합니다.

+ 이미 존재하는 식단일 경우 중복저장 되지 않도록 처리합니다.(별도의 ErrorResponse 응답 X) + 가격을 외부에서 입력받습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 추가 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(고정 메뉴 식당)", content = + @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "잘못된 날짜형식", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse createMealWithPrice( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) Date date, + @Parameter(description = "식당이름") Restaurant restaurant, + @Parameter(description = "시간대") TimePart timePart, + MealCreateWithPriceRequest request); + + @Operation(summary = "변동 메뉴 식단 리스트 조회 [인증 토큰 필요 X]", description = """ + 변동 메뉴 식단 리스트를 조회하는 API 입니다. + 변동 메뉴 식당 (학생 식당, 도담, 기숙사 식당) 의 특정날짜(yyyyMMdd), 특정시간대(아침/점심/저녁)에 해당하는 식단 목록을 조회합니다. + 일반적으로 학생식당과 도담의 경우 식단 여러 개가 조회되고 기숙사식당은 한 개만 조회됩니다.) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "지원 하지 않는 식당 (고정 메뉴 식당)", content = + @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재 하지 않는 식당", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMealDetail( + @Parameter(schema = @Schema(type = "string", format = "date", example = "20240101")) Date date, + @Parameter(description = "식당 이름") Restaurant restaurant, + @Parameter(description = "시간대") TimePart timePart); + + @Operation(summary = "식단 삭제 [인증 토큰 필요 X]", description = "식단을 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "식단 삭제 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse deleteMeal(@Parameter(description = "mealId") Long mealId); + + @Operation(summary = "메뉴 정보 리스트 조회 [인증 토큰 필요 X]", description = """ + 메뉴 정보 리스트를 조회하는 API 입니다.

+ 식단식별자(mealId)로 해당 식단에 속하는 메뉴 정보 목록을 조회합니다.") + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메뉴 정보 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMenusInMeal(@Parameter(description = "mealId") Long mealId); +} diff --git a/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MenuControllerDocs.java b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MenuControllerDocs.java new file mode 100644 index 00000000..b23367bc --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/menu/presentation/rest/docs/MenuControllerDocs.java @@ -0,0 +1,32 @@ +package ssu.eatssu.domain.menu.presentation.rest.docs; + +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 io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.menu.presentation.dto.response.MenuRestaurantResponse; +import ssu.eatssu.domain.restaurant.entity.Restaurant; +import ssu.eatssu.domain.user.entity.Language; +import ssu.eatssu.global.handler.response.BaseResponse; + +@Tag(name = "Menu", description = "메뉴 API") +public interface MenuControllerDocs { + + @Operation(summary = "고정 메뉴 리스트 조회 [인증 토큰 필요 X]", + description = + """ + 고정 메뉴 리스트를 조회하는 API 입니다. + 메뉴가 고정된 식당(푸드코트, 스낵코너, 더 키친)의 메뉴 리스트를 조회합니다. + """ + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "메뉴 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 식당(변동 메뉴 식당)", content = + @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식당", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMenus(Restaurant restaurant, Language language); +} diff --git a/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java b/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java index 0e76475e..9e1dfc51 100644 --- a/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java +++ b/src/main/java/ssu/eatssu/domain/partnership/presentation/PartnershipController.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.partnership.presentation; -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 io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -17,6 +11,7 @@ import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.partnership.dto.CreatePartnershipRequest; import ssu.eatssu.domain.partnership.dto.PartnershipResponse; +import ssu.eatssu.domain.partnership.presentation.docs.PartnershipControllerDocs; import ssu.eatssu.domain.partnership.service.PartnershipService; import ssu.eatssu.global.handler.response.BaseResponse; @@ -25,41 +20,24 @@ @RestController @RequestMapping("/partnerships") @RequiredArgsConstructor -@Tag(name = "Partnership", description = "제휴 API") -public class PartnershipController { +public class PartnershipController implements PartnershipControllerDocs { private final PartnershipService partnershipService; - @Operation(summary = "제휴 등록", description = "제휴를 등록하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 등록 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 대학", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping public BaseResponse createPartnership(@RequestBody CreatePartnershipRequest request) { partnershipService.createPartnership(request); return BaseResponse.success(); } - @Operation(summary = "식당별 전체 제휴 조회", description = "식당별 전체 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 조회 성공"), - }) + @Override @GetMapping public BaseResponse> getAllPartnerships(@AuthenticationPrincipal CustomUserDetails userDetails) { return BaseResponse.success(partnershipService.getAllPartnerships(userDetails)); } - @Operation(summary = "제휴 찜 등록하기/취소하기", description = "제휴 찜 등록하기/취소하기(토글) API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "제휴 찜 등록/취소 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 제휴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping("/{partnershipId}/like") public BaseResponse togglePartnershipLike( @PathVariable Long partnershipId, diff --git a/src/main/java/ssu/eatssu/domain/partnership/presentation/docs/PartnershipControllerDocs.java b/src/main/java/ssu/eatssu/domain/partnership/presentation/docs/PartnershipControllerDocs.java new file mode 100644 index 00000000..b2b30e19 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/partnership/presentation/docs/PartnershipControllerDocs.java @@ -0,0 +1,44 @@ +package ssu.eatssu.domain.partnership.presentation.docs; + +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 io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.partnership.dto.CreatePartnershipRequest; +import ssu.eatssu.domain.partnership.dto.PartnershipResponse; +import ssu.eatssu.global.handler.response.BaseResponse; + +import java.util.List; + +@Tag(name = "Partnership", description = "제휴 API") +public interface PartnershipControllerDocs { + + @Operation(summary = "제휴 등록", description = "제휴를 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 등록 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 대학", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse createPartnership(CreatePartnershipRequest request); + + @Operation(summary = "식당별 전체 제휴 조회", description = "식당별 전체 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 조회 성공"), + }) + BaseResponse> getAllPartnerships(CustomUserDetails userDetails); + + @Operation(summary = "제휴 찜 등록하기/취소하기", description = "제휴 찜 등록하기/취소하기(토글) API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "제휴 찜 등록/취소 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 제휴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse togglePartnershipLike(Long partnershipId, CustomUserDetails userDetails); +} diff --git a/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java b/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java index e760a02c..1e3499e9 100644 --- a/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java +++ b/src/main/java/ssu/eatssu/domain/report/presentation/ReportController.java @@ -1,11 +1,5 @@ package ssu.eatssu.domain.report.presentation; -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 io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -16,6 +10,7 @@ import ssu.eatssu.domain.auth.security.CustomUserDetails; import ssu.eatssu.domain.report.dto.ReportCreateRequest; import ssu.eatssu.domain.report.dto.ReportTypeList; +import ssu.eatssu.domain.report.presentation.docs.ReportControllerDocs; import ssu.eatssu.domain.report.service.ReportService; import ssu.eatssu.domain.review.entity.Report; import ssu.eatssu.domain.slack.entity.SlackChannel; @@ -26,13 +21,12 @@ @RestController @RequiredArgsConstructor @RequestMapping("/reports") -@Tag(name = "Report", description = "신고 API") -public class ReportController { +public class ReportController implements ReportControllerDocs { private final ReportService reportService; private final SlackService slackService; - @Operation(summary = "리뷰 신고 사유 종류 조회", description = "리뷰 신고 사유 종류를 조회하는 API 입니다.") + @Override @GetMapping("/types") public BaseResponse getReportType() { ReportTypeList reportTypeList = reportService.getReportType(); @@ -42,13 +36,8 @@ public BaseResponse getReportType() { /** * 리뷰 신고 */ - @Operation(summary = "리뷰 신고하기", description = "리뷰를 신고하는 API 입니다.") + @Override @PostMapping("") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 신고 성공", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) public BaseResponse reportReview(@RequestBody ReportCreateRequest createReportRequest, @AuthenticationPrincipal CustomUserDetails customUserDetails) { Report report = reportService.reportReview(customUserDetails, createReportRequest); diff --git a/src/main/java/ssu/eatssu/domain/report/presentation/docs/ReportControllerDocs.java b/src/main/java/ssu/eatssu/domain/report/presentation/docs/ReportControllerDocs.java new file mode 100644 index 00000000..b8c9b205 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/report/presentation/docs/ReportControllerDocs.java @@ -0,0 +1,30 @@ +package ssu.eatssu.domain.report.presentation.docs; + +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 io.swagger.v3.oas.annotations.tags.Tag; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.report.dto.ReportCreateRequest; +import ssu.eatssu.domain.report.dto.ReportTypeList; +import ssu.eatssu.global.handler.response.BaseResponse; + +@Tag(name = "Report", description = "신고 API") +public interface ReportControllerDocs { + + @Operation(summary = "리뷰 신고 사유 종류 조회", description = "리뷰 신고 사유 종류를 조회하는 API 입니다.") + BaseResponse getReportType(); + + @Operation(summary = "리뷰 신고하기", description = "리뷰를 신고하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 신고 성공", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse reportReview(ReportCreateRequest createReportRequest, CustomUserDetails customUserDetails); +} diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java index 35439833..3d208384 100644 --- a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java +++ b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewController.java @@ -1,16 +1,6 @@ package ssu.eatssu.domain.review.presentation; -import io.swagger.v3.oas.annotations.Hidden; -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.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -37,6 +27,7 @@ import ssu.eatssu.domain.review.dto.ReviewUpdateRequest; import ssu.eatssu.domain.review.dto.SavedReviewImage; import ssu.eatssu.domain.review.dto.UploadReviewRequest; +import ssu.eatssu.domain.review.presentation.docs.ReviewControllerDocs; import ssu.eatssu.domain.review.service.ReviewService; import ssu.eatssu.domain.slice.dto.SliceResponse; import ssu.eatssu.domain.slice.service.SliceService; @@ -47,33 +38,19 @@ @RestController @RequiredArgsConstructor @RequestMapping("/reviews") -@Tag(name = "Review", description = "리뷰 API") -public class ReviewController { +public class ReviewController implements ReviewControllerDocs { private final ReviewService reviewService; private final SliceService sliceService; - @Operation(summary = "리뷰 리스트 조회 [인증 토큰 필수 X]", description = """ - 리뷰 리스트를 조회하는 API 입니다.

- menuType=FIX 의 경우 menuId 파라미터를 넣어주세요.

- menuType=CHANGE 의 경우 mealId 파라미터를 넣어주세요.

- 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

- 페이징 기본 값 = {size=20, sort=date, direction=desc}

- """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("") public BaseResponse> getReviews( - @Parameter(description = "타입(변동메뉴(식단)/고정메뉴)") @RequestParam("menuType") MenuType menuType, - @Parameter(description = "menuId(고정메뉴)") @RequestParam(value = "menuId", required = false) Long menuId, - @Parameter(description = "mealId(변동메뉴)") @RequestParam(value = "mealId", required = false) Long mealId, - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) + @RequestParam("menuType") MenuType menuType, + @RequestParam(value = "menuId", required = false) Long menuId, + @RequestParam(value = "mealId", required = false) Long mealId, @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal CustomUserDetails customUserDetails) { SliceResponse myReviews = sliceService.findReviewsV1(menuType, menuId, mealId, pageable, lastReviewId, customUserDetails); @@ -81,23 +58,12 @@ public BaseResponse> getReviews( return BaseResponse.success(myReviews); } - @Hidden - @Operation(summary = "리뷰 작성", description = """ - 리뷰를 작성하는 API 입니다.

- reviewCreate는 application/json, multipartFileList는 multipart/form-data로 요청해주세요.

- 사진은 여러장 첨부 가능합니다.(기획상으로는 한 장만 첨부하도록 제한이 있지만 API 스펙 자체는 여러 장 첨부 가능) - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping(value = "/{menuId}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}, produces = MediaType.APPLICATION_JSON_VALUE) public BaseResponse writeReview( - @Parameter(description = "menuId") @PathVariable("menuId") Long menuId, + @PathVariable("menuId") Long menuId, @RequestPart ReviewCreateRequest createReviewRequest, @RequestPart(value = "multipartFileList", required = false) List multipartFileList, @AuthenticationPrincipal CustomUserDetails customUserDetails) { @@ -109,11 +75,7 @@ public BaseResponse writeReview( /** * 리뷰 이미지 업로드 */ - @Operation(summary = "리뷰 이미지 업로드", description = "리뷰 이미지를 업로드하고 이미지 URL 을 반환합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "이미지 업로드 및 URL 반환 성공"), - @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping(value = "/upload/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @@ -121,86 +83,43 @@ public BaseResponse uploadReviewImage(@RequestPart(value = "im return BaseResponse.success(reviewService.uploadImage(image)); } - @Operation(summary = "리뷰 작성", description = "리뷰를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping("/write/{menuId}") - public BaseResponse writeReview(@Parameter(description = "menuId") @PathVariable("menuId") Long menuId, + public BaseResponse writeReview(@PathVariable("menuId") Long menuId, @RequestBody UploadReviewRequest uploadReviewRequest, @AuthenticationPrincipal CustomUserDetails customUserDetails) { reviewService.uploadReview(customUserDetails, menuId, uploadReviewRequest); return BaseResponse.success(); } - @Operation(summary = "리뷰 수정(글 수정)", description = """ - 리뷰 내용을 수정하는 API 입니다.

- 글 수정만 가능하며 사진 수정은 지원하지 않습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PatchMapping("/{reviewId}") - public BaseResponse updateReview(@Parameter(description = "reviewId") - @PathVariable("reviewId") Long reviewId, + public BaseResponse updateReview(@PathVariable("reviewId") Long reviewId, @RequestBody ReviewUpdateRequest request, @AuthenticationPrincipal CustomUserDetails customUserDetails) { reviewService.updateReview(customUserDetails, reviewId, request); return BaseResponse.success(); } - @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @DeleteMapping("/{reviewId}") public BaseResponse deleteReview( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, + @PathVariable("reviewId") Long reviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { reviewService.deleteReview(customUserDetails, reviewId); return BaseResponse.success(); } - @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 식단 리뷰 정보를 조회하는 API 입니다.

- 메뉴명 리스트, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/meals/{mealId}") public BaseResponse getMealReviews( - @Parameter(description = "mealId") @PathVariable(value = "mealId") Long mealId) { return BaseResponse.success(reviewService.findMealReviews(mealId)); } - @Operation(summary = "고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

- 메뉴명, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/menus/{menuId}") public BaseResponse getMainReviews( - @Parameter(description = "menuId") @PathVariable(value = "menuId") Long menuId) { return BaseResponse.success(reviewService.findMenuReviews(menuId)); } diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java index c19382e2..70ad0783 100644 --- a/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java +++ b/src/main/java/ssu/eatssu/domain/review/presentation/ReviewControllerV2.java @@ -1,16 +1,7 @@ package ssu.eatssu.domain.review.presentation; -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.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -37,6 +28,7 @@ import ssu.eatssu.domain.review.dto.ReviewTranslationResponse; import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; import ssu.eatssu.domain.review.dto.ValidMenuForViewResponse; +import ssu.eatssu.domain.review.presentation.docs.ReviewControllerV2Docs; import ssu.eatssu.domain.review.service.ReviewServiceV2; import ssu.eatssu.domain.review.service.ReviewTranslationService; import ssu.eatssu.domain.slice.dto.SliceResponse; @@ -47,18 +39,11 @@ @RestController @RequiredArgsConstructor @RequestMapping("/v2/reviews") -@Tag(name = "Review V2", description = "리뷰 V2 API") -public class ReviewControllerV2 { +public class ReviewControllerV2 implements ReviewControllerV2Docs { private final ReviewServiceV2 reviewServiceV2; private final ReviewTranslationService reviewTranslationService; - @Operation(summary = "meal(식단)에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping("/meal") public BaseResponse createMealReview( @Valid @RequestBody CreateMealReviewRequest createMealReviewRequest, @@ -67,37 +52,18 @@ public BaseResponse createMealReview( return BaseResponse.success(); } - @Operation(summary = "특정 식당 모든 리뷰 정보 조회(리뷰 개수, 평점 등등)", description = """ - 특정 식당 모든 리뷰 정보를 조회하는 API 입니다.

- 리뷰 개수, 별점 별 개수, 리뷰 평점, 좋아요 개수, 싫어요 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "path parameter 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/statistics/restaurant") public BaseResponse getRestaurantReviews( - @Parameter(description = "restaurant") @RequestParam Restaurant restaurant ) { return BaseResponse.success(reviewServiceV2.findRestaurantReviews(restaurant)); } - @Operation(summary = "meal(식단)에 대한 리뷰 리스트 조회", description = """ - 리뷰 리스트를 조회하는 API 입니다.

- 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

- 페이징 기본 값 = {size=20, sort=date, direction=desc}

- """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/list/meal") public BaseResponse> getMealReviewList( - @Parameter(description = "mealId") @RequestParam Long mealId, - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { Pageable pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "id")); @@ -106,113 +72,52 @@ public BaseResponse> getMealReviewList( return BaseResponse.success(myReviews); } - @Operation(summary = "리뷰 수정(글 수정)", description = """ - 리뷰 내용을 수정하는 API 입니다.

- 글 수정만 가능하며 사진 수정은 지원하지 않습니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @PatchMapping("/{reviewId}") - public BaseResponse updateReview(@Parameter(description = "reviewId") - @PathVariable("reviewId") Long reviewId, + public BaseResponse updateReview(@PathVariable("reviewId") Long reviewId, @RequestBody UpdateMealReviewRequest request, @AuthenticationPrincipal CustomUserDetails customUserDetails) { reviewServiceV2.updateReview(customUserDetails, reviewId, request); return BaseResponse.success(); } - @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), - @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @DeleteMapping("/{reviewId}") public BaseResponse deleteReview( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, + @PathVariable("reviewId") Long reviewId, @AuthenticationPrincipal CustomUserDetails customUserDetails) { reviewServiceV2.deleteReview(customUserDetails, reviewId); return BaseResponse.success(); } - @Operation(summary = "리뷰 번역", description = """ - 리뷰 내용을 DeepL로 번역하는 API 입니다.

- 같은 리뷰/언어 조합은 캐시된 결과를 반환합니다.

- 현재는 language=EN만 지원합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 번역 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 언어이거나 번역할 내용이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "429", description = "번역 API 사용량 한도 초과", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "503", description = "번역 시간 초과 또는 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PostMapping("/{reviewId}/translate") public BaseResponse translateReview( - @Parameter(description = "reviewId") @PathVariable("reviewId") Long reviewId, - @Parameter(description = "번역 대상 언어(현재 EN만 지원)") @RequestParam Language language) { + @PathVariable("reviewId") Long reviewId, + @RequestParam Language language) { return BaseResponse.success(reviewTranslationService.translateReview(reviewId, language)); } - @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 식단 리뷰 정보를 조회하는 API 입니다.

- 메뉴명 리스트, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/statistics/meals/{mealId}") public BaseResponse getMealReviews( - @Parameter(description = "mealId") @PathVariable(value = "mealId") Long mealId) { return BaseResponse.success(reviewServiceV2.findMealReviews(mealId)); } - @Operation(summary = "고정 메뉴 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ - 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

- 메뉴명, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = - @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/statistics/menus/{menuId}") public BaseResponse getMainReviews( - @Parameter(description = "menuId") @PathVariable(value = "menuId") Long menuId) { return BaseResponse.success(reviewServiceV2.findMenuReviews(menuId)); } - @Operation(summary = "menu 에 대한 리뷰 리스트 조회", description = """ - 리뷰 리스트를 조회하는 API 입니다.

- 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

- 페이징 기본 값 = {size=20, sort=date, direction=desc}

- """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/list/menu") public BaseResponse> getMenuReviewList( - @Parameter(description = "menuId(고정메뉴)") @RequestParam(value = "menuId") Long menuId, - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) + @RequestParam(value = "menuId") Long menuId, @RequestParam(value = "lastReviewId", required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal CustomUserDetails customUserDetails) { SliceResponse myReviews = reviewServiceV2.findMenuReviewList(menuId, pageable, @@ -222,12 +127,7 @@ public BaseResponse> getMenuReviewList( return BaseResponse.success(myReviews); } - @Operation(summary = "menu에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping("/menu") public BaseResponse createMenuReview(@Valid @RequestBody CreateMenuReviewRequestV2 createMenuReviewRequestV2, @AuthenticationPrincipal CustomUserDetails customUserDetails) { @@ -235,15 +135,11 @@ public BaseResponse createMenuReview(@Valid @RequestBody CreateMenuReviewRequ return BaseResponse.success(); } - @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/my") public BaseResponse> getMyReviews( - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @RequestParam(required = false) Long lastReviewId, + @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal CustomUserDetails customUserDetails) { SliceResponse myReviews = reviewServiceV2.findMyReviews(customUserDetails, lastReviewId, @@ -251,14 +147,9 @@ public BaseResponse> getMyReviews( return BaseResponse.success(myReviews); } - @Operation(summary = "식단 id를 통해 리뷰 작성할 수 있는 메뉴들 조회", description = "리뷰 작성할 수 있는 메뉴들 조회하는 API입니다. (노션 문서 > 리뷰v2 기능명세서> 리뷰에 제외되는 메뉴 참고") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "리뷰 작성할 수 있는 메뉴들 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/meal/valid-for-review/{mealId}") public BaseResponse getValidMenuForReview( - @Parameter(description = "mealId") @PathVariable("mealId") Long mealId) { ValidMenuForViewResponse validMenuForViewResponse = reviewServiceV2.validMenuForReview(mealId); return BaseResponse.success(validMenuForViewResponse); diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerDocs.java b/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerDocs.java new file mode 100644 index 00000000..51b3ee02 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerDocs.java @@ -0,0 +1,137 @@ +package ssu.eatssu.domain.review.presentation.docs; + +import io.swagger.v3.oas.annotations.Hidden; +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.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.web.multipart.MultipartFile; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.menu.entity.constants.MenuType; +import ssu.eatssu.domain.review.dto.MealReviewsResponse; +import ssu.eatssu.domain.review.dto.MenuReviewResponse; +import ssu.eatssu.domain.review.dto.ReviewCreateRequest; +import ssu.eatssu.domain.review.dto.ReviewDetailV1; +import ssu.eatssu.domain.review.dto.ReviewUpdateRequest; +import ssu.eatssu.domain.review.dto.SavedReviewImage; +import ssu.eatssu.domain.review.dto.UploadReviewRequest; +import ssu.eatssu.domain.slice.dto.SliceResponse; +import ssu.eatssu.global.handler.response.BaseResponse; + +import java.util.List; + +@Tag(name = "Review", description = "리뷰 API") +public interface ReviewControllerDocs { + + @Operation(summary = "리뷰 리스트 조회 [인증 토큰 필수 X]", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ menuType=FIX 의 경우 menuId 파라미터를 넣어주세요.

+ menuType=CHANGE 의 경우 mealId 파라미터를 넣어주세요.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getReviews( + @Parameter(description = "타입(변동메뉴(식단)/고정메뉴)") MenuType menuType, + @Parameter(description = "menuId(고정메뉴)") Long menuId, + @Parameter(description = "mealId(변동메뉴)") Long mealId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + @ParameterObject Pageable pageable, + CustomUserDetails customUserDetails); + + @Hidden + @Operation(summary = "리뷰 작성", description = """ + 리뷰를 작성하는 API 입니다.

+ reviewCreate는 application/json, multipartFileList는 multipart/form-data로 요청해주세요.

+ 사진은 여러장 첨부 가능합니다.(기획상으로는 한 장만 첨부하도록 제한이 있지만 API 스펙 자체는 여러 장 첨부 가능) + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse writeReview( + @Parameter(description = "menuId") Long menuId, + ReviewCreateRequest createReviewRequest, + List multipartFileList, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 이미지 업로드", description = "리뷰 이미지를 업로드하고 이미지 URL 을 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "이미지 업로드 및 URL 반환 성공"), + @ApiResponse(responseCode = "500", description = "이미지 업로드 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse uploadReviewImage(MultipartFile image); + + @Operation(summary = "리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + BaseResponse writeReview(@Parameter(description = "menuId") Long menuId, + UploadReviewRequest uploadReviewRequest, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 수정(글 수정)", description = """ + 리뷰 내용을 수정하는 API 입니다.

+ 글 수정만 가능하며 사진 수정은 지원하지 않습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse updateReview(@Parameter(description = "reviewId") Long reviewId, + ReviewUpdateRequest request, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse deleteReview(@Parameter(description = "reviewId") Long reviewId, + CustomUserDetails customUserDetails); + + @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 식단 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명 리스트, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMealReviews(@Parameter(description = "mealId") Long mealId); + + @Operation(summary = "고정 메뉴 리뷰 정보 조회(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명, 리뷰 수, 메인 평점, 양 평점, 맛 평점, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMainReviews(@Parameter(description = "menuId") Long menuId); +} diff --git a/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerV2Docs.java b/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerV2Docs.java new file mode 100644 index 00000000..01a2d1e9 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/review/presentation/docs/ReviewControllerV2Docs.java @@ -0,0 +1,178 @@ +package ssu.eatssu.domain.review.presentation.docs; + +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.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.restaurant.entity.Restaurant; +import ssu.eatssu.domain.review.dto.CreateMealReviewRequest; +import ssu.eatssu.domain.review.dto.CreateMenuReviewRequestV2; +import ssu.eatssu.domain.review.dto.MealReviewResponse; +import ssu.eatssu.domain.review.dto.MealReviewsV2Response; +import ssu.eatssu.domain.review.dto.MenuReviewsV2Response; +import ssu.eatssu.domain.review.dto.RestaurantReviewResponse; +import ssu.eatssu.domain.review.dto.ReviewDetail; +import ssu.eatssu.domain.review.dto.ReviewTranslationResponse; +import ssu.eatssu.domain.review.dto.UpdateMealReviewRequest; +import ssu.eatssu.domain.review.dto.ValidMenuForViewResponse; +import ssu.eatssu.domain.slice.dto.SliceResponse; +import ssu.eatssu.domain.user.dto.MyMealReviewResponse; +import ssu.eatssu.domain.user.entity.Language; +import ssu.eatssu.global.handler.response.BaseResponse; + +@Tag(name = "Review V2", description = "리뷰 V2 API") +public interface ReviewControllerV2Docs { + + @Operation(summary = "meal(식단)에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + BaseResponse createMealReview(CreateMealReviewRequest createMealReviewRequest, + CustomUserDetails customUserDetails); + + @Operation(summary = "특정 식당 모든 리뷰 정보 조회(리뷰 개수, 평점 등등)", description = """ + 특정 식당 모든 리뷰 정보를 조회하는 API 입니다.

+ 리뷰 개수, 별점 별 개수, 리뷰 평점, 좋아요 개수, 싫어요 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "path parameter 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getRestaurantReviews( + @Parameter(description = "restaurant") Restaurant restaurant); + + @Operation(summary = "meal(식단)에 대한 리뷰 리스트 조회", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMealReviewList( + @Parameter(description = "mealId") Long mealId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 수정(글 수정)", description = """ + 리뷰 내용을 수정하는 API 입니다.

+ 글 수정만 가능하며 사진 수정은 지원하지 않습니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 수정 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse updateReview(@Parameter(description = "reviewId") Long reviewId, + UpdateMealReviewRequest request, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 삭제", description = "리뷰를 삭제하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 삭제 성공"), + @ApiResponse(responseCode = "403", description = "리뷰에 대한 권한이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse deleteReview(@Parameter(description = "reviewId") Long reviewId, + CustomUserDetails customUserDetails); + + @Operation(summary = "리뷰 번역", description = """ + 리뷰 내용을 DeepL로 번역하는 API 입니다.

+ 같은 리뷰/언어 조합은 캐시된 결과를 반환합니다.

+ 현재는 language=EN만 지원합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 번역 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 언어이거나 번역할 내용이 없음", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 리뷰", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "429", description = "번역 API 사용량 한도 초과", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "503", description = "번역 시간 초과 또는 실패", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse translateReview( + @Parameter(description = "reviewId") Long reviewId, + @Parameter(description = "번역 대상 언어(현재 EN만 지원)") Language language); + + @Operation(summary = "식단(변동 메뉴) 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 식단 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명 리스트, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 식단", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMealReviews(@Parameter(description = "mealId") Long mealId); + + @Operation(summary = "고정 메뉴 리뷰 정보 조회 V2(메뉴명, 평점 등등) [인증 토큰 필요 X]", description = """ + 고정 메뉴 리뷰 정보를 조회하는 API 입니다.

+ 메뉴명, 리뷰 수, 메인 평점, 좋아요 개수, 싫어요 개수, 각 평점의 개수를 조회합니다. + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 정보 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = + @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMainReviews(@Parameter(description = "menuId") Long menuId); + + @Operation(summary = "menu 에 대한 리뷰 리스트 조회", description = """ + 리뷰 리스트를 조회하는 API 입니다.

+ 커서 기반 페이지네이션으로 리뷰 리스트를 조회합니다.

+ 페이징 기본 값 = {size=20, sort=date, direction=desc}

+ """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "400", description = "쿼리 파라미터 누락", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMenuReviewList( + @Parameter(description = "menuId(고정메뉴)") Long menuId, + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + @ParameterObject Pageable pageable, + CustomUserDetails customUserDetails); + + @Operation(summary = "menu에 대한 리뷰 작성", description = "리뷰를 작성하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + }) + BaseResponse createMenuReview(CreateMenuReviewRequestV2 createMenuReviewRequestV2, + CustomUserDetails customUserDetails); + + @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMyReviews( + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + @ParameterObject Pageable pageable, + CustomUserDetails customUserDetails); + + @Operation(summary = "식단 id를 통해 리뷰 작성할 수 있는 메뉴들 조회", description = "리뷰 작성할 수 있는 메뉴들 조회하는 API입니다. (노션 문서 > 리뷰v2 기능명세서> 리뷰에 제외되는 메뉴 참고") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "리뷰 작성할 수 있는 메뉴들 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 메뉴", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getValidMenuForReview(@Parameter(description = "mealId") Long mealId); +} diff --git a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java index 9b7fe845..4ad35db7 100644 --- a/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java +++ b/src/main/java/ssu/eatssu/domain/user/presentation/UserController.java @@ -1,16 +1,7 @@ package ssu.eatssu.domain.user.presentation; -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.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; @@ -40,6 +31,7 @@ import ssu.eatssu.domain.user.dto.MyReviewDetail; import ssu.eatssu.domain.user.dto.NicknameUpdateRequest; import ssu.eatssu.domain.user.dto.UpdateDepartmentRequest; +import ssu.eatssu.domain.user.presentation.docs.UserControllerDocs; import ssu.eatssu.domain.user.service.UserService; import ssu.eatssu.global.handler.response.BaseResponse; @@ -48,66 +40,27 @@ @RestController @RequiredArgsConstructor @RequestMapping("/users") -@Tag(name = "User", description = "유저 API") -public class UserController { +public class UserController implements UserControllerDocs { private final UserService userService; private final SliceService sliceService; private final PartnershipService partnershipService; private final ReviewServiceV2 reviewServiceV2; - @Operation(summary = "이메일 중복 체크", description = """ - 이메일 중복 체크 API 입니다.

- 중복되지 않은 이메일이면 true 를 반환합니다 - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "중복되지 않은 이메일") - }) + @Override @PostMapping("/validate/email/{email}") //todo: 중복인 경우 error throw, 중복 아니면 ApiReposne return public BaseResponse validateDuplicatedEmail( - @Parameter(description = "이메일") @PathVariable String email) { + @PathVariable String email) { return BaseResponse.success(userService.validateDuplicatedEmail(email)); } - @Operation(summary = "닉네임 중복 및 유효성 체크", description = """ - 닉네임 중복 및 유효성 체크 API 입니다.

- 유효하고 중복되지 않은 닉네임이면 true 를 반환합니다 - """) - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유효하고 중복되지 않은 닉네임"), - @ApiResponse(responseCode = "400", description = "숫자로만 이루어진 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "연속된 공백을 포함하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "연속된 하이폰을 사용하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "첫 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "마지막 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "한글,영문,숫자,공백,하이폰(-)이외의 문자를 쓴 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "닉네임이 1자 이상 16이하가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "서비스명/브랜드명 단독 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "관리자로 혼동될 수 있는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "욕설/비속어가 이름에 포함되는 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/validate/nickname") - public BaseResponse validateNickname(@Parameter(description = "닉네임") - @RequestParam(value = "nickname") String nickname) { + public BaseResponse validateNickname(@RequestParam(value = "nickname") String nickname) { return BaseResponse.success(userService.validateNickname(nickname)); } - @Operation(summary = "닉네임 수정", description = "닉네임 수정 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "닉네임 수정 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "숫자로만 이루어진 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "연속된 공백을 포함하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "연속된 하이폰을 사용하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "첫 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "마지막 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "한글,영문,숫자,공백,하이폰(-)이외의 문자를 쓴 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "닉네임이 1자 이상 16이하가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "서비스명/브랜드명 단독 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "관리자로 혼동될 수 있는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "욕설/비속어가 이름에 포함되는 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "409", description = "중복된 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PatchMapping("/nickname") public BaseResponse updateNickname( @Valid @RequestBody NicknameUpdateRequest updateNicknameRequest, @@ -116,12 +69,7 @@ public BaseResponse updateNickname( return BaseResponse.success(); } - @Operation(summary = "언어 설정 수정", description = "유저의 언어 설정을 수정하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "언어 설정 수정 성공"), - @ApiResponse(responseCode = "400", description = "지원하지 않는 언어 또는 누락된 언어 설정", content = @Content(schema = @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @PatchMapping("/language") public BaseResponse updateLanguage( @Valid @RequestBody LanguageUpdateRequest request, @@ -130,36 +78,24 @@ public BaseResponse updateLanguage( return BaseResponse.success(); } - @Operation(summary = "언어 설정 조회", description = "유저의 언어 설정을 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "언어 설정 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/language") public BaseResponse getLanguage( @AuthenticationPrincipal CustomUserDetails userDetails) { return BaseResponse.success(userService.findLanguage(userDetails)); } - @Operation(summary = "유저 탈퇴", description = "유저 탈퇴 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저 탈퇴 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @DeleteMapping("") public BaseResponse withdraw(@AuthenticationPrincipal CustomUserDetails userDetails) { return BaseResponse.success(userService.withdraw(userDetails)); } - @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/reviews") public BaseResponse> getMyReviewList( - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @RequestParam(required = false) Long lastReviewId, + @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal CustomUserDetails customUserDetails) { SliceResponse myReviews = sliceService.findMyReviews(customUserDetails, pageable, @@ -168,37 +104,21 @@ public BaseResponse> getMyReviewList( } - @Operation(summary = "마이페이지 정보 조회", description = "마이페이지 정보를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "마이페이지 정보 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/mypage") public BaseResponse getMyPage( @AuthenticationPrincipal CustomUserDetails customUserDetails) { return BaseResponse.success(userService.findMyPage(customUserDetails)); } - @Operation(summary = "유저가 찜한 제휴 조회", description = "유저가 찜한 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저가 찜한 제휴 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) + @Override @GetMapping("/partnerships") public BaseResponse> getUserLikedPartnerships( @AuthenticationPrincipal CustomUserDetails userDetails) { return BaseResponse.success(partnershipService.getUserLikedPartnerships(userDetails)); } - @Operation(summary = "유저의 학과 등록", description = "유저의 학과를 등록하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 학과 등록 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) + @Override @PostMapping("/department") public BaseResponse registerDepartment(@RequestBody UpdateDepartmentRequest request, @AuthenticationPrincipal CustomUserDetails userDetails) { @@ -206,14 +126,7 @@ public BaseResponse registerDepartment(@RequestBody UpdateDepartmentRequest r return BaseResponse.success(); } - @Operation(summary = "유저의 단과대/학과 제휴 조회", description = "유저의 단과대/학과 제휴를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 단과대/학과 제휴 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) + @Override @GetMapping("/department/partnerships") public BaseResponse> getUserDepartmentPartnerships( @AuthenticationPrincipal CustomUserDetails userDetails) { @@ -221,28 +134,17 @@ public BaseResponse> getUserDepartmentPartnerships( } - @Operation(summary = "유저의 학과 조회", description = "유저의 학과를 조회하는 API 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "유저의 학과 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = - @Schema(implementation = BaseResponse.class))), - }) + @Override @GetMapping("/department") public BaseResponse getDepartment(@AuthenticationPrincipal CustomUserDetails userDetails) { return BaseResponse.success(userService.getDepartment(userDetails)); } - @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/v2/reviews") public BaseResponse> getMyReviews( - @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) @RequestParam(required = false) Long lastReviewId, - @ParameterObject @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, + @RequestParam(required = false) Long lastReviewId, + @PageableDefault(size = 20, sort = "date", direction = Sort.Direction.DESC) Pageable pageable, @AuthenticationPrincipal CustomUserDetails customUserDetails) { SliceResponse myReviews = reviewServiceV2.findMyReviews(customUserDetails, lastReviewId, @@ -250,11 +152,7 @@ public BaseResponse> getMyReviews( return BaseResponse.success(myReviews); } - @Operation(summary = "단과대 조회", description = "숭실대학교 단과대학 들을 조회하는 API입니다.(토큰 불필요)") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"), - @ApiResponse(responseCode = "404", description = "존재하지 않는 단과대", content = @Content(schema = @Schema(implementation = BaseResponse.class))) - }) + @Override @GetMapping("/lookup/colleges") public BaseResponse> getColleges( @AuthenticationPrincipal CustomUserDetails userDetails) { @@ -262,10 +160,7 @@ public BaseResponse> getColleges( return BaseResponse.success(getCollegeResponses); } - @Operation(summary = "단과대에 따른 학과 조회", description = "단과대학을 입력하면 단과대에 속한 숭실대학교 학과를 조회하는 API입니다.(토큰 불필요)") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"), - }) + @Override @GetMapping("/lookup/departments") public BaseResponse> getDepartments(@RequestParam Long collegeId, @AuthenticationPrincipal CustomUserDetails userDetails) { diff --git a/src/main/java/ssu/eatssu/domain/user/presentation/docs/UserControllerDocs.java b/src/main/java/ssu/eatssu/domain/user/presentation/docs/UserControllerDocs.java new file mode 100644 index 00000000..06bfcd62 --- /dev/null +++ b/src/main/java/ssu/eatssu/domain/user/presentation/docs/UserControllerDocs.java @@ -0,0 +1,178 @@ +package ssu.eatssu.domain.user.presentation.docs; + +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.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import ssu.eatssu.domain.auth.security.CustomUserDetails; +import ssu.eatssu.domain.partnership.dto.PartnershipResponse; +import ssu.eatssu.domain.slice.dto.SliceResponse; +import ssu.eatssu.domain.user.dto.DepartmentResponse; +import ssu.eatssu.domain.user.dto.GetCollegeResponse; +import ssu.eatssu.domain.user.dto.GetDepartmentResponse; +import ssu.eatssu.domain.user.dto.LanguageResponse; +import ssu.eatssu.domain.user.dto.LanguageUpdateRequest; +import ssu.eatssu.domain.user.dto.MyMealReviewResponse; +import ssu.eatssu.domain.user.dto.MyPageResponse; +import ssu.eatssu.domain.user.dto.MyReviewDetail; +import ssu.eatssu.domain.user.dto.NicknameUpdateRequest; +import ssu.eatssu.domain.user.dto.UpdateDepartmentRequest; +import ssu.eatssu.global.handler.response.BaseResponse; + +import java.util.List; + +@Tag(name = "User", description = "유저 API") +public interface UserControllerDocs { + + @Operation(summary = "이메일 중복 체크", description = """ + 이메일 중복 체크 API 입니다.

+ 중복되지 않은 이메일이면 true 를 반환합니다 + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "중복되지 않은 이메일") + }) + BaseResponse validateDuplicatedEmail(@Parameter(description = "이메일") String email); + + @Operation(summary = "닉네임 중복 및 유효성 체크", description = """ + 닉네임 중복 및 유효성 체크 API 입니다.

+ 유효하고 중복되지 않은 닉네임이면 true 를 반환합니다 + """) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유효하고 중복되지 않은 닉네임"), + @ApiResponse(responseCode = "400", description = "숫자로만 이루어진 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "연속된 공백을 포함하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "연속된 하이폰을 사용하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "첫 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "마지막 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "한글,영문,숫자,공백,하이폰(-)이외의 문자를 쓴 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "닉네임이 1자 이상 16이하가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "서비스명/브랜드명 단독 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "관리자로 혼동될 수 있는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "욕설/비속어가 이름에 포함되는 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse validateNickname(@Parameter(description = "닉네임") String nickname); + + @Operation(summary = "닉네임 수정", description = "닉네임 수정 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "닉네임 수정 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "숫자로만 이루어진 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "연속된 공백을 포함하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "연속된 하이폰을 사용하는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "첫 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "마지막 글자가 한글,영문,숫자가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "한글,영문,숫자,공백,하이폰(-)이외의 문자를 쓴 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "닉네임이 1자 이상 16이하가 아닌 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "서비스명/브랜드명 단독 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "관리자로 혼동될 수 있는 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "욕설/비속어가 이름에 포함되는 경우", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "409", description = "중복된 닉네임", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse updateNickname(NicknameUpdateRequest updateNicknameRequest, CustomUserDetails userDetails); + + @Operation(summary = "언어 설정 수정", description = "유저의 언어 설정을 수정하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "언어 설정 수정 성공"), + @ApiResponse(responseCode = "400", description = "지원하지 않는 언어 또는 누락된 언어 설정", content = @Content(schema = @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse updateLanguage(LanguageUpdateRequest request, CustomUserDetails userDetails); + + @Operation(summary = "언어 설정 조회", description = "유저의 언어 설정을 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "언어 설정 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getLanguage(CustomUserDetails userDetails); + + @Operation(summary = "유저 탈퇴", description = "유저 탈퇴 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저 탈퇴 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse withdraw(CustomUserDetails userDetails); + + @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMyReviewList( + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + @ParameterObject Pageable pageable, + CustomUserDetails customUserDetails); + + @Operation(summary = "마이페이지 정보 조회", description = "마이페이지 정보를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "마이페이지 정보 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse getMyPage(CustomUserDetails customUserDetails); + + @Operation(summary = "유저가 찜한 제휴 조회", description = "유저가 찜한 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저가 찜한 제휴 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse> getUserLikedPartnerships(CustomUserDetails userDetails); + + @Operation(summary = "유저의 학과 등록", description = "유저의 학과를 등록하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 학과 등록 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "404", description = "존재하지 않는 학과", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse registerDepartment(UpdateDepartmentRequest request, CustomUserDetails userDetails); + + @Operation(summary = "유저의 단과대/학과 제휴 조회", description = "유저의 단과대/학과 제휴를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 단과대/학과 제휴 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse> getUserDepartmentPartnerships(CustomUserDetails userDetails); + + @Operation(summary = "유저의 학과 조회", description = "유저의 학과를 조회하는 API 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "유저의 학과 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + @ApiResponse(responseCode = "400", description = "유저의 학과 정보가 등록되지 않음", content = @Content(schema = + @Schema(implementation = BaseResponse.class))), + }) + BaseResponse getDepartment(CustomUserDetails userDetails); + + @Operation(summary = "내가 쓴 리뷰 리스트 조회", description = "내가 쓴 리뷰 리스트를 조회하는 API V2 입니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "내가 쓴 리뷰 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 유저", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getMyReviews( + @Parameter(description = "마지막으로 조회된 reviewId값(첫 조회시 값 필요 없음)", in = ParameterIn.QUERY) Long lastReviewId, + @ParameterObject Pageable pageable, + CustomUserDetails customUserDetails); + + @Operation(summary = "단과대 조회", description = "숭실대학교 단과대학 들을 조회하는 API입니다.(토큰 불필요)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"), + @ApiResponse(responseCode = "404", description = "존재하지 않는 단과대", content = @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + BaseResponse> getColleges(CustomUserDetails userDetails); + + @Operation(summary = "단과대에 따른 학과 조회", description = "단과대학을 입력하면 단과대에 속한 숭실대학교 학과를 조회하는 API입니다.(토큰 불필요)") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "단과대 리스트 조회 성공"), + }) + BaseResponse> getDepartments(Long collegeId, CustomUserDetails userDetails); +}