-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 룩 조회(스와이프, 상세) 및 좋아요,싫어요 기능 구현 #17
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
Conversation
- LookImage 엔티티용 JpaRepository 인터페이스 생성 - lookId 기반으로 displayOrder 오름차순 정렬 조회 메서드 추가
- LookImageRepository를 통한 이미지 조회 서비스 계층 구현 - lookId 기준으로 displayOrder 오름차순 정렬하여 반환
- look_products 테이블 매핑 엔티티 생성 - look_id + product_id에 대한 UniqueConstraint 설정
- LookProduct 엔티티용 JpaRepository 인터페이스 생성 - lookId 기준으로 displayOrder 오름차순 정렬 조회 메서드 추가
- LookProductRepository를 활용한 상품 조회 서비스 계층 구현 - lookId 기준으로 displayOrder 오름차순 정렬하여 반환
- 룩 리액션 기록 및 취소 기능 제공 - recordReaction: 사용자 존재 여부 검증 후 리액션 생성(중복 시 무시) - cancelReaction: 사용자와 룩 ID 기반 리액션 삭제
- LookReaction 엔티티용 JpaRepository 인터페이스 생성 - 사용자 ID와 룩 ID 기반 존재 여부 확인 메서드 추가 - 사용자 ID와 룩 ID 기반 삭제 메서드 추가 - LIKE 타입 리액션만 조건부 삭제하는 deleteLikeIfExists 쿼리 메서드 구현(@Modifying)
- createReactionIfAbsent: 동일 사용자·룩 조합 중복 방지 후 리액션 생성 - LIKE 타입일 경우 좋아요 수 증가 처리(incrementLikeCountIfNeeded) - deleteReaction: LIKE 리액션 우선 삭제 후 좋아요 수 감소, 그 외 리액션 일반 삭제
- findSwipeLooks: 특정 사용자가 반응하지 않은 룩을 cursor 기반으로 페이지네이션 조회 - incrementLike: 룩의 좋아요 수 1 증가 - decrementLike: 좋아요 수가 0보다 큰 경우 1 감소 - @Modifying 및 clearAutomatically/flushAutomatically 설정으로 데이터 변경 즉시 반영
- createLook: Look 엔티티 생성 후 이미지와 상품 추가, 저장 처리 - getSwipeLooks: 사용자 기준 미반응 룩을 페이지네이션 조회 - getLook: 룩 ID 기반 조회, 없을 경우 LOOK_NOT_FOUND 예외 발생
- 룩 리액션 기록 API 요청 바디 매핑용 DTO 정의 - reactionType 필드에 @NotNull 검증 적용 및 사용자 메시지 설정
- 룩 리액션 타입 정의 - LIKE, DISLIKE 두 가지 유형 제공
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (2)
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
5-14: likesCount는 도메인이 관리해야 합니다 — ProductCreateCommand 및 관련 매핑에서 완전 제거 필요생성 시점에 likesCount를 0으로 초기화하고 외부에서 주입하지 않도록 아래 항목을 수정해주세요:
• src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt
–val likesCount: Int프로퍼티 삭제
• src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt
–val likesCount: Int필드 및toCommand()매핑에서likesCount = likesCount제거
• src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt
–Product.newProduct(..., likesCount = cmd.likesCount)호출부에서 해당 인자 제거 (기본값 0 사용)
• 테스트 코드
– ProductServiceTest, Fixture 등에서likesCount = …부분 제거 또는 기본값 검증으로 수정예시 diff:
--- a/src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt +++ b/src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt @@ data class ProductCreateCommand( - val likesCount: Int,수정 후 빌드·테스트가 통과하는지, 외부로부터 likesCount가 주입되지 않는지 한 번 더 확인해주세요.
src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
101-107: 음수/0 주문 수량 방지 — 수량 유효성 검증 추가 필요
orderQuantity <= 0이면 재고가 증가하거나(음수) 무의미한 호출(0)이 됩니다. 즉시 방어로직을 추가하세요.fun deductStockForOrder(orderQuantity: Int) { + require(orderQuantity > 0) { "orderQuantity must be positive" } require(quantity >= orderQuantity) { ErrorMessage.OUT_OF_STOCK.format(id) } quantity -= orderQuantity }
♻️ Duplicate comments (1)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
68-76: 이미지 추가 로직의 순서 중복 위험성현재 구현에서는
start + idx + 1로 displayOrder를 계산하지만, 동시성 환경에서 images.size가 변경될 수 있어 displayOrder 중복이 발생할 가능성이 있습니다.데이터베이스 레벨에서
(look_id, display_order)유니크 제약을 추가하고, 애플리케이션 레벨에서도 동시성을 고려한 순서 할당 로직을 검토해야 합니다.
🧹 Nitpick comments (5)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (3)
40-49: 정렬 보장 강화: 입력 정렬에 의존하지 말고 displayOrder 기준으로 정렬상위 레이어에서 이미 정렬해 전달하는 계약일 수 있으나, 응답 조립부에서 한 번 더 정렬해 두면 회귀에 강해집니다.
- val productDtos = lookProducts.mapNotNull { lp -> + val productDtos = lookProducts + .sortedBy { it.displayOrder } + .mapNotNull { lp -> val p = productMap[lp.productId] ?: return@mapNotNull null ProductItemDto( productId = p.id, name = p.getName(), price = p.getPrice(), thumbnailUrl = p.getThumbnailUrl(), displayOrder = lp.displayOrder, ) - } + }
41-41: 누락된 productId 스킵 시 관측 가능성 확보 제안
return@mapNotNull null로 조용히 누락되면 데이터 불일치가 장기간 숨겨질 수 있습니다. 로그/메트릭(샘플링) 추가를 고려해 주세요.
57-57: 이미지 응답도 displayOrder 기준으로 정렬하여 안전성 확보입력 정렬 계약을 신뢰하지 않고, 응답 조립 시 정렬을 보장하면 회귀에 강합니다.
- images = images.map { ImageDto(it.imageUrl, it.displayOrder) }, + images = images + .sortedBy { it.displayOrder } + .map { ImageDto(it.imageUrl, it.displayOrder) },src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
69-70: 카테고리 반환 순서 결정 제안소비자 입장에서 순서가 안정적이면 다루기 쉽습니다. id 기준 정렬을 제안합니다.
- fun getProductCategories(): List<ProductCategory> = productCategories.toList() + fun getProductCategories(): List<ProductCategory> = + productCategories.sortedBy { it.category.id }src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
28-29: 이미지 URL 검증 강화imageUrls 필드에
@Size(min = 1)검증과 각 URL에@NotBlank검증을 적용한 것이 좋습니다. 추가로 URL 형식 검증(@URL)도 고려해볼 수 있습니다.URL 형식 검증을 추가하려면 다음과 같이 수정할 수 있습니다:
- val imageUrls: List<@NotBlank String>, + val imageUrls: List<@NotBlank @URL String>,
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
src/main/kotlin/com/dh/baro/look/application/LookFacade.kt(1 hunks)src/main/kotlin/com/dh/baro/look/domain/Look.kt(2 hunks)src/main/kotlin/com/dh/baro/look/domain/LookProduct.kt(1 hunks)src/main/kotlin/com/dh/baro/look/domain/repository/LookProductRepository.kt(1 hunks)src/main/kotlin/com/dh/baro/look/domain/vo/LookImageView.kt(1 hunks)src/main/kotlin/com/dh/baro/look/domain/vo/LookProductView.kt(1 hunks)src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt(1 hunks)src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt(1 hunks)src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt(1 hunks)src/main/kotlin/com/dh/baro/product/domain/Product.kt(2 hunks)src/main/kotlin/com/dh/baro/product/domain/service/ProductQueryService.kt(2 hunks)src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt(2 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt(1 hunks)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt(0 hunks)src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt(1 hunks)src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt(2 hunks)
💤 Files with no reviewable changes (1)
- src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt
🚧 Files skipped from review as they are similar to previous changes (6)
- src/main/kotlin/com/dh/baro/look/domain/repository/LookProductRepository.kt
- src/main/kotlin/com/dh/baro/look/domain/LookProduct.kt
- src/test/kotlin/com/dh/baro/product/domain/ProductServiceTest.kt
- src/main/kotlin/com/dh/baro/look/application/LookFacade.kt
- src/main/kotlin/com/dh/baro/look/presentation/swagger/LookSwagger.kt
- src/main/kotlin/com/dh/baro/product/domain/service/ProductQueryService.kt
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: codrin2
PR: S-BARO/server#17
File: src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt:38-59
Timestamp: 2025-08-12T13:52:29.423Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
📚 Learning: 2025-08-12T13:52:29.423Z
Learnt from: codrin2
PR: S-BARO/server#17
File: src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt:38-59
Timestamp: 2025-08-12T13:52:29.423Z
Learning: codrin2 prefers direct field access for immutable id properties (val) rather than using getters, reasoning that immutable fields pose less encapsulation risk.
Applied to files:
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt
🧬 Code Graph Analysis (10)
src/main/kotlin/com/dh/baro/look/domain/vo/LookProductView.kt (1)
src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)
src/main/kotlin/com/dh/baro/look/domain/vo/LookImageView.kt (3)
src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)
id(6-25)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
id(6-21)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (4)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt (2)
id(6-25)from(16-23)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)
id(6-25)src/main/kotlin/com/dh/baro/product/presentation/dto/CategoryResponse.kt (1)
id(5-16)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
id(6-21)
src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (1)
src/test/kotlin/com/dh/baro/order/Fixture.kt (1)
images(23-35)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (4)
src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)src/main/kotlin/com/dh/baro/order/presentation/OrderCreateRequest.kt (1)
message(8-25)src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (1)
productId(11-20)src/main/kotlin/com/dh/baro/cart/presentation/dto/AddItemRequest.kt (1)
message(6-11)
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (3)
src/main/kotlin/com/dh/baro/order/application/OrderCreateCommand.kt (2)
productId(11-20)userId(5-31)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (1)
id(6-25)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt (1)
id(6-25)
src/main/kotlin/com/dh/baro/product/domain/Product.kt (4)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (1)
addImage(78-85)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (2)
id(6-25)from(16-23)src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)src/main/kotlin/com/dh/baro/product/domain/ProductCategory.kt (1)
name(6-30)
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (3)
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductDetail.kt (2)
id(6-25)from(16-23)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductResponse.kt (1)
id(6-25)src/main/kotlin/com/dh/baro/product/presentation/dto/ProductListItem.kt (1)
id(6-21)
src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (2)
src/main/kotlin/com/dh/baro/cart/presentation/swagger/CartSwagger.kt (2)
summary(61-95)name(15-150)src/main/kotlin/com/dh/baro/order/presentation/swagger/OrderSwagger.kt (1)
summary(24-93)
src/main/kotlin/com/dh/baro/look/domain/Look.kt (2)
src/main/kotlin/com/dh/baro/product/domain/Product.kt (1)
addImage(81-88)src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt (1)
name(6-22)
🔇 Additional comments (18)
src/main/kotlin/com/dh/baro/look/domain/vo/LookProductView.kt (1)
3-6: VO 설계 적절 — 불필요한 노출 없이 최소 정보만 캡슐화Look-Product 관계에서 productId와 displayOrder만 담는 값 객체로 경계가 깔끔합니다. 도메인 캡슐화 의도와 맞고, 직렬화/테스트에도 용이합니다. LGTM.
src/main/kotlin/com/dh/baro/look/domain/vo/LookImageView.kt (1)
3-6: 단순/명확한 이미지 VO 정의 👍이미지 URL과 표시 순서만 노출되어 프레젠테이션 변환에 적합합니다. 별도 비즈니스 로직이 없어 직렬화와 정렬 처리도 간단합니다.
src/main/kotlin/com/dh/baro/product/application/ProductCreateCommand.kt (1)
12-14: 생성 플로우 확장에 맞춘 입력 확장 👍 (categoryIds, imageUrls 추가)카테고리/이미지 일괄 등록에 필요한 데이터가 커맨드로 잘 모아졌습니다. 파이프라인 단순화에 도움이 됩니다.
src/main/kotlin/com/dh/baro/look/presentation/dto/LookDetailResponse.kt (1)
38-38: 제품 매핑을 위한 Map 구성 OK — 불변 id 직접 접근 팀 선호와 일치
products.associateBy { it.id }는 성능/가독성 모두 적절합니다. 불변 id의 직접 접근은 Team Learning과도 합치합니다.src/main/kotlin/com/dh/baro/product/domain/Product.kt (2)
53-70: 도메인 캡슐화 적용 잘 됨쓰기 가능한 속성들을 private으로 감추고, 읽기는 게터로 노출한 방향이 적절합니다. 컬렉션도 직접 노출을 피하고 정렬/복제로 안전하게 반환하는 점 좋습니다.
81-88: ProductImage.of 팩토리 메서드 존재 확인 완료
src/main/kotlin/com/dh/baro/product/domain/ProductImage.kt파일의companion object내에fun of(product: Product, imageUrl: String, displayOrder: Int)가 정의되어 있어, 해당 호출로 인한 컴파일 오류는 발생하지 않습니다. 추가 조치 불필요합니다.src/main/kotlin/com/dh/baro/look/domain/Look.kt (6)
16-17: 필드 접근성 개선 확인User 엔티티 참조를 제거하고 creatorId로 변경한 것은 적절합니다. 도메인 간의 결합도를 줄이고 성능을 개선할 수 있습니다.
20-26: 캡슐화 개선가변 필드들을 private으로 변경하고 getter 메서드를 제공하여 캡슐화를 개선한 것이 좋습니다. 불변성 보장과 내부 상태 보호가 가능합니다.
31-46: 연관관계 매핑 적절함OneToMany 매핑에서 LAZY 로딩, CascadeType.ALL, orphanRemoval 설정이 적절하게 구성되었습니다. private 필드로 캡슐화도 잘 되어있습니다.
56-66: 정렬된 뷰 제공 로직 개선images와 products를 displayOrder로 정렬하여 View 객체로 변환하는 로직이 잘 구현되었습니다. 불변 컬렉션을 반환하여 외부에서 수정할 수 없도록 보호하고 있습니다.
87-97: 상품 추가 시 중복 제거 로직 우수productIds를 distinct()로 중복 제거하고, 이미 존재하는 상품은 filterNot으로 제외하는 로직이 잘 구현되었습니다. 멱등성을 보장하여 동일한 상품이 여러 번 추가되는 것을 방지합니다.
107-121: 팩토리 메서드 패턴 적용 우수companion object의 newLook 팩토리 메서드를 통해 Look 인스턴스 생성을 캡슐화하고, IdGenerator를 사용하여 ID를 생성하는 것이 좋은 설계입니다.
src/main/kotlin/com/dh/baro/product/domain/service/ProductService.kt (1)
25-26: 배치 처리 로직 개선개별 루프 대신
addImages()와addCategories()배치 메서드를 사용하여 성능을 개선하고 코드를 단순화한 것이 좋습니다.src/main/kotlin/com/dh/baro/product/application/ProductFacade.kt (1)
14-16: Command 패턴 적용으로 계층 분리 개선Request 객체 대신 ProductCreateCommand를 직접 받도록 변경하여 프레젠테이션 계층과 애플리케이션 계층 간의 책임을 명확히 분리한 것이 좋습니다.
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateResponse.kt (1)
11-14: 검증 결과: getName()으로만 접근 가능하므로 코멘트 무시
Product.name은private var로 선언되어 직접 접근(product.name)이 불가하며, 모든 DTO에서getName()을 사용 중이므로 접근 방식에 일관성이 이미 확보되어 있습니다. 추가 변경이 필요 없습니다.Likely an incorrect or invalid review comment.
src/main/kotlin/com/dh/baro/product/presentation/dto/ProductCreateRequest.kt (1)
31-42: Command 매핑 로직 적절함toCommand() 메서드를 통해 Request를 Command로 변환하는 로직이 명확하고 모든 필드를 적절히 매핑하고 있습니다.
src/main/kotlin/com/dh/baro/product/presentation/ProductController.kt (1)
44-44: 타입 정합성 확인 완료
getLikesCount(): Int, PopularCursor.likes: Int, getPopularProducts의 cursorLikes: Int? 간 타입 호환성에 문제가 없습니다.src/main/kotlin/com/dh/baro/product/presentation/swagger/ProductSwagger.kt (1)
55-55: 컨트롤러와 반환 타입(ProductCreateResponse) 정합성 확보 — 좋습니다.
Issue Number
#16
As-Is
To-Be
Look 도메인 리팩토링
LookProduct,LookImage) 도입Reaction 기능 추가
LookReaction엔티티 및ReactionTypeenum 도입LookReactionFacade,LookReactionService로 유스케이스 로직 분리ProductLike 개선
API 및 Swagger 문서화
CORS 설정 개선
allowedOriginPatterns("*"),allowCredentials(true)적용테스트 코드
✅ Check List
📸 Test Screenshot
Additional Description