diff --git a/src/main/java/com/matzip/admin/controller/AdminController.java b/src/main/java/com/matzip/admin/controller/AdminController.java index 234c751..0e5dee7 100644 --- a/src/main/java/com/matzip/admin/controller/AdminController.java +++ b/src/main/java/com/matzip/admin/controller/AdminController.java @@ -9,6 +9,8 @@ import com.matzip.common.response.ApiResponse; // import com.matzip.common.security.UserPrincipal; import com.matzip.place.repository.PlaceRepository; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; // import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -19,6 +21,7 @@ import java.util.List; +@Tag(name = "어드민", description = "맛집 등록 요청 승인/거절 관리 API") @RequestMapping("/admin/api") @RestController public class AdminController { @@ -31,6 +34,7 @@ public AdminController(PlaceRepository placeRepository, AdminPlaceRegisterReques this.adminPlaceRegisterRequestService = adminPlaceRegisterRequestService; } + @Operation(summary = "맛집 등록 요청 목록 (어드민)", description = "승인 대기(PENDING) 상태인 맛집 등록 요청 목록을 조회한다.") @GetMapping("/requests/places") public ApiResponse> findPlaceRegisterRequests() { List data = placeRepository.findPendingPlaces() @@ -41,6 +45,9 @@ public ApiResponse> findPlaceRegisterRequ return ApiResponse.success(data); } + @Operation(summary = "맛집 등록 요청 상세 (어드민)", description = "특정 맛집 등록 요청의 상세 정보를 조회한다.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집을 찾을 수 없습니다.") @GetMapping("/requests/places/{placeId}") public ApiResponse findPlaceRegisterRequestDetail( @PathVariable("placeId") Long placeId @@ -52,6 +59,7 @@ public ApiResponse findPlaceRegisterReque return ApiResponse.success(data); } + @Operation(summary = "맛집 등록 요청 승인/거절 (어드민)", description = "맛집 등록 요청을 승인(APPROVED) 또는 거절(REJECTED)한다. 거절 시 사유를 함께 전달한다.") @PostMapping("/requests/places/{placeId}/review") public ApiResponse reviewPlaceRegisterRequest( @PathVariable("placeId") Long placeId, diff --git a/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestDetailResponseDto.java b/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestDetailResponseDto.java index d6ff6fa..4631812 100644 --- a/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestDetailResponseDto.java +++ b/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestDetailResponseDto.java @@ -6,21 +6,28 @@ import com.matzip.place.dto.LocationDto; import com.matzip.place.dto.PhotoDto; import com.matzip.place.dto.TagDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDate; import java.util.List; @Builder -public record PlaceRegisterRequestDetailResponseDto(Long placeId, String placeName, LocalDate requestDate, List photos, - String address, LocationDto location, String description, - List menus, List categories, List tags) { +public record PlaceRegisterRequestDetailResponseDto( + @Schema(description = "맛집 ID", example = "15") Long placeId, + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") String placeName, + @Schema(description = "등록 요청일", example = "2025-08-12") LocalDate requestDate, + List photos, + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") String address, + LocationDto location, + @Schema(description = "맛집 설명", example = "직원이 엄청 친절해요! 근데 화장실에 갔고 냄새나요 ㅠㅠㅠ 그래도 맛은 있어서 괜찮아요") String description, + List menus, List categories, List tags) { @Builder public record MenusResponse( - String name, - Integer price, - Boolean isRecommended + @Schema(description = "메뉴 이름", example = "숙성우대갈비") String name, + @Schema(description = "가격(원)", example = "32000") Integer price, + @Schema(description = "대표 메뉴 여부", example = "true") Boolean isRecommended ) { public static MenusResponse from(Menu menu) { return MenusResponse.builder() diff --git a/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestsResponseDto.java b/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestsResponseDto.java index 69b6c97..ac693c1 100644 --- a/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestsResponseDto.java +++ b/src/main/java/com/matzip/admin/dto/response/PlaceRegisterRequestsResponseDto.java @@ -4,14 +4,19 @@ import com.matzip.place.domain.entity.Place; import com.matzip.place.dto.CategoryDto; import com.matzip.place.dto.TagDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDate; import java.util.List; @Builder -public record PlaceRegisterRequestsResponseDto(Long placeId, String placeName, LocalDate requestDate, Campus campus, - List categories, List tags) { +public record PlaceRegisterRequestsResponseDto( + @Schema(description = "맛집 ID", example = "15") Long placeId, + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") String placeName, + @Schema(description = "등록 요청일", example = "2025-08-12") LocalDate requestDate, + @Schema(description = "캠퍼스", example = "SINGWAN") Campus campus, + List categories, List tags) { public static PlaceRegisterRequestsResponseDto from(Place place) { return PlaceRegisterRequestsResponseDto.builder() diff --git a/src/main/java/com/matzip/auth/controller/AuthController.java b/src/main/java/com/matzip/auth/controller/AuthController.java index d745fa8..76bca57 100644 --- a/src/main/java/com/matzip/auth/controller/AuthController.java +++ b/src/main/java/com/matzip/auth/controller/AuthController.java @@ -13,6 +13,8 @@ import com.matzip.common.exception.BusinessException; import com.matzip.common.exception.code.ErrorCode; import com.matzip.common.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; @@ -24,6 +26,7 @@ import java.time.Duration; +@Tag(name = "인증", description = "카카오 로그인 및 JWT 토큰 발급 API") @RestController @RequestMapping("/api/v1/auth") @RequiredArgsConstructor @@ -39,6 +42,7 @@ public class AuthController { private final StatelessStateSigner stateSigner; private final KakaoAuthorizeUrlBuilder kakaoAuthorizeUrlBuilder; + @Operation(summary = "카카오 인가 URL 리다이렉트", description = "허용된 clientOrigin을 검증하고 카카오 로그인 페이지(302)로 리다이렉트한다.") @GetMapping("/authorize") public ResponseEntity authorize(@RequestParam("clientOrigin") String clientOrigin) { @@ -66,6 +70,7 @@ public ResponseEntity authorize(@RequestParam("clientOrigin") String clien * @param body * @return */ + @Operation(summary = "액세스 토큰 재발급", description = "쿠키 또는 요청 본문의 리프레시 토큰으로 액세스 토큰을 재발급하고, 회전된 리프레시 토큰을 쿠키로 설정한다.") @PostMapping("/token") public ResponseEntity> reissue( @CookieValue(value = RT_COOKIE_NAME, required = false) String rtCookie, @@ -106,6 +111,7 @@ private ResponseCookie buildRtCookie(String rt, long maxAgeMs) { .build(); } + @Operation(summary = "카카오 로그인 콜백", description = "카카오 인가 코드를 받아 로그인을 처리하고, 성공/실패에 따라 프론트 페이지로 리다이렉트(302)한다.") @GetMapping("/callback") public ResponseEntity kakaoCallback( @RequestParam("code") @NotBlank String code, diff --git a/src/main/java/com/matzip/auth/controller/AuthControllerV2.java b/src/main/java/com/matzip/auth/controller/AuthControllerV2.java index 4b6d641..181fb1d 100644 --- a/src/main/java/com/matzip/auth/controller/AuthControllerV2.java +++ b/src/main/java/com/matzip/auth/controller/AuthControllerV2.java @@ -4,6 +4,8 @@ import com.matzip.auth.dto.response.LoginResponseDto; import com.matzip.auth.service.AuthService; import com.matzip.common.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; @@ -14,6 +16,7 @@ import java.time.Duration; +@Tag(name = "인증", description = "카카오 로그인 및 JWT 토큰 발급 API") @RequestMapping("/api/v2/auth") @RestController public class AuthControllerV2 { @@ -24,6 +27,7 @@ public AuthControllerV2(AuthService authService) { this.authService = authService; } + @Operation(summary = "카카오 로그인 (v2)", description = "인가 코드와 redirectUri로 로그인하고, 액세스 토큰을 본문으로, 리프레시 토큰을 쿠키로 반환한다.") @GetMapping("/oauth2") public ResponseEntity> kakaoLogin(@RequestParam("code") String code, @RequestParam("redirectUri") String redirectUri) { diff --git a/src/main/java/com/matzip/lottery/controller/LotteryEventController.java b/src/main/java/com/matzip/lottery/controller/LotteryEventController.java index 3e137ab..18e2241 100644 --- a/src/main/java/com/matzip/lottery/controller/LotteryEventController.java +++ b/src/main/java/com/matzip/lottery/controller/LotteryEventController.java @@ -8,6 +8,8 @@ import com.matzip.lottery.dto.response.LotteryEventView; import com.matzip.lottery.dto.response.ParticipatedEventResponseDto; import com.matzip.lottery.service.LotteryEventService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -19,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "이벤트", description = "추첨 이벤트 조회 및 응모 API") @RequestMapping("/api/v1/events") @RestController public class LotteryEventController { @@ -29,6 +32,7 @@ public LotteryEventController(LotteryEventService lotteryEventService) { this.lotteryEventService = lotteryEventService; } + @Operation(summary = "현재 진행 중인 이벤트 조회", description = "진행 중인 추첨 이벤트를 조회한다. 비로그인 시 익명 응답, 로그인 시 사용 티켓 수 등이 포함된다.") @GetMapping public ApiResponse findEvent(@AuthenticationPrincipal UserPrincipal user) { Long userId = (user != null) ? user.getUserId() : null; @@ -36,12 +40,18 @@ public ApiResponse findEvent(@AuthenticationPrincipal UserPrin return ApiResponse.success(data); } + @Operation(summary = "내가 응모한 이벤트 목록", description = "인증된 사용자가 응모한 이벤트 목록을 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @GetMapping("/entries") public ApiResponse> getParticipatedEvents(@AuthenticationPrincipal UserPrincipal user) { List data = lotteryEventService.getParticipatedEvents(user.getUserId()); return ApiResponse.success(data); } + @Operation(summary = "이벤트 응모 결과 조회", description = "특정 이벤트에 대한 응모/당첨 결과를 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @GetMapping("/{eventId}/entries") public ApiResponse getEntryResult(@PathVariable Long eventId, @AuthenticationPrincipal UserPrincipal user) { @@ -49,6 +59,9 @@ public ApiResponse getEntryResult(@PathVariable Lon return ApiResponse.success(data); } + @Operation(summary = "이벤트 응모", description = "연락처와 약관 동의 정보로 이벤트에 응모한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "응모 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @PostMapping("/{eventId}/apply") public ApiResponse applyForPrize(@PathVariable Long eventId, @Validated @RequestBody ApplyEventRequestDto request, diff --git a/src/main/java/com/matzip/lottery/dto/response/ApplyEventResponseDto.java b/src/main/java/com/matzip/lottery/dto/response/ApplyEventResponseDto.java index 4896d63..707754e 100644 --- a/src/main/java/com/matzip/lottery/dto/response/ApplyEventResponseDto.java +++ b/src/main/java/com/matzip/lottery/dto/response/ApplyEventResponseDto.java @@ -1,16 +1,19 @@ package com.matzip.lottery.dto.response; import com.matzip.lottery.domain.WinnerContact; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @Builder public record ApplyEventResponseDto( - String phoneNumber, + @Schema(description = "연락처", example = "010-1234-5678") String phoneNumber, AgreementsResponse agreements ) { - public record AgreementsResponse(boolean termsAgreed, boolean privacyAgreed) { + public record AgreementsResponse( + @Schema(description = "이용약관 동의 여부", example = "true") boolean termsAgreed, + @Schema(description = "개인정보 수집 동의 여부", example = "true") boolean privacyAgreed) { } public static ApplyEventResponseDto from(WinnerContact contact) { diff --git a/src/main/java/com/matzip/lottery/dto/response/EventEntryResultResponseDto.java b/src/main/java/com/matzip/lottery/dto/response/EventEntryResultResponseDto.java index 9b100fc..be8aa5a 100644 --- a/src/main/java/com/matzip/lottery/dto/response/EventEntryResultResponseDto.java +++ b/src/main/java/com/matzip/lottery/dto/response/EventEntryResultResponseDto.java @@ -1,20 +1,21 @@ package com.matzip.lottery.dto.response; import com.matzip.lottery.domain.LotteryEvent; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDateTime; @Builder public record EventEntryResultResponseDto( - Long eventId, + @Schema(description = "이벤트 ID", example = "3") Long eventId, LotteryEventResponseDto.PrizeResponse prize, - int totalWinnersCount, - int participantsCount, - int usedTicketsCount, - boolean isWinner, - LocalDateTime eventEndDate, - boolean isPhoneSubmitted + @Schema(description = "총 당첨자 수", example = "3") int totalWinnersCount, + @Schema(description = "참여자 수", example = "27") int participantsCount, + @Schema(description = "사용한 응모권 수", example = "3") int usedTicketsCount, + @Schema(description = "당첨 여부", example = "true") boolean isWinner, + @Schema(description = "이벤트 종료 일시", example = "2025-08-21T00:00:00") LocalDateTime eventEndDate, + @Schema(description = "연락처 제출 여부", example = "true") boolean isPhoneSubmitted ) { public static EventEntryResultResponseDto of(LotteryEvent event, int participantsCount, int usedTicketsCount, diff --git a/src/main/java/com/matzip/lottery/dto/response/LotteryEventAnonymousResponseDto.java b/src/main/java/com/matzip/lottery/dto/response/LotteryEventAnonymousResponseDto.java index 7548704..933ca71 100644 --- a/src/main/java/com/matzip/lottery/dto/response/LotteryEventAnonymousResponseDto.java +++ b/src/main/java/com/matzip/lottery/dto/response/LotteryEventAnonymousResponseDto.java @@ -1,13 +1,18 @@ package com.matzip.lottery.dto.response; import com.matzip.lottery.domain.LotteryEvent; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDateTime; @Builder -public record LotteryEventAnonymousResponseDto(Long eventId, LotteryEventResponseDto.PrizeResponse prize, - int totalWinnersCount, int participantsCount, LocalDateTime eventEndDate) +public record LotteryEventAnonymousResponseDto( + @Schema(description = "이벤트 ID", example = "3") Long eventId, + LotteryEventResponseDto.PrizeResponse prize, + @Schema(description = "총 당첨자 수", example = "3") int totalWinnersCount, + @Schema(description = "참여자 수", example = "27") int participantsCount, + @Schema(description = "이벤트 종료 일시", example = "2025-08-21T00:00:00") LocalDateTime eventEndDate) implements LotteryEventView { public static LotteryEventAnonymousResponseDto empty() { diff --git a/src/main/java/com/matzip/lottery/dto/response/LotteryEventResponseDto.java b/src/main/java/com/matzip/lottery/dto/response/LotteryEventResponseDto.java index d365cc2..795c50c 100644 --- a/src/main/java/com/matzip/lottery/dto/response/LotteryEventResponseDto.java +++ b/src/main/java/com/matzip/lottery/dto/response/LotteryEventResponseDto.java @@ -2,13 +2,19 @@ import com.matzip.lottery.domain.LotteryEvent; import com.matzip.lottery.domain.Prize; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDateTime; @Builder -public record LotteryEventResponseDto(Long eventId, PrizeResponse prize, int totalWinnersCount, int participantsCount, - int usedTicketsCount, LocalDateTime eventEndDate) +public record LotteryEventResponseDto( + @Schema(description = "이벤트 ID", example = "3") Long eventId, + PrizeResponse prize, + @Schema(description = "총 당첨자 수", example = "3") int totalWinnersCount, + @Schema(description = "참여자 수", example = "27") int participantsCount, + @Schema(description = "사용한 응모권 수", example = "3") int usedTicketsCount, + @Schema(description = "이벤트 종료 일시", example = "2025-08-21T00:00:00") LocalDateTime eventEndDate) implements LotteryEventView { public static LotteryEventResponseDto empty() { @@ -28,7 +34,9 @@ public static LotteryEventResponseDto of(LotteryEvent lotteryEvent, int particip } @Builder - record PrizeResponse(String description, String imageUrl) { + record PrizeResponse( + @Schema(description = "경품 설명", example = "BHC 뿌링클 치킨 기프티콘 1장") String description, + @Schema(description = "경품 이미지 URL", example = "https://example.com/images/bhc_bburinkle_chicken.png") String imageUrl) { public static PrizeResponse from(Prize prize) { return PrizeResponse.builder() diff --git a/src/main/java/com/matzip/lottery/dto/response/ParticipatedEventResponseDto.java b/src/main/java/com/matzip/lottery/dto/response/ParticipatedEventResponseDto.java index 5b6ba93..d5761e7 100644 --- a/src/main/java/com/matzip/lottery/dto/response/ParticipatedEventResponseDto.java +++ b/src/main/java/com/matzip/lottery/dto/response/ParticipatedEventResponseDto.java @@ -1,17 +1,18 @@ package com.matzip.lottery.dto.response; import com.matzip.lottery.domain.LotteryEvent; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.time.LocalDateTime; @Builder public record ParticipatedEventResponseDto( - Long eventId, + @Schema(description = "이벤트 ID", example = "3") Long eventId, LotteryEventResponseDto.PrizeResponse prize, - int totalWinnersCount, - int participantsCount, - LocalDateTime eventEndDate + @Schema(description = "총 당첨자 수", example = "3") int totalWinnersCount, + @Schema(description = "참여자 수", example = "27") int participantsCount, + @Schema(description = "이벤트 종료 일시", example = "2025-08-21T00:00:00") LocalDateTime eventEndDate ) { public static ParticipatedEventResponseDto of(LotteryEvent event, int participantsCount) { diff --git a/src/main/java/com/matzip/place/controller/CategoryController.java b/src/main/java/com/matzip/place/controller/CategoryController.java index 36bfdb7..a45c734 100644 --- a/src/main/java/com/matzip/place/controller/CategoryController.java +++ b/src/main/java/com/matzip/place/controller/CategoryController.java @@ -4,6 +4,8 @@ import com.matzip.place.dto.response.PlaceCommonResponseDto; import com.matzip.place.service.PlaceReadService; import com.matzip.place.domain.Campus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import com.matzip.place.service.CategoryService; @@ -14,6 +16,7 @@ import java.util.List; +@Tag(name = "카테고리", description = "카테고리 및 카테고리별 맛집 조회 API") @RestController @RequestMapping("/api/v1/categories") @RequiredArgsConstructor @@ -22,6 +25,7 @@ public class CategoryController { private final PlaceReadService placeReadService; private final CategoryService categoryService; + @Operation(summary = "카테고리별 맛집 목록", description = "특정 카테고리에 속한 캠퍼스별 맛집 목록을 조회한다.") @GetMapping("/{categoryId}/places") public ApiResponse> getPlacesByCategory( @PathVariable Long categoryId, @@ -31,6 +35,7 @@ public ApiResponse> getPlacesByCategory( return ApiResponse.success(places); } + @Operation(summary = "전체 카테고리 목록", description = "등록에 사용할 수 있는 전체 카테고리 목록을 조회한다.") @GetMapping public ApiResponse> getAllCategories() { List categories = categoryService.getAllCategories(); diff --git a/src/main/java/com/matzip/place/controller/PlaceController.java b/src/main/java/com/matzip/place/controller/PlaceController.java index 2d7188d..aceb3bb 100644 --- a/src/main/java/com/matzip/place/controller/PlaceController.java +++ b/src/main/java/com/matzip/place/controller/PlaceController.java @@ -11,6 +11,8 @@ import com.matzip.place.service.PlaceReadService; import com.matzip.place.service.PlaceService; import com.matzip.place.domain.Campus; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -18,6 +20,7 @@ import java.util.List; +@Tag(name = "맛집", description = "맛집 등록·검색 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/places") @@ -27,6 +30,7 @@ public class PlaceController { private final PlaceReadService placeReadService; // 프리뷰 + @Operation(summary = "맛집 등록 프리뷰", description = "카카오 장소 ID로 등록 전 미리보기 정보를 조회한다.") @GetMapping("/preview") public ApiResponse preview(@Valid @ModelAttribute PlaceCheckRequestDto req) { PlaceCheckResponseDto data = placeService.preview(req); @@ -34,6 +38,7 @@ public ApiResponse preview(@Valid @ModelAttribute PlaceCh } // 등록 + @Operation(summary = "맛집 등록", description = "맛집 등록을 요청한다. 로그인 시 등록자 정보가 함께 저장된다.") @PostMapping public ApiResponse register( @Valid @RequestBody PlaceRequestDto req, @@ -48,6 +53,7 @@ public ApiResponse register( } // 가게 이름으로 검색 + @Operation(summary = "가게 이름으로 맛집 검색", description = "키워드와 캠퍼스로 가게 이름을 검색한다.") @GetMapping("/search") public ApiResponse> search( @RequestParam String keyword, @@ -57,6 +63,7 @@ public ApiResponse> search( } // 메뉴 이름으로 가게 검색 + @Operation(summary = "메뉴 이름으로 맛집 검색", description = "키워드와 캠퍼스로 메뉴 이름을 검색해 해당 메뉴를 파는 가게를 조회한다.") @GetMapping("/search/menu") public ApiResponse> searchByMenu( @RequestParam String keyword, diff --git a/src/main/java/com/matzip/place/controller/PlaceLikeController.java b/src/main/java/com/matzip/place/controller/PlaceLikeController.java index 9043057..6af4ab3 100644 --- a/src/main/java/com/matzip/place/controller/PlaceLikeController.java +++ b/src/main/java/com/matzip/place/controller/PlaceLikeController.java @@ -5,6 +5,8 @@ import com.matzip.place.dto.response.PlaceLikeResponseDto; import com.matzip.place.dto.response.PlaceCommonResponseDto; import com.matzip.place.service.PlaceLikeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.RestController; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -12,6 +14,7 @@ import java.util.List; +@Tag(name = "맛집 찜", description = "맛집 찜 추가·취소·목록 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/places") @@ -20,6 +23,9 @@ public class PlaceLikeController { private final PlaceLikeService placeLikeService; + @Operation(summary = "맛집 찜 추가", description = "맛집을 찜 목록에 추가한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "찜 추가 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @PostMapping("/{placeId}/like") public ApiResponse addLike( @PathVariable Long placeId, @@ -29,6 +35,9 @@ public ApiResponse addLike( return ApiResponse.success(response); } + @Operation(summary = "맛집 찜 취소", description = "맛집을 찜 목록에서 제거한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "찜 취소 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @DeleteMapping("/{placeId}/like") public ApiResponse removeLike( @PathVariable Long placeId, @@ -38,6 +47,9 @@ public ApiResponse removeLike( return ApiResponse.success(response); } + @Operation(summary = "내가 찜한 맛집 목록", description = "인증된 사용자가 찜한 맛집 목록을 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @GetMapping("/like") public ApiResponse> getMyLikedPlaces( @AuthenticationPrincipal UserPrincipal userPrincipal) { diff --git a/src/main/java/com/matzip/place/controller/PlaceReadController.java b/src/main/java/com/matzip/place/controller/PlaceReadController.java index 281a451..926239b 100644 --- a/src/main/java/com/matzip/place/controller/PlaceReadController.java +++ b/src/main/java/com/matzip/place/controller/PlaceReadController.java @@ -10,12 +10,15 @@ import com.matzip.place.service.PlaceReadService; import com.matzip.place.domain.Campus; import com.matzip.place.domain.SortType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; import java.util.List; +@Tag(name = "맛집", description = "맛집 조회 API") @RestController @RequestMapping("/api/v1/places") @RequiredArgsConstructor @@ -23,6 +26,9 @@ public class PlaceReadController { private final PlaceReadService placeReadService; + @Operation(summary = "맛집 상세 조회", description = "맛집 상세 정보를 조회한다. 로그인 시 찜 여부(isLiked)가 함께 내려간다.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "맛집을 찾을 수 없습니다.") @GetMapping("/{placeId}") public ApiResponse getPlaceDetail( @PathVariable Long placeId, @@ -34,6 +40,7 @@ public ApiResponse getPlaceDetail( return ApiResponse.success(placeDetail); } + @Operation(summary = "지도 범위 내 맛집 조회", description = "지도 좌표 범위(bounds) 내의 맛집 목록을 조회한다.") @GetMapping public ApiResponse> getPlacesInMap( @Valid @ModelAttribute MapSearchRequestDto requestDto) { @@ -42,6 +49,7 @@ public ApiResponse> getPlacesInMap( return ApiResponse.success(places); } + @Operation(summary = "맛집 랭킹 조회", description = "캠퍼스별로 정렬 기준(좋아요/조회수/최신)에 따른 맛집 랭킹을 조회한다.") @GetMapping("/ranking") public ApiResponse> getRanking( @RequestParam SortType sort, diff --git a/src/main/java/com/matzip/place/controller/PlaceRequestController.java b/src/main/java/com/matzip/place/controller/PlaceRequestController.java index 92a0cd8..3de670f 100644 --- a/src/main/java/com/matzip/place/controller/PlaceRequestController.java +++ b/src/main/java/com/matzip/place/controller/PlaceRequestController.java @@ -8,6 +8,8 @@ import com.matzip.place.dto.response.PlaceRegisterStatusDetailResponseDto; import com.matzip.place.dto.response.PlaceRegisterStatusResponseDto; import com.matzip.place.service.PlaceReadService; +import io.swagger.v3.oas.annotations.Operation; +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 +19,7 @@ import java.util.List; +@Tag(name = "맛집 등록 요청", description = "내 맛집 등록 요청 현황 조회 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/requests/places") @@ -24,6 +27,9 @@ public class PlaceRequestController { private final PlaceReadService placeReadService; + @Operation(summary = "내 맛집 등록 요청 목록", description = "인증된 사용자가 등록 요청한 맛집들의 승인 상태 목록을 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "로그인이 필요합니다.") @GetMapping public ApiResponse> getMyPlaceRequests( @AuthenticationPrincipal UserPrincipal userPrincipal @@ -36,6 +42,9 @@ public ApiResponse> getMyPlaceRequests( return ApiResponse.success(response); } + @Operation(summary = "내 맛집 등록 요청 상세", description = "인증된 사용자의 특정 맛집 등록 요청 상세와 승인/거절 사유를 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "로그인이 필요합니다.") @GetMapping("/{placeId}") public ApiResponse getMyPlaceRequestDetail( @PathVariable Long placeId, diff --git a/src/main/java/com/matzip/place/dto/CategoryDto.java b/src/main/java/com/matzip/place/dto/CategoryDto.java index 62b9f41..fdf1e43 100644 --- a/src/main/java/com/matzip/place/dto/CategoryDto.java +++ b/src/main/java/com/matzip/place/dto/CategoryDto.java @@ -1,14 +1,21 @@ package com.matzip.place.dto; import com.matzip.place.domain.entity.Category; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter @Builder public class CategoryDto { + + @Schema(description = "카테고리 ID", example = "5") private Long id; + + @Schema(description = "카테고리 이름", example = "고기·구이") private String name; + + @Schema(description = "아이콘 키", example = "Meat") private String iconKey; public static CategoryDto from(Category category) { diff --git a/src/main/java/com/matzip/place/dto/LocationDto.java b/src/main/java/com/matzip/place/dto/LocationDto.java index 9a6c679..6fd1c3a 100644 --- a/src/main/java/com/matzip/place/dto/LocationDto.java +++ b/src/main/java/com/matzip/place/dto/LocationDto.java @@ -1,5 +1,6 @@ package com.matzip.place.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; @@ -7,7 +8,10 @@ @Builder public class LocationDto { + @Schema(description = "위도", example = "37.625") private double latitude; + + @Schema(description = "경도", example = "127.151") private double longitude; public static LocationDto of(double latitude, double longitude) { diff --git a/src/main/java/com/matzip/place/dto/PhotoDto.java b/src/main/java/com/matzip/place/dto/PhotoDto.java index 8ebd16b..d851ee4 100644 --- a/src/main/java/com/matzip/place/dto/PhotoDto.java +++ b/src/main/java/com/matzip/place/dto/PhotoDto.java @@ -1,14 +1,21 @@ package com.matzip.place.dto; import com.matzip.place.domain.entity.Photo; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter @Builder public class PhotoDto { + + @Schema(description = "사진 ID", example = "101") private Long photoId; + + @Schema(description = "사진 URL", example = "https://example.com/spots/photo1.jpg") private String photoUrl; + + @Schema(description = "노출 순서(0부터)", example = "0") private Integer displayOrder; public static PhotoDto from(Photo photo) { diff --git a/src/main/java/com/matzip/place/dto/TagDto.java b/src/main/java/com/matzip/place/dto/TagDto.java index 198f245..88d99d4 100644 --- a/src/main/java/com/matzip/place/dto/TagDto.java +++ b/src/main/java/com/matzip/place/dto/TagDto.java @@ -1,6 +1,7 @@ package com.matzip.place.dto; import com.matzip.place.domain.entity.Tag; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -8,8 +9,13 @@ @Builder public class TagDto { + @Schema(description = "태그 ID", example = "3") private Long id; + + @Schema(description = "태그 이름", example = "혼밥하기 좋은") private String name; + + @Schema(description = "아이콘 키", example = "fingerUp") private String iconKey; public static TagDto from(Tag tag) { diff --git a/src/main/java/com/matzip/place/dto/response/MapSearchResponseDto.java b/src/main/java/com/matzip/place/dto/response/MapSearchResponseDto.java index 12b640c..7e92340 100644 --- a/src/main/java/com/matzip/place/dto/response/MapSearchResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/MapSearchResponseDto.java @@ -8,6 +8,7 @@ import com.matzip.place.domain.entity.Photo; import com.matzip.place.domain.entity.Place; import com.matzip.place.domain.entity.Tag; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -18,9 +19,15 @@ @Builder public class MapSearchResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; + private LocationDto location; private List photos; private List categories; diff --git a/src/main/java/com/matzip/place/dto/response/MenuResponseDto.java b/src/main/java/com/matzip/place/dto/response/MenuResponseDto.java index c5d99cc..2a8fc00 100644 --- a/src/main/java/com/matzip/place/dto/response/MenuResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/MenuResponseDto.java @@ -2,16 +2,24 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.matzip.place.domain.entity.Menu; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @Getter @Builder public class MenuResponseDto { + + @Schema(description = "메뉴 ID", example = "202") private Long menuId; + + @Schema(description = "메뉴 이름", example = "숙성우대갈비") private String name; + + @Schema(description = "가격(원)", example = "32000") private int price; + @Schema(description = "대표 메뉴 여부", example = "true") @Getter(onMethod_ = @JsonProperty("isRecommended")) private boolean isRecommended; diff --git a/src/main/java/com/matzip/place/dto/response/PlaceCheckResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceCheckResponseDto.java index 35461fb..56b76a9 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceCheckResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceCheckResponseDto.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.matzip.place.dto.LocationDto; import com.matzip.place.dto.PhotoDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -12,10 +13,15 @@ @Builder public class PlaceCheckResponseDto { + @Schema(description = "이미 등록된 가게인지 여부", example = "false") private Boolean alreadyRegistered; // 우리 서비스에 이미 등록된 가게인지 여부 + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; + private LocationDto location; // 미등록 프리뷰: 실제 사진/메뉴로 채움 @@ -26,10 +32,17 @@ public class PlaceCheckResponseDto { @Getter @Builder public static class MenuItem { + + @Schema(description = "메뉴 ID", example = "202") private Long menuId; + + @Schema(description = "메뉴 이름", example = "숙성우대갈비") private String name; + + @Schema(description = "가격(원)", example = "32000") private int price; + @Schema(description = "대표 메뉴 여부", example = "true") @Getter(onMethod_ = @JsonProperty("isRecommended")) private boolean isRecommended; // 프리뷰 기본값: false } diff --git a/src/main/java/com/matzip/place/dto/response/PlaceCommonResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceCommonResponseDto.java index ba462bd..f797c42 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceCommonResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceCommonResponseDto.java @@ -5,6 +5,7 @@ import com.matzip.place.domain.entity.Category; import com.matzip.place.domain.entity.Place; import com.matzip.place.domain.entity.Tag; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -14,9 +15,16 @@ @Getter @Builder public class PlaceCommonResponseDto { + + @Schema(description = "맛집 ID", example = "15") private final Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private final String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private final String address; + private final List categories; private final List tags; diff --git a/src/main/java/com/matzip/place/dto/response/PlaceDetailResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceDetailResponseDto.java index 17f0235..8adcbfd 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceDetailResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceDetailResponseDto.java @@ -10,6 +10,7 @@ import com.matzip.place.dto.LocationDto; import com.matzip.place.dto.PhotoDto; import com.matzip.place.dto.TagDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -20,16 +21,26 @@ @Builder public class PlaceDetailResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; + private LocationDto location; private List photos; + + @Schema(description = "맛집 설명", example = "직원이 엄청 친절해요! 근데 화장실에 갔고 냄새나요 ㅠㅠㅠ 그래도 맛은 있어서 괜찮아요") private String description; + private List menus; private List tags; private List categories; + @Schema(description = "로그인 사용자의 찜 여부", example = "true") @Getter(onMethod_ = @JsonProperty("isLiked")) private boolean isLiked; diff --git a/src/main/java/com/matzip/place/dto/response/PlaceMenuSearchResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceMenuSearchResponseDto.java index 108577b..11301b6 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceMenuSearchResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceMenuSearchResponseDto.java @@ -1,6 +1,7 @@ package com.matzip.place.dto.response; import com.matzip.place.domain.entity.Menu; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -8,8 +9,13 @@ @Builder public class PlaceMenuSearchResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "메뉴 이름", example = "숙성우대갈비") private String menuName; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; public static PlaceMenuSearchResponseDto from(Menu menu) { diff --git a/src/main/java/com/matzip/place/dto/response/PlaceRegisterResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceRegisterResponseDto.java index c21afe3..ca6aa37 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceRegisterResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceRegisterResponseDto.java @@ -5,6 +5,7 @@ import com.matzip.place.domain.entity.Category; import com.matzip.place.domain.entity.Place; import com.matzip.place.domain.entity.Tag; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -15,9 +16,15 @@ @Builder public class PlaceRegisterResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; + private List categories; private List tags; diff --git a/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusDetailResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusDetailResponseDto.java index 1bf2782..e49bc58 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusDetailResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusDetailResponseDto.java @@ -9,6 +9,7 @@ import com.matzip.place.dto.LocationDto; import com.matzip.place.dto.PhotoDto; import com.matzip.place.dto.TagDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -21,17 +22,33 @@ @Builder public class PlaceRegisterStatusDetailResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "등록 요청일", example = "2025-08-12") private LocalDate requestDate; + private List photos; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; + private LocationDto location; + + @Schema(description = "맛집 설명", example = "직원이 엄청 친절해요! 근데 화장실에 갔고 냄새나요 ㅠㅠㅠ 그래도 맛은 있어서 괜찮아요") private String description; + private List menus; private List categories; private List tags; + + @Schema(description = "등록 상태", example = "REJECTED") private String registerStatus; + + @Schema(description = "거절 사유(거절 시)", example = "메뉴 정보가 부족합니다.") private String rejectedReason; public static PlaceRegisterStatusDetailResponseDto from(Place place, diff --git a/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusResponseDto.java index 64e8036..10e6e31 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceRegisterStatusResponseDto.java @@ -2,6 +2,7 @@ import com.matzip.place.domain.entity.Place; import com.matzip.place.dto.CategoryDto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,10 +15,18 @@ @Builder public class PlaceRegisterStatusResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "등록 요청일", example = "2025-08-12") private LocalDate requestDate; + private List categories; + + @Schema(description = "등록 상태", example = "PENDING") private String registerStatus; public static PlaceRegisterStatusResponseDto from(Place place) { diff --git a/src/main/java/com/matzip/place/dto/response/PlaceSearchResponseDto.java b/src/main/java/com/matzip/place/dto/response/PlaceSearchResponseDto.java index c14e374..6a945db 100644 --- a/src/main/java/com/matzip/place/dto/response/PlaceSearchResponseDto.java +++ b/src/main/java/com/matzip/place/dto/response/PlaceSearchResponseDto.java @@ -1,6 +1,7 @@ package com.matzip.place.dto.response; import com.matzip.place.domain.entity.Place; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -8,8 +9,13 @@ @Builder public class PlaceSearchResponseDto { + @Schema(description = "맛집 ID", example = "15") private Long placeId; + + @Schema(description = "맛집 이름", example = "우돈탄 다산본점") private String placeName; + + @Schema(description = "주소", example = "경기 남양주시 다산중앙로82번길 25") private String address; public static PlaceSearchResponseDto from(Place place) { diff --git a/src/main/java/com/matzip/user/controller/UserController.java b/src/main/java/com/matzip/user/controller/UserController.java index 87744cf..d2a7d17 100644 --- a/src/main/java/com/matzip/user/controller/UserController.java +++ b/src/main/java/com/matzip/user/controller/UserController.java @@ -4,12 +4,15 @@ import com.matzip.common.security.UserPrincipal; import com.matzip.user.dto.response.UserProfileResponseDto; import com.matzip.user.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +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; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "사용자", description = "사용자 프로필 API") @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/users") @@ -17,6 +20,9 @@ public class UserController { private final UserService userService; + @Operation(summary = "내 프로필 조회", description = "인증된 사용자의 프로필(닉네임/프로필 이미지/배경)을 조회한다. 인증 필요.") + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공", useReturnTypeSchema = true) + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "인증이 필요합니다.") @GetMapping("/me") public ApiResponse getMyProfile( @AuthenticationPrincipal UserPrincipal userPrincipal) { diff --git a/src/main/java/com/matzip/user/dto/response/UserProfileResponseDto.java b/src/main/java/com/matzip/user/dto/response/UserProfileResponseDto.java index 2723886..0aa1869 100644 --- a/src/main/java/com/matzip/user/dto/response/UserProfileResponseDto.java +++ b/src/main/java/com/matzip/user/dto/response/UserProfileResponseDto.java @@ -1,6 +1,7 @@ package com.matzip.user.dto.response; import com.matzip.user.domain.User; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -8,8 +9,13 @@ @Builder public class UserProfileResponseDto { + @Schema(description = "닉네임", example = "행복한 다람쥐") private String nickname; + + @Schema(description = "프로필 이미지 URL", example = "https://knu-matzip.co.kr/images/profiles/tiger.png") private String profileImageUrl; + + @Schema(description = "프로필 배경 색상 HEX 코드", example = "BEE1E6") private String profileBackgroundHexCode; public static UserProfileResponseDto from(User user, String imageBaseUrl) { diff --git a/src/test/java/com/matzip/common/config/OpenApiSmokeTest.java b/src/test/java/com/matzip/common/config/OpenApiSmokeTest.java index b7866b2..0bd9c81 100644 --- a/src/test/java/com/matzip/common/config/OpenApiSmokeTest.java +++ b/src/test/java/com/matzip/common/config/OpenApiSmokeTest.java @@ -2,6 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -27,6 +29,34 @@ class OpenApiSmokeTest { assertThat(response.getBody()).doesNotContain("/api/v1/auth/dev/token"); } + @Test + void apiDocs_컨트롤러_어노테이션_반영() { + ResponseEntity response = restTemplate.getForEntity("/v3/api-docs", String.class); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + // @Tag: 도메인 한글 태그로 그룹화 + assertThat(response.getBody()).contains("\"맛집\""); + // @Operation: 엔드포인트 요약 + assertThat(response.getBody()).contains("맛집 상세 조회"); + } + + @Test + void 모든_엔드포인트에_200_응답_존재() throws Exception { + // 에러 @ApiResponse만 선언하면 springdoc가 기본 200을 대체해 성공 응답이 누락된다. + // 모든 오퍼레이션에 200이 남아있는지 회귀 방지로 검증한다. + ResponseEntity response = restTemplate.getForEntity("/v3/api-docs", String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + + JsonNode paths = new ObjectMapper().readTree(response.getBody()).get("paths"); + paths.fields().forEachRemaining(pathEntry -> + pathEntry.getValue().fields().forEachRemaining(opEntry -> { + JsonNode responses = opEntry.getValue().get("responses"); + assertThat(responses.has("200")) + .as("%s %s 에 200 응답이 있어야 한다", opEntry.getKey(), pathEntry.getKey()) + .isTrue(); + })); + } + @Test void swaggerUi_토큰없이_401아님() { ResponseEntity response = restTemplate.getForEntity("/swagger-ui/index.html", String.class);