Skip to content

Commit 8016219

Browse files
committed
feat/ #18-clothes-favorite
1 parent e03b361 commit 8016219

6 files changed

Lines changed: 67 additions & 1 deletion

File tree

src/main/java/com/closetnangam/be/domain/clothes/controller/ClothesController.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.closetnangam.be.domain.clothes.controller;
22

33
import com.closetnangam.be.domain.clothes.dto.request.ClothesCreateRequest;
4+
import com.closetnangam.be.domain.clothes.dto.request.ClothesFavoriteRequest;
45
import com.closetnangam.be.domain.clothes.dto.request.ClothesUpdateRequest;
56
import com.closetnangam.be.domain.clothes.dto.response.ClothesResponse;
67
import com.closetnangam.be.domain.clothes.service.ClothesService;
@@ -35,6 +36,12 @@ public ResponseEntity<ApiResponse<List<ClothesResponse>>> getOwnedClothes(@PathV
3536
return ResponseEntity.ok(ApiResponse.ok(clothesService.getOwnedClothes(userId)));
3637
}
3738

39+
@Operation(summary = "즐겨찾기 옷 목록 조회", description = "즐겨찾기로 표시한 보유 옷 목록을 조회합니다.")
40+
@GetMapping("/api/users/{userId}/clothes/favorites")
41+
public ResponseEntity<ApiResponse<List<ClothesResponse>>> getFavoriteOwnedClothes(@PathVariable Long userId) {
42+
return ResponseEntity.ok(ApiResponse.ok(clothesService.getFavoriteOwnedClothes(userId)));
43+
}
44+
3845
@Operation(summary = "옷 상세 조회", description = "옷 이미지, 이름, 브랜드, 카테고리, 타입, 색상, 스타일 정보를 조회합니다.")
3946
@GetMapping("/api/clothes/{clothesId}")
4047
public ResponseEntity<ApiResponse<ClothesResponse>> getClothes(@PathVariable Long clothesId) {
@@ -52,6 +59,15 @@ public ResponseEntity<ApiResponse<ClothesResponse>> createOwnedClothes(
5259
.body(ApiResponse.ok(clothesService.createOwnedClothes(userId, request)));
5360
}
5461

62+
@Operation(summary = "옷 즐겨찾기 설정", description = "옷의 즐겨찾기 상태를 등록/해제합니다.")
63+
@PatchMapping("/api/clothes/{clothesId}/favorite")
64+
public ResponseEntity<ApiResponse<ClothesResponse>> updateFavorite(
65+
@PathVariable Long clothesId,
66+
@Valid @RequestBody ClothesFavoriteRequest request
67+
) {
68+
return ResponseEntity.ok(ApiResponse.ok(clothesService.updateFavorite(clothesId, request)));
69+
}
70+
5571
@Operation(summary = "옷 정보 수정", description = "등록된 보유 옷 정보를 수정합니다.")
5672
@PatchMapping("/api/clothes/{clothesId}")
5773
public ResponseEntity<ApiResponse<ClothesResponse>> updateClothes(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.closetnangam.be.domain.clothes.dto.request;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
5+
public record ClothesFavoriteRequest(
6+
@NotNull Boolean isFavorite
7+
) {
8+
}

src/main/java/com/closetnangam/be/domain/clothes/dto/response/ClothesResponse.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public record ClothesResponse(
2525
String externalProductId,
2626
String externalProductUrl,
2727
Boolean isVerified,
28+
Boolean isFavorite,
2829
LocalDateTime createdAt,
2930
LocalDateTime updatedAt
3031
) {
@@ -62,6 +63,7 @@ public static ClothesResponse from(Clothes clothes) {
6263
clothes.getExternalProductId(),
6364
clothes.getExternalProductUrl(),
6465
clothes.getIsVerified(),
66+
clothes.getIsFavorite(),
6567
clothes.getCreatedAt(),
6668
clothes.getUpdatedAt()
6769
);

src/main/java/com/closetnangam/be/domain/clothes/entity/Clothes.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ public class Clothes extends BaseEntity {
7676
@Column(name = "is_verified", nullable = false)
7777
private Boolean isVerified;
7878

79+
@Column(name = "is_favorite", nullable = false)
80+
private Boolean isFavorite = false;
81+
7982
@OneToMany(mappedBy = "clothes", cascade = CascadeType.ALL, orphanRemoval = true)
8083
private List<ClothesStyleTag> styleTags = new ArrayList<>();
8184

@@ -93,7 +96,8 @@ private Clothes(
9396
String externalSource,
9497
String externalProductId,
9598
String externalProductUrl,
96-
Boolean isVerified
99+
Boolean isVerified,
100+
Boolean isFavorite
97101
) {
98102
this.wardrobe = wardrobe;
99103
this.name = name;
@@ -108,6 +112,7 @@ private Clothes(
108112
this.externalProductId = externalProductId;
109113
this.externalProductUrl = externalProductUrl;
110114
this.isVerified = isVerified;
115+
this.isFavorite = isFavorite != null ? isFavorite : false;
111116
}
112117

113118
public void update(
@@ -138,4 +143,8 @@ public void replaceStyleTags(List<ClothesStyleTag> newStyleTags) {
138143
public void addStyleTag(ClothesStyleTag styleTag) {
139144
this.styleTags.add(styleTag);
140145
}
146+
147+
public void updateFavorite(Boolean isFavorite) {
148+
this.isFavorite = isFavorite;
149+
}
141150
}

src/main/java/com/closetnangam/be/domain/clothes/repository/ClothesRepository.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,20 @@ List<Clothes> findAllByUserIdAndSourceType(
3434
where c.id = :clothesId
3535
""")
3636
Optional<Clothes> findByIdWithDetails(@Param("clothesId") Long clothesId);
37+
38+
@Query("""
39+
select distinct c from Clothes c
40+
join fetch c.wardrobe w
41+
join fetch w.user
42+
left join fetch c.styleTags st
43+
left join fetch st.style
44+
where w.user.id = :userId
45+
and c.sourceType = :sourceType
46+
and c.isFavorite = true
47+
order by c.updatedAt desc
48+
""")
49+
List<Clothes> findFavoritesByUserIdAndSourceType(
50+
@Param("userId") Long userId,
51+
@Param("sourceType") SourceType sourceType
52+
);
3753
}

src/main/java/com/closetnangam/be/domain/clothes/service/ClothesService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.closetnangam.be.domain.catalog.repository.StyleRepository;
44
import com.closetnangam.be.domain.catalog.service.CategoryCatalogService;
55
import com.closetnangam.be.domain.clothes.dto.request.ClothesCreateRequest;
6+
import com.closetnangam.be.domain.clothes.dto.request.ClothesFavoriteRequest;
67
import com.closetnangam.be.domain.clothes.dto.request.ClothesUpdateRequest;
78
import com.closetnangam.be.domain.clothes.dto.response.ClothesResponse;
89
import com.closetnangam.be.domain.clothes.entity.Clothes;
@@ -35,6 +36,12 @@ public List<ClothesResponse> getOwnedClothes(Long userId) {
3536
.toList();
3637
}
3738

39+
public List<ClothesResponse> getFavoriteOwnedClothes(Long userId) {
40+
return clothesRepository.findFavoritesByUserIdAndSourceType(userId, SourceType.OWNED).stream()
41+
.map(ClothesResponse::from)
42+
.toList();
43+
}
44+
3845
public ClothesResponse getClothes(Long clothesId) {
3946
Clothes clothes = getClothesWithDetails(clothesId);
4047
return ClothesResponse.from(clothes);
@@ -60,6 +67,7 @@ public ClothesResponse createOwnedClothes(Long userId, ClothesCreateRequest requ
6067
.externalProductId(EXTERNAL_NONE)
6168
.externalProductUrl(EXTERNAL_NONE)
6269
.isVerified(request.isVerified())
70+
.isFavorite(false)
6371
.build();
6472

6573
applyStyleTags(clothes, request.styles());
@@ -88,6 +96,13 @@ public ClothesResponse updateClothes(Long clothesId, ClothesUpdateRequest reques
8896
return ClothesResponse.from(clothes);
8997
}
9098

99+
@Transactional
100+
public ClothesResponse updateFavorite(Long clothesId, ClothesFavoriteRequest request) {
101+
Clothes clothes = getClothesWithDetails(clothesId);
102+
clothes.updateFavorite(request.isFavorite());
103+
return ClothesResponse.from(clothes);
104+
}
105+
91106
@Transactional
92107
public void deleteClothes(Long clothesId) {
93108
Clothes clothes = getClothesWithDetails(clothesId);

0 commit comments

Comments
 (0)