Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import org.sopt.snappinserver.api.v1.category.dto.response.CategoriesResponse;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.web.bind.annotation.GetMapping;

@Tag(name = "02 - Category", description = "촬영 상황 관련 API")
public interface CategoryApi {
Expand All @@ -12,5 +13,6 @@ public interface CategoryApi {
summary = "촬영 상황 조회",
description = "촬영 상황 옵션으로 사용될 스냅 유형 전체 목록을 조회합니다."
)
@GetMapping
ApiResponseBody<CategoriesResponse, Void> getCategories();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import org.sopt.snappinserver.api.v1.category.dto.response.CategoryResponse;
import org.sopt.snappinserver.global.enums.SnapCategory;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -18,7 +17,6 @@
public class CategoryController implements CategoryApi {

@Override
@GetMapping
public ApiResponseBody<CategoriesResponse, Void> getCategories() {
List<CategoryResponse> categories =
Arrays.stream(SnapCategory.values())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.sopt.snappinserver.api.v1.portfolio.dto.response.GetPortfolioMetaResponse;
import org.sopt.snappinserver.domain.auth.infra.jwt.CustomUserInfo;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;

Expand All @@ -22,12 +23,14 @@ public interface PortfolioApi {
summary = "비로그인 시 인기 무드 기반 포폴 추천 목록 조회 API",
description = "비로그인 시 인기 무드와 많이 매칭되는 순서대로 속한 포트폴리오를 3개 조회합니다."
)
@GetMapping("/popular")
ApiResponseBody<GetPopularPortfolioListResponse, Void> getPopularPortfolios();

@Operation(
summary = "포트폴리오 상세 조회 API",
description = "포트폴리오 ID를 받아서 포트폴리오 상세 정보를 조회합니다."
)
@GetMapping("/{portfolioId}")
ApiResponseBody<GetPortfolioDetailResponse, Void> getPortfolioDetail(
@Parameter(hidden = true)
CustomUserInfo userInfo,
Expand All @@ -39,6 +42,7 @@ ApiResponseBody<GetPortfolioDetailResponse, Void> getPortfolioDetail(
summary = "로그인 시 큐레이션 기반 포폴 추천 목록 조회",
description = "큐레이션 기반 포트폴리오 추천 목록을 조회합니다."
)
@GetMapping("/recommendation")
ApiResponseBody<GetCurationResponse, Void> getCuratedPortfolios(
@Parameter(hidden = true)
CustomUserInfo userInfo
Expand All @@ -48,6 +52,7 @@ ApiResponseBody<GetCurationResponse, Void> getCuratedPortfolios(
summary = "포폴 목록 조회 (전체조회/필터링(무드&상품)/검색) API",
description = "포트폴리오 전체 조회, 필터링, 검색 시 사용되는 API 입니다."
)
@GetMapping
ApiResponseBody<GetPortfolioListResponse, GetPortfolioMetaResponse> getPortfolioList(
@Valid @ModelAttribute GetPortfolioListRequest request
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
import org.sopt.snappinserver.domain.portfolio.service.usecase.GetPortfolioListUseCase;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -39,7 +36,6 @@ public class PortfolioController implements PortfolioApi {
private final GetPortfolioListUseCase getPortfolioListUseCase;

@Override
@GetMapping("/popular")
public ApiResponseBody<GetPopularPortfolioListResponse, Void> getPopularPortfolios() {
GetPopularPortfolioListResult result = getPopularPortfolioListUseCase
.getPopularPortfolioList();
Expand All @@ -49,10 +45,9 @@ public ApiResponseBody<GetPopularPortfolioListResponse, Void> getPopularPortfoli
}

@Override
@GetMapping("/{portfolioId}")
public ApiResponseBody<GetPortfolioDetailResponse, Void> getPortfolioDetail(
@AuthenticationPrincipal CustomUserInfo userInfo,
@PathVariable Long portfolioId
Long portfolioId
) {
Long userId = (userInfo != null) ? userInfo.userId() : null;
GetPortfolioDetailResult result = getPortfolioDetailUseCase.findPortfolioDetail(
Expand All @@ -65,7 +60,6 @@ public ApiResponseBody<GetPortfolioDetailResponse, Void> getPortfolioDetail(
}

@Override
@GetMapping("/recommendation")
public ApiResponseBody<GetCurationResponse, Void> getCuratedPortfolios(
@AuthenticationPrincipal CustomUserInfo userInfo
) {
Expand All @@ -78,9 +72,8 @@ public ApiResponseBody<GetCurationResponse, Void> getCuratedPortfolios(
}

@Override
@GetMapping
public ApiResponseBody<GetPortfolioListResponse, GetPortfolioMetaResponse> getPortfolioList(
@ModelAttribute GetPortfolioListRequest request
GetPortfolioListRequest request
) {
GetPortfolioListQuery query = getQuery(request);
GetPortfolioListResult result = getPortfolioListUseCase.getPortfolioList(query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand Down Expand Up @@ -167,7 +166,7 @@ public ApiResponseBody<GetProductDetailResponse, Void> getProductDetail(

@Override
public ApiResponseBody<GetProductListResponse, GetProductListMeta> getProductList(
@ModelAttribute GetProductListQuery query
GetProductListQuery query
) {
GetProductListResult result = getProductListUseCase.getProductList(query);
GetProductListResponse response = GetProductListResponse.from(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
Expand All @@ -13,6 +12,7 @@
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "011 - Review", description = "리뷰 관련 API")
Expand All @@ -22,6 +22,7 @@ public interface ReviewApi {
summary = "리뷰 사진 url 발급 API",
description = "리뷰에 업로드하는 사진을 저장할 S3 Presigned URL을 생성하여 반환합니다."
)
@PostMapping("/image")
ApiResponseBody<PostPresignedUrlResponse, Void> postPresignedUrl(
@Parameter(hidden = true)
CustomUserInfo userInfo,
Expand All @@ -35,7 +36,7 @@ ApiResponseBody<PostPresignedUrlResponse, Void> postPresignedUrl(
)
@GetMapping("/{reviewId}")
ApiResponseBody<GetReviewDetailResponse, Void> getReviewDetail(
@Schema(description = "리뷰 ID")
@Parameter(description = "리뷰 ID")
@PathVariable @NotNull Long reviewId
Comment on lines +39 to 40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

@PathVariable 파라미터에 @Positive 추가를 고려해보세요.

@Schema@Parameter 변경은 PathVariable 파라미터 문서화 컨벤션(PR #250 학습 내용)에 잘 맞습니다.

다만, reviewId는 DB 식별자이므로 @NotNull과 함께 @Positive를 추가하면 음수나 0이 입력되는 경우를 API 계층에서 차단할 수 있습니다. 프로젝트 내 다른 PathVariable 패턴(PR #80)에서도 @NotNull, @Positive를 함께 사용하는 것이 확인됩니다.

추가로, @Parameter(description = "리뷰 ID")example 속성을 함께 작성하면 Swagger 문서 품질이 향상됩니다.

✨ 제안 수정 사항
-    `@Parameter`(description = "리뷰 ID")
-    `@PathVariable` `@NotNull` Long reviewId
+    `@Parameter`(description = "리뷰 ID", example = "1", required = true)
+    `@PathVariable` `@NotNull` `@Positive` Long reviewId

Based on learnings: "define API-facing interfaces for controllers that declare PathVariable, NotNull, Positive, etc." (PR #80) and "leverage description, required, and example attributes where appropriate" (PR #250).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/sopt/snappinserver/api/v1/review/controller/ReviewApi.java`
around lines 39 - 40, The PathVariable parameter reviewId in ReviewApi should
validate that it is a positive DB identifier and provide an example for Swagger:
add the javax.validation.constraints.@Positive annotation alongside `@NotNull` on
the reviewId parameter and augment the `@Parameter`(...) declaration to include an
example (e.g., example = "1") so the controller method signature (the reviewId
parameter in ReviewApi) both enforces >0 at the API layer and improves OpenAPI
docs.

);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.sopt.snappinserver.global.response.code.review.ReviewSuccessCode.POST_PRESIGNED_URL_OK;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sopt.snappinserver.global.response.code.review.ReviewSuccessCode;
import org.sopt.snappinserver.api.v1.review.dto.request.PostPresignedUrlRequest;
Expand All @@ -16,8 +15,6 @@
import org.sopt.snappinserver.domain.review.service.usecase.PostPresignedUrlUseCase;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -30,10 +27,9 @@ public class ReviewController implements ReviewApi {
private final GetReviewDetailUseCase getReviewDetailUseCase;

@Override
@PostMapping("/image")
public ApiResponseBody<PostPresignedUrlResponse, Void> postPresignedUrl(
@AuthenticationPrincipal CustomUserInfo userInfo,
@Valid @RequestBody PostPresignedUrlRequest request
PostPresignedUrlRequest request
) {
PostPresignedUrlCommand command = getPostPresignedUrlCommand(userInfo, request);
PostPresignedUrlResult result = postPresignedUrlUseCase.getPresignedUrlForUpload(command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
import org.sopt.snappinserver.api.v1.wish.dto.response.WishedProductsResponse;
import org.sopt.snappinserver.domain.auth.infra.jwt.CustomUserInfo;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "012 - Wish", description = "좋아요 관련 API")
@Validated
public interface WishApi {

@Operation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
import org.sopt.snappinserver.domain.wish.service.usecase.PostWishProductUseCase;
import org.sopt.snappinserver.global.response.dto.ApiResponseBody;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/api/v1/wishes")
@RequiredArgsConstructor
@RestController
@Validated
public class WishController implements WishApi {

private final PostWishPortfolioUseCase postWishPortfolioUseCase;
Expand Down