Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat][#5] 상품 및 카테고리 관련 API 구현 #7

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from

Conversation

mjeongriver
Copy link
Collaborator

@mjeongriver mjeongriver commented Feb 23, 2025

관련 이슈

resolve : #5

작업 내용

사용자 + 관리자 공통 API (상품)

  • GET /products → 상품 목록 조회
  • GET /products/{id} → 상품 상세 조회

관리자 전용 API (상품)

  • POST /products → 상품 등록
  • PUT /products/{id} → 상품 수정
  • DELETE /products/{id} → 상품 삭제

사용자 + 관리자 공통 API (카테고리)

  • GET /�categories/active → 활성화된 카테고리 목록 조회

관리자 전용 API (카테고리)

  • GET /�categories → 모든 카테고리 목록 조회
  • GET /{id}/�sub-categories → 세부 카테고리 조회
  • POST /�categories → 카테고리 등록
  • PATCH /�categories/{id} → 카테고리 수정
  • PATCH /categories/{id}/activate → 카테고리 활성화
  • PATCH /categories/{id}/deactivate → 카테고리 비활성화

테스트 방법

1. 로그인 성공 화면

image

2. 관리자 API에 접근했을 경우

image

3. 일반 사용자 API 접근했을 경우

image

참고 사항

기존 문제

  • 시큐리티에서 발생하는 인증 실패, 인가 실패 응답도 별도로 관리되어 서비스 전체적으로 응답 체계가 일관되지 않았습니다.

개선 포인트

  • 성공 응답은 'ApiResponse'로 통일
  • 실패 응답은 'ErrorResponse'로 통일
  • 컨트롤러 / 서비스 / 시큐리티 레이어 구분 없이, 모든 응답 포맷을 동일하게 맞췄습니다.

@mjeongriver mjeongriver added ✨ Feature 기능 개발 📬 API 서버 API 통신 labels Feb 23, 2025
@mjeongriver mjeongriver added this to the CRUD 기능 개발 milestone Feb 23, 2025
@mjeongriver mjeongriver self-assigned this Feb 23, 2025
mjeongriver added 25 commits February 23, 2025 23:08
@mjeongriver mjeongriver marked this pull request as ready for review February 28, 2025 12:56
@mjeongriver mjeongriver force-pushed the feature/5-product-crud branch 3 times, most recently from 279d030 to 6010918 Compare February 28, 2025 14:06
Copy link

@f-lab-plam f-lab-plam left a comment

Choose a reason for hiding this comment

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

확인하였습니다.
작성하신 로직이 제가 이해한 것이 맞다면 큰 이견은 없습니다만 예외처리를 위한 구간은 많은 것 같아요. 서비스 중요 로직이 아니라면 null 값이 아예 안들어오게 만드는 경우가 가장 좋지 않을까 싶어요.

Comment on lines 68 to 74
if (requestDTO.getName() != null && !requestDTO.getName().equals(category.getName())) {
if(categoryRepository.existsByNameAndIdNot(requestDTO.getName(), id)) {
throw new CategoryAlreadyExistsException();
}
category.updateName(requestDTO.getName());
}

Choose a reason for hiding this comment

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

빈 string 에 대한 검사가 불가능 한 것 같아요. 위 CategoryUpdateRequestDTO 에도 @NotBlank 가 안걸려 있어서요.
String 의 null 값과 빈값을 동시 검출할 수 있는 StringUtils 에 isNotBlank 를 쓰시는건 어떨까요?
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#isNotBlank(java.lang.CharSequence)

Comment on lines +53 to +71
if (requestDTO.getName() != null && !requestDTO.getName().isEmpty()) {
this.name = requestDTO.getName();
}
if (requestDTO.getDescription() != null && !requestDTO.getDescription().isEmpty()) {
this.description = requestDTO.getDescription();
}
if (requestDTO.getPrice() != null) {
this.price = requestDTO.getPrice();
}
if (requestDTO.getStockQuantity() > 0) {
this.stockQuantity = requestDTO.getStockQuantity();
}
if (requestDTO.getImageUrl() != null && !requestDTO.getImageUrl().isEmpty()) {
this.imageUrl = requestDTO.getImageUrl();
}
if (newCategory != null) {
this.category = newCategory;
}
}

Choose a reason for hiding this comment

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

product 에서 바꾸고 싶지 않은 필드값은 ProductUpdateRequestDTO의 필드에 null 값을 주입해서 주기 위한, 일종의 편의성을 위한 로직인걸까요?

Comment on lines +63 to +75
@Transactional
public ProductDetailResponseDTO updateProduct(long id, ProductUpdateRequestDTO requestDTO) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));

Category category = requestDTO.getCategoryId() == null
? product.getCategory()
: categoryRepository.findById(requestDTO.getCategoryId())
.orElseThrow(CategoryNotFoundException::new);

product.update(requestDTO, category);
return new ProductDetailResponseDTO(product);
}

Choose a reason for hiding this comment

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

생각보다 탈출구가 많은 로직인 것 같아요. requestDTO 에 category 가 null 값일때를 가정하신 이유도 있나요? category를 변경하고 싶지 않을 때 requestDTO 에 null 값을 넣는다고 가정하신건가요?

Comment on lines +21 to 29
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));

return ResponseEntity.status(ex.getStatus())
.body(new ErrorResponse(ex.getStatus().value(), ex.getMessage(), ex.getErrorCode()));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(HttpStatus.BAD_REQUEST.value(), errorMessage, "VALIDATION_FAILED"));
}

Choose a reason for hiding this comment

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

👍👍👍

Comment on lines +8 to +30
@Getter
public class ApiResponse<T> {
private final int status;
private final String message;
private final T data;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
private final LocalDateTime timestamp;

public ApiResponse(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
this.timestamp = LocalDateTime.now();
}

public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "Success", data);
}

public static <T> ApiResponse<T> created(T data) {
return new ApiResponse<>(201, "Created", data);
}
}

Choose a reason for hiding this comment

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

👍👍👍👍👍👍

Copy link

sonarqubecloud bot commented Mar 4, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
4.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📬 API 서버 API 통신 ✨ Feature 기능 개발
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feat] 상품 및 카테고리 관련 API 구현
2 participants