-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat] 관리자 기능 추가 #103
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] 관리자 기능 추가 #103
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.ongil.backend.domain.admin.dto.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @Schema(description = "브랜드 수정 요청") | ||
| public class AdminBrandUpdateRequest { | ||
|
|
||
| @Schema(description = "브랜드명", example = "나이키") | ||
| private String name; | ||
|
|
||
| @Schema(description = "브랜드 설명", example = "스포츠 의류 브랜드") | ||
| private String description; | ||
|
|
||
| @Schema(description = "로고 이미지 URL", example = "https://example.com/logo.png") | ||
| private String logoImageUrl; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.ongil.backend.domain.admin.dto.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @Schema(description = "카테고리 수정 요청") | ||
| public class AdminCategoryUpdateRequest { | ||
|
|
||
| @Schema(description = "카테고리명", example = "상의") | ||
| private String name; | ||
|
|
||
| @Schema(description = "아이콘 URL", example = "https://example.com/icon.png") | ||
| private String iconUrl; | ||
|
|
||
| @Schema(description = "정렬 순서", example = "1") | ||
| private Integer displayOrder; | ||
|
|
||
| @Schema(description = "상위 카테고리 ID (하위 카테고리인 경우)", example = "1") | ||
| private Long parentCategoryId; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.ongil.backend.domain.admin.dto.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @Schema(description = "상품 옵션 수정 요청") | ||
| public class AdminProductOptionUpdateRequest { | ||
|
|
||
| @Schema(description = "사이즈", example = "M") | ||
| private String size; | ||
|
|
||
| @Schema(description = "색상", example = "화이트") | ||
| private String color; | ||
|
|
||
| @Schema(description = "재고 수량", example = "100") | ||
| private Integer stock; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.ongil.backend.domain.admin.dto.request; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.ongil.backend.domain.product.enums.ProductType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "상품 수정 요청") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class AdminProductUpdateRequest { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "상품명", example = "베이직 코튼 티셔츠") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String name; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "상품 설명", example = "편안한 착용감의 코튼 티셔츠입니다.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String description; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "가격", example = "29000") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Integer price; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "소재 정보", example = "면 100%") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String materialOriginal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "이미지 URL들 (쉼표로 구분)", example = "https://example.com/img1.jpg,https://example.com/img2.jpg") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String imageUrls; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "사이즈들 (쉼표로 구분)", example = "S,M,L,XL") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String sizes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "색상들 (쉼표로 구분)", example = "화이트,블랙,네이비") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String colors; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "할인율 (%)", example = "10") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Integer discountRate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "상품 타입", example = "NORMAL") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private ProductType productType; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "브랜드 ID", example = "1") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long brandId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Schema(description = "카테고리 ID", example = "1") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private Long categoryId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
커밋 메시지에 "주문 이력 보존을 위해 상품의 물리 삭제 기능 제거. 상품 판매 중단은 상품 수정 API를 통해 🐛 onSale 필드 추가 제안 `@Schema`(description = "카테고리 ID", example = "1")
private Long categoryId;
+
+ `@Schema`(description = "판매 여부", example = "true")
+ private Boolean onSale;
}As per coding guidelines, DTO 설계 검증: "누락된 필드 - API 응답에 필요한 필드가 빠져있는지 확인". 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,9 +4,13 @@ | |
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.ongil.backend.domain.admin.dto.request.AdminBrandCreateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminBrandUpdateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminCategoryCreateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminCategoryUpdateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminProductCreateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminProductOptionCreateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminProductOptionUpdateRequest; | ||
| import com.ongil.backend.domain.admin.dto.request.AdminProductUpdateRequest; | ||
| import com.ongil.backend.domain.brand.converter.BrandConverter; | ||
| import com.ongil.backend.domain.brand.dto.response.BrandResponse; | ||
| import com.ongil.backend.domain.brand.entity.Brand; | ||
|
|
@@ -127,4 +131,137 @@ public ProductOptionResponse createProductOption(AdminProductOptionCreateRequest | |
| .stockStatus(savedOption.getStockStatus()) | ||
| .build(); | ||
| } | ||
|
|
||
| // 브랜드 수정 | ||
| public BrandResponse updateBrand(Long brandId, AdminBrandUpdateRequest request) { | ||
| Brand brand = brandRepository.findById(brandId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.BRAND_NOT_FOUND)); | ||
|
|
||
| brand.updateBrand( | ||
| request.getName(), | ||
| request.getDescription(), | ||
| request.getLogoImageUrl() | ||
| ); | ||
|
|
||
| return brandConverter.toResponse(brand); | ||
| } | ||
|
|
||
| // 브랜드 삭제 | ||
| public void deleteBrand(Long brandId) { | ||
| Brand brand = brandRepository.findById(brandId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.BRAND_NOT_FOUND)); | ||
|
|
||
| // 해당 브랜드를 사용하는 상품이 있는지 확인 | ||
| if (productRepository.existsByBrandId(brandId)) { | ||
| throw new ValidationException(ErrorCode.CANNOT_DELETE_BRAND_WITH_PRODUCTS); | ||
| } | ||
|
|
||
| brandRepository.delete(brand); | ||
| } | ||
|
|
||
| // 카테고리 수정 | ||
| public CategorySimpleResponse updateCategory(Long categoryId, AdminCategoryUpdateRequest request) { | ||
| Category category = categoryRepository.findById(categoryId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORY_NOT_FOUND)); | ||
|
|
||
| Category parentCategory = null; | ||
| if (request.getParentCategoryId() != null) { | ||
| parentCategory = categoryRepository.findById(request.getParentCategoryId()) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORY_NOT_FOUND)); | ||
| } | ||
|
Comment on lines
+167
to
+171
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # AdminService.java의 updateCategory 메서드 전체 확인
fd "AdminService.java" --type f -x cat {}Repository: IT-Cotato/12th-OnGil-BE Length of output: 9362 🏁 Script executed: # Category.java 찾기 및 updateCategory 메서드 확인
fd "Category.java" --type f -x cat {}Repository: IT-Cotato/12th-OnGil-BE Length of output: 1924 🏁 Script executed: # ProductOptionResponse 확인 - converter 패턴 사용 여부
rg "ProductOptionResponse" --type java -A 5 | head -60Repository: IT-Cotato/12th-OnGil-BE Length of output: 6463 🏁 Script executed: # ProductConverter 전체 내용 확인
fd "ProductConverter.java" --type f -x cat {}Repository: IT-Cotato/12th-OnGil-BE Length of output: 4821
명시적인 "해제" 신호(예: 🤖 Prompt for AI Agents |
||
|
|
||
| category.updateCategory( | ||
| request.getName(), | ||
| request.getIconUrl(), | ||
| request.getDisplayOrder(), | ||
| parentCategory | ||
| ); | ||
|
|
||
| return categoryConverter.toSimpleResponse(category); | ||
| } | ||
|
|
||
| // 카테고리 삭제 | ||
| public void deleteCategory(Long categoryId) { | ||
| Category category = categoryRepository.findById(categoryId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORY_NOT_FOUND)); | ||
|
|
||
| // 해당 카테고리를 사용하는 상품이 있는지 확인 | ||
| if (productRepository.existsByCategoryId(categoryId)) { | ||
| throw new ValidationException(ErrorCode.CANNOT_DELETE_CATEGORY_WITH_PRODUCTS); | ||
| } | ||
|
|
||
| // 하위 카테고리가 있는지 확인 | ||
| if (categoryRepository.existsByParentCategoryId(categoryId)) { | ||
| throw new ValidationException(ErrorCode.CANNOT_DELETE_CATEGORY_WITH_SUBCATEGORIES); | ||
| } | ||
|
|
||
| categoryRepository.delete(category); | ||
| } | ||
|
|
||
| // 상품 수정 | ||
| public ProductSimpleResponse updateProduct(Long productId, AdminProductUpdateRequest request) { | ||
| Product product = productRepository.findById(productId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.PRODUCT_NOT_FOUND)); | ||
|
|
||
| Brand brand = null; | ||
| if (request.getBrandId() != null) { | ||
| brand = brandRepository.findById(request.getBrandId()) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.BRAND_NOT_FOUND)); | ||
| } | ||
|
|
||
| Category category = null; | ||
| if (request.getCategoryId() != null) { | ||
| category = categoryRepository.findById(request.getCategoryId()) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.CATEGORY_NOT_FOUND)); | ||
|
|
||
| // 상품은 하위 카테고리에만 등록 가능 | ||
| if (category.getParentCategory() == null) { | ||
| throw new ValidationException(ErrorCode.INVALID_CATEGORY); | ||
| } | ||
| } | ||
|
|
||
| product.updateProduct( | ||
| request.getName(), | ||
| request.getDescription(), | ||
| request.getPrice(), | ||
| request.getMaterialOriginal(), | ||
| request.getImageUrls(), | ||
| request.getSizes(), | ||
| request.getColors(), | ||
| request.getDiscountRate(), | ||
| request.getProductType(), | ||
| brand, | ||
| category | ||
| ); | ||
|
|
||
| return productConverter.toSimpleResponse(product); | ||
| } | ||
|
Comment on lines
+201
to
+238
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
PR 목표에 명시된 "상품 판매 중단은 상품 수정 API를 통해 🤖 Prompt for AI Agents |
||
|
|
||
| // 상품 옵션 수정 | ||
| public ProductOptionResponse updateProductOption(Long optionId, AdminProductOptionUpdateRequest request) { | ||
| ProductOption productOption = productOptionRepository.findById(optionId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.PRODUCT_OPTION_NOT_FOUND)); | ||
|
|
||
| productOption.updateProductOption( | ||
| request.getSize(), | ||
| request.getColor(), | ||
| request.getStock() | ||
| ); | ||
|
|
||
| return ProductOptionResponse.builder() | ||
| .optionId(productOption.getId()) | ||
| .size(productOption.getSize()) | ||
| .color(productOption.getColor()) | ||
| .stock(productOption.getStock()) | ||
| .stockStatus(productOption.getStockStatus()) | ||
| .build(); | ||
| } | ||
|
|
||
| // 상품 옵션 삭제 | ||
| public void deleteProductOption(Long optionId) { | ||
| ProductOption productOption = productOptionRepository.findById(optionId) | ||
| .orElseThrow(() -> new EntityNotFoundException(ErrorCode.PRODUCT_OPTION_NOT_FOUND)); | ||
|
|
||
| productOptionRepository.delete(productOption); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,4 +45,26 @@ public Category(String name, String iconUrl, Integer displayOrder, Category pare | |
| this.displayOrder = displayOrder; | ||
| this.parentCategory = parentCategory; | ||
| } | ||
|
|
||
| // 카테고리 정보 수정 | ||
| public void updateCategory(String name, String iconUrl, Integer displayOrder, Category parentCategory) { | ||
| if (name != null) { | ||
| this.name = name; | ||
| } | ||
| if (iconUrl != null) { | ||
| this.iconUrl = iconUrl; | ||
| } | ||
| if (displayOrder != null) { | ||
| this.displayOrder = displayOrder; | ||
| } | ||
| if (parentCategory != null) { | ||
| // 자기 참조(순환 참조) 방지 | ||
| if (parentCategory.getId().equals(this.id)) { | ||
| throw new com.ongil.backend.global.common.exception.ValidationException( | ||
| com.ongil.backend.global.common.exception.ErrorCode.CATEGORY_SELF_REFERENCE | ||
| ); | ||
| } | ||
| this.parentCategory = parentCategory; | ||
| } | ||
|
Comment on lines
+60
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 간접 순환 참조(A→B→A) 감지 누락 현재 자기 참조 방지 로직은 직접적인 자기 참조( 간접 순환을 방지하려면 부모 체인을 따라 올라가며 현재 카테고리가 이미 존재하는지 확인하는 로직이 필요합니다. ♻️ 순환 참조 방어 코드 예시 if (parentCategory != null) {
if (parentCategory.getId().equals(this.id)) {
- throw new com.ongil.backend.global.common.exception.ValidationException(
- com.ongil.backend.global.common.exception.ErrorCode.CATEGORY_SELF_REFERENCE
- );
+ throw new ValidationException(ErrorCode.CATEGORY_SELF_REFERENCE);
+ }
+ // 간접 순환 참조 방지
+ Category ancestor = parentCategory.getParentCategory();
+ while (ancestor != null) {
+ if (ancestor.getId().equals(this.id)) {
+ throw new ValidationException(ErrorCode.CATEGORY_SELF_REFERENCE);
+ }
+ ancestor = ancestor.getParentCategory();
}
this.parentCategory = parentCategory;
}참고: 이 체크가 Entity 내부에서 동작하려면 부모 체인이 이미 fetch된 상태여야 합니다. Lazy 로딩 환경에서는 🤖 Prompt for AI Agents |
||
| } | ||
|
Comment on lines
+48
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자기 참조(순환 참조) 방지 로직 누락
🛡️ 순환 참조 방어 코드 제안 if (parentCategory != null) {
+ if (parentCategory.getId().equals(this.id)) {
+ throw new IllegalArgumentException("자기 자신을 상위 카테고리로 설정할 수 없습니다.");
+ }
this.parentCategory = parentCategory;
}🤖 Prompt for AI Agents |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.