Skip to content

Commit cce9470

Browse files
Merge pull request #134 from IT-Cotato/feature/128
[Fix] 검색 오류, 리뷰 작성 기능 수정
2 parents a14d643 + a885e71 commit cce9470

File tree

12 files changed

+178
-54
lines changed

12 files changed

+178
-54
lines changed

src/main/java/com/ongil/backend/domain/product/service/ProductService.java

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -193,18 +193,9 @@ public ProductSearchPageResDto getProducts(
193193

194194
// 추천 검색어에 이용하기 위한 과정
195195
if (hasQuery && !products.isEmpty()) {
196-
Product firstProduct = products.getContent().get(0);
197-
String savedKeyword = null;
198-
199-
if (firstProduct.getBrand() != null && firstProduct.getBrand().getName() != null) {
200-
savedKeyword = firstProduct.getBrand().getName();
201-
}
202-
else if (firstProduct.getCategory() != null && firstProduct.getCategory().getName() != null) {
203-
savedKeyword = firstProduct.getCategory().getName();
204-
}
205-
206-
if (savedKeyword != null && !savedKeyword.isBlank()) {
207-
recordSearchSideEffects(savedKeyword, userId);
196+
String representativeKeyword = searchService.extractRepresentativeKeyword(query);
197+
if (representativeKeyword != null && !representativeKeyword.isBlank()) {
198+
recordSearchSideEffects(representativeKeyword, userId);
208199
}
209200
}
210201

src/main/java/com/ongil/backend/domain/review/converter/ReviewConverter.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@
33
import java.util.Arrays;
44
import java.util.Collections;
55
import java.util.List;
6+
import java.util.Optional;
67
import java.util.stream.Collectors;
78

89
import org.springframework.stereotype.Component;
910

11+
import com.ongil.backend.domain.category.entity.Category;
1012
import com.ongil.backend.domain.order.entity.OrderItem;
1113
import com.ongil.backend.domain.product.entity.Product;
1214
import com.ongil.backend.domain.review.dto.response.MyReviewResponse;
1315
import com.ongil.backend.domain.review.dto.response.PendingReviewResponse;
1416
import com.ongil.backend.domain.review.dto.response.ReviewDetailResponse;
1517
import com.ongil.backend.domain.review.dto.response.ReviewListResponse;
1618
import com.ongil.backend.domain.review.entity.Review;
19+
import com.ongil.backend.domain.review.enums.ClothingCategory;
20+
import com.ongil.backend.domain.review.enums.MaterialAnswer;
1721
import com.ongil.backend.domain.review.enums.ReviewType;
22+
import com.ongil.backend.domain.review.enums.SizeAnswer;
1823
import com.ongil.backend.domain.user.entity.User;
1924

2025
@Component
@@ -83,6 +88,7 @@ public ReviewDetailResponse toDetailResponse(Review review, boolean isHelpful) {
8388

8489
ReviewDetailResponse.ReviewDetailResponseBuilder builder = ReviewDetailResponse.builder()
8590
.reviewId(review.getId())
91+
.reviewStatus(review.getReviewStatus().name())
8692
.reviewType(review.getReviewType().name())
8793
.rating(review.getRating())
8894
.helpfulCount(review.getHelpfulCount())
@@ -132,25 +138,46 @@ private ReviewDetailResponse.ProductInfo buildProductInfo(Product product) {
132138
return ReviewDetailResponse.ProductInfo.builder()
133139
.productId(product.getId())
134140
.productName(product.getName())
141+
.clothingCategory(resolveClothingCategory(product))
135142
.brandName(product.getBrand() != null ? product.getBrand().getName() : null)
136143
.thumbnailImageUrl(getFirstImage(product.getImageUrls()))
137144
.build();
138145
}
139146

140147
// 1차 리뷰 답변 구성
141148
private ReviewDetailResponse.InitialFirstAnswers buildInitialFirstAnswers(Review review) {
149+
if (review.getSizeAnswer() == null && review.getColorAnswer() == null && review.getMaterialAnswer() == null) {
150+
return null;
151+
}
152+
153+
SizeAnswer sizeAnswer = review.getSizeAnswer();
154+
MaterialAnswer materialAnswer = review.getMaterialAnswer();
155+
156+
String sizeSecondaryType = null;
157+
if (sizeAnswer != null && sizeAnswer.isNeedsSecondaryQuestion()) {
158+
sizeSecondaryType = (sizeAnswer == SizeAnswer.LOOSE || sizeAnswer == SizeAnswer.TOO_BIG_NEED_ALTERATION)
159+
? "POSITIVE" : "NEGATIVE";
160+
}
161+
162+
String materialSecondaryType = null;
163+
if (materialAnswer != null && materialAnswer.isNeedsSecondaryQuestion()) {
164+
materialSecondaryType = materialAnswer.isPositive() ? "POSITIVE" : "NEGATIVE";
165+
}
166+
142167
return ReviewDetailResponse.InitialFirstAnswers.builder()
143-
.sizeAnswer(review.getSizeAnswer().getDisplayName())
144-
.colorAnswer(review.getColorAnswer().getDisplayName())
145-
.materialAnswer(review.getMaterialAnswer().getDisplayName())
168+
.sizeAnswer(sizeAnswer != null ? sizeAnswer.name() : null)
169+
.sizeSecondaryType(sizeSecondaryType)
170+
.colorAnswer(review.getColorAnswer() != null ? review.getColorAnswer().name() : null)
171+
.materialAnswer(materialAnswer != null ? materialAnswer.name() : null)
172+
.materialSecondaryType(materialSecondaryType)
146173
.build();
147174
}
148175

149176
// 2차 리뷰 답변 구성
150177
private ReviewDetailResponse.InitialSecondAnswers buildInitialSecondAnswers(Review review) {
151178
return ReviewDetailResponse.InitialSecondAnswers.builder()
152-
.fitIssueParts(review.getFitIssueParts())
153-
.materialFeatures(review.getMaterialFeatures())
179+
.fitIssueParts(parseToList(review.getFitIssueParts(), ","))
180+
.materialFeatures(parseToList(review.getMaterialFeatures(), ","))
154181
.build();
155182
}
156183

@@ -225,13 +252,15 @@ private MyReviewResponse.OneMonthAnswers buildMyOneMonthAnswers(Review review) {
225252
작성 가능한 리뷰 응답 변환
226253
*/
227254
public PendingReviewResponse toPendingReviewResponse(
228-
OrderItem orderItem, ReviewType availableType
255+
OrderItem orderItem, ReviewType availableType, Review draftReview
229256
) {
230257
Product product = orderItem.getProduct();
231258

232259
return PendingReviewResponse.builder()
233260
.orderItemId(orderItem.getId())
234261
.availableReviewType(availableType.name())
262+
.reviewStatus(draftReview != null ? draftReview.getReviewStatus().name() : null)
263+
.reviewId(draftReview != null ? draftReview.getId() : null)
235264
.product(buildPendingProductInfo(product))
236265
.purchaseOption(formatPurchaseOption(orderItem))
237266
.orderedAt(orderItem.getOrder().getCreatedAt())
@@ -248,11 +277,20 @@ private PendingReviewResponse.ProductInfo buildPendingProductInfo(Product produc
248277
return PendingReviewResponse.ProductInfo.builder()
249278
.productId(product.getId())
250279
.productName(product.getName())
280+
.clothingCategory(resolveClothingCategory(product))
251281
.brandName(product.getBrand() != null ? product.getBrand().getName() : null)
252282
.thumbnailImageUrl(getFirstImage(product.getImageUrls()))
253283
.build();
254284
}
255285

286+
private ClothingCategory resolveClothingCategory(Product product) {
287+
return Optional.ofNullable(product.getCategory())
288+
.map(Category::getParentCategory)
289+
.map(Category::getName)
290+
.map(ClothingCategory::fromDisplayName)
291+
.orElse(null);
292+
}
293+
256294
// 공통 유틸리티 메서드
257295
private List<String> parseImageUrls(String imageUrls) {
258296
if (imageUrls == null || imageUrls.trim().isEmpty()) {

src/main/java/com/ongil/backend/domain/review/converter/ReviewWriteConverter.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.ongil.backend.domain.review.enums.ClothingCategory;
1414
import com.ongil.backend.domain.review.enums.ReviewStatus;
1515
import com.ongil.backend.domain.review.enums.ReviewType;
16+
import com.ongil.backend.domain.review.enums.SizeAnswer;
1617
import com.ongil.backend.domain.user.entity.User;
1718

1819
@Component
@@ -33,14 +34,28 @@ public ReviewStep1Response toStep1Response(Review review) {
3334
boolean needsSizeQ = review.getSizeAnswer() != null && review.getSizeAnswer().isNeedsSecondaryQuestion();
3435
boolean needsMaterialQ = review.getMaterialAnswer() != null && review.getMaterialAnswer().isNeedsSecondaryQuestion();
3536

37+
String sizeSecondaryType = null;
38+
if (needsSizeQ) {
39+
SizeAnswer sizeAnswer = review.getSizeAnswer();
40+
sizeSecondaryType = (sizeAnswer == SizeAnswer.LOOSE || sizeAnswer == SizeAnswer.TOO_BIG_NEED_ALTERATION)
41+
? "POSITIVE" : "NEGATIVE";
42+
}
43+
44+
String materialSecondaryType = null;
45+
if (needsMaterialQ) {
46+
materialSecondaryType = review.getMaterialAnswer().isPositive() ? "POSITIVE" : "NEGATIVE";
47+
}
48+
3649
List<String> availableBodyParts = (needsSizeQ && review.getClothingCategory() != null)
3750
? review.getClothingCategory().getBodyParts()
3851
: Collections.emptyList();
3952

4053
return ReviewStep1Response.of(
4154
review.getId(),
4255
needsSizeQ,
56+
sizeSecondaryType,
4357
needsMaterialQ,
58+
materialSecondaryType,
4459
availableBodyParts
4560
);
4661
}

src/main/java/com/ongil/backend/domain/review/dto/response/PendingReviewResponse.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import java.time.LocalDateTime;
44

5+
import com.ongil.backend.domain.review.enums.ClothingCategory;
6+
57
import io.swagger.v3.oas.annotations.media.Schema;
68
import lombok.Builder;
79
import lombok.Getter;
@@ -16,6 +18,12 @@ public class PendingReviewResponse {
1618

1719
@Schema(description = "작성 가능한 리뷰 타입 (INITIAL / ONE_MONTH)")
1820
private String availableReviewType;
21+
22+
@Schema(description = "작성 중인 리뷰 ID (DRAFT 상태일 때만 존재)")
23+
private Long reviewId;
24+
25+
@Schema(description = "작성 중인 리뷰 상태 (DRAFT: 작성 중 / null: 미시작)")
26+
private String reviewStatus;
1927

2028
@Schema(description = "상품 정보")
2129
private ProductInfo product;
@@ -40,6 +48,9 @@ public static class ProductInfo {
4048
@Schema(description = "상품명")
4149
private String productName;
4250

51+
@Schema(description = "상위 카테고리")
52+
private ClothingCategory clothingCategory;
53+
4354
@Schema(description = "브랜드명")
4455
private String brandName;
4556

src/main/java/com/ongil/backend/domain/review/dto/response/ReviewDetailResponse.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.time.LocalDateTime;
44
import java.util.List;
55

6+
import com.ongil.backend.domain.review.enums.ClothingCategory;
7+
68
import io.swagger.v3.oas.annotations.media.Schema;
79
import lombok.Builder;
810
import lombok.Getter;
@@ -15,6 +17,9 @@ public class ReviewDetailResponse {
1517
@Schema(description = "리뷰 ID")
1618
private Long reviewId;
1719

20+
@Schema(description = "리뷰 상태 (DRAFT: 작성 중 / COMPLETED: 작성 완료)")
21+
private String reviewStatus;
22+
1823
@Schema(description = "리뷰 타입 (INITIAL / ONE_MONTH)")
1924
private String reviewType;
2025

@@ -107,6 +112,9 @@ public static class ProductInfo {
107112
@Schema(description = "상품명")
108113
private String productName;
109114

115+
@Schema(description = "상위 카테고리")
116+
private ClothingCategory clothingCategory;
117+
110118
@Schema(description = "브랜드명")
111119
private String brandName;
112120

@@ -119,26 +127,32 @@ public static class ProductInfo {
119127
@Schema(description = "구매 직후 리뷰 - 1차 질문 답변 (사이즈/색감/소재)")
120128
public static class InitialFirstAnswers {
121129

122-
@Schema(description = "사이즈 답변")
130+
@Schema(description = "사이즈 답변 enum (TIGHT_IMMEDIATELY / TIGHT_WHEN_MOVING / COMFORTABLE / LOOSE / TOO_BIG_NEED_ALTERATION)")
123131
private String sizeAnswer;
124132

125-
@Schema(description = "색감 답변")
133+
@Schema(description = "사이즈 2차 질문 방향 (POSITIVE / NEGATIVE / null)")
134+
private String sizeSecondaryType;
135+
136+
@Schema(description = "색감 답변 enum (BRIGHTER_THAN_SCREEN / SAME_AS_SCREEN / DARKER_THAN_SCREEN)")
126137
private String colorAnswer;
127138

128-
@Schema(description = "소재 답변")
139+
@Schema(description = "소재 답변 enum (VERY_GOOD / GOOD / NORMAL / BAD / VERY_BAD)")
129140
private String materialAnswer;
141+
142+
@Schema(description = "소재 2차 질문 방향 (POSITIVE / NEGATIVE / null)")
143+
private String materialSecondaryType;
130144
}
131145

132146
@Getter
133147
@Builder
134148
@Schema(description = "구매 직후 리뷰 - 2차 질문 답변 (핏/소재특징)")
135149
public static class InitialSecondAnswers {
136150

137-
@Schema(description = "핏 문제 부위")
138-
private String fitIssueParts;
151+
@Schema(description = "핏 문제 부위 목록")
152+
private List<String> fitIssueParts;
139153

140-
@Schema(description = "소재 특징")
141-
private String materialFeatures;
154+
@Schema(description = "소재 특징 목록")
155+
private List<String> materialFeatures;
142156
}
143157

144158
@Getter

src/main/java/com/ongil/backend/domain/review/dto/response/ReviewStep1Response.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44

5+
import io.swagger.v3.oas.annotations.media.Schema;
56
import lombok.AllArgsConstructor;
67
import lombok.Builder;
78
import lombok.Getter;
@@ -13,14 +14,19 @@ public class ReviewStep1Response {
1314

1415
private Long reviewId;
1516
private boolean needsSizeSecondaryQuestion;
17+
private String sizeSecondaryType;
1618
private boolean needsMaterialSecondaryQuestion;
19+
private String materialSecondaryType;
1720
private List<String> availableBodyParts;
18-
19-
public static ReviewStep1Response of(Long reviewId, boolean needsSizeQ, boolean needsMaterialQ, List<String> availableBodyParts) {
21+
22+
public static ReviewStep1Response of(Long reviewId, boolean needsSizeQ, String sizeSecondaryType,
23+
boolean needsMaterialQ, String materialSecondaryType, List<String> availableBodyParts) {
2024
return ReviewStep1Response.builder()
2125
.reviewId(reviewId)
2226
.needsSizeSecondaryQuestion(needsSizeQ)
27+
.sizeSecondaryType(sizeSecondaryType)
2328
.needsMaterialSecondaryQuestion(needsMaterialQ)
29+
.materialSecondaryType(materialSecondaryType)
2430
.availableBodyParts(availableBodyParts)
2531
.build();
2632
}

src/main/java/com/ongil/backend/domain/review/entity/Review.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@
2121
import lombok.NoArgsConstructor;
2222

2323
@Entity
24-
@Table(name = "reviews")
24+
@Table(name = "reviews",
25+
uniqueConstraints = {
26+
@UniqueConstraint(columnNames = {"order_item_id", "review_type", "review_status"})
27+
}
28+
)
2529
@NoArgsConstructor(access = AccessLevel.PROTECTED)
2630
@Getter
2731
@AllArgsConstructor

src/main/java/com/ongil/backend/domain/review/repository/ReviewRepository.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@ Page<Review> findByUserIdAndReviewStatusAndReviewType(
182182
// N+1 문제 해결: 여러 OrderItem에 대해 리뷰 작성된 ID 목록 한번에 조회
183183
@Query("SELECT r.orderItem.id FROM Review r " +
184184
"WHERE r.orderItem.id IN :orderItemIds " +
185-
"AND r.reviewType = :reviewType")
185+
"AND r.reviewType = :reviewType " +
186+
"AND r.reviewStatus = 'COMPLETED'")
186187
List<Long> findReviewedOrderItemIds(
187188
@Param("orderItemIds") List<Long> orderItemIds,
188189
@Param("reviewType") ReviewType reviewType
@@ -201,4 +202,6 @@ boolean existsByOrderItemIdAndReviewTypeAndReviewStatus(
201202
ReviewType reviewType,
202203
ReviewStatus reviewStatus
203204
);
205+
206+
Optional<Review> findByOrderItemIdAndReviewTypeAndReviewStatus(Long orderItemId, ReviewType reviewType, ReviewStatus reviewStatus);
204207
}

0 commit comments

Comments
 (0)