Skip to content

Commit 1ed162f

Browse files
authored
Merge pull request #7 from Kimjipang/round03
Round03
2 parents 81a0579 + bb14bff commit 1ed162f

25 files changed

Lines changed: 607 additions & 10 deletions
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.loopers.application.like;
2+
3+
import com.loopers.domain.like.Like;
4+
import com.loopers.domain.like.LikeRepository;
5+
import com.loopers.domain.product.ProductRepository;
6+
import com.loopers.domain.user.UserRepository;
7+
import com.loopers.interfaces.api.like.LikeV1Dto;
8+
import com.loopers.support.error.CoreException;
9+
import com.loopers.support.error.ErrorType;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
@Component
15+
@RequiredArgsConstructor
16+
public class LikeFacade {
17+
private final LikeRepository likeRepository;
18+
private final UserRepository userRepository;
19+
private final ProductRepository productRepository;
20+
21+
@Transactional
22+
public LikeInfo doLike(LikeV1Dto.LikeRequest request) {
23+
/*
24+
- [ ] 사용자 검증
25+
- [ ] 상품 검증
26+
- [ ] 좋아요 등록 (멱등)
27+
*/
28+
Long userId = request.userId();
29+
Long productId = request.productId();
30+
31+
userRepository.findById(userId).orElseThrow(
32+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 유저입니다.")
33+
);
34+
35+
productRepository.findById(productId).orElseThrow(
36+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 상품입니다.")
37+
);
38+
39+
return likeRepository.findByUserIdAndProductId(userId, productId)
40+
.map(LikeInfo::from)
41+
.orElseGet(() -> {
42+
Like newLike = request.toEntity();
43+
likeRepository.save(newLike);
44+
45+
return LikeInfo.from(newLike);
46+
});
47+
}
48+
49+
@Transactional
50+
public void doUnlike(Long userId, Long productId) {
51+
/*
52+
- [ ] 사용자 검증
53+
- [ ] 상품 검증
54+
- [ ] 좋아요 취소 (멱등)
55+
*/
56+
57+
userRepository.findById(userId).orElseThrow(
58+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 유저입니다.")
59+
);
60+
61+
productRepository.findById(productId).orElseThrow(
62+
() -> new CoreException(ErrorType.NOT_FOUND, "존재하지 않는 상품입니다.")
63+
);
64+
65+
likeRepository.findByUserIdAndProductId(userId, productId)
66+
.ifPresent(likeRepository::delete);
67+
68+
}
69+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.loopers.application.like;
2+
3+
import com.loopers.domain.like.Like;
4+
5+
public record LikeInfo(Long id, Long userId, Long productId) {
6+
public static LikeInfo from(Like like) {
7+
return new LikeInfo(
8+
like.getId(),
9+
like.getUserId(),
10+
like.getProductId()
11+
);
12+
}
13+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.loopers.application.product;
2+
3+
import com.loopers.domain.like.LikeRepository;
4+
import com.loopers.domain.product.Product;
5+
import com.loopers.domain.product.ProductRepository;
6+
import com.loopers.domain.user.UserRepository;
7+
import com.loopers.interfaces.api.product.ProductV1Dto;
8+
import com.loopers.support.error.CoreException;
9+
import com.loopers.support.error.ErrorType;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
import java.util.List;
15+
16+
@Component
17+
@RequiredArgsConstructor
18+
public class ProductFacade {
19+
private final ProductRepository productRepository;
20+
private final LikeRepository likeRepository;
21+
22+
23+
@Transactional
24+
public ProductInfo registerProduct(ProductV1Dto.ProductRequest request) {
25+
Product product = request.toEntity();
26+
productRepository.save(product);
27+
28+
return ProductInfo.from(product, 0);
29+
}
30+
31+
@Transactional
32+
public List<ProductInfo> findAllProducts() {
33+
List<Product> products = productRepository.findAll();
34+
35+
return products.stream()
36+
.map(product -> {
37+
int likeCount = likeRepository.countByProductId(product.getId());
38+
return ProductInfo.from(product, likeCount);
39+
})
40+
.toList();
41+
}
42+
43+
@Transactional(readOnly = true)
44+
public ProductInfo findProductById(Long id) {
45+
Product product = productRepository.findById(id).orElseThrow(
46+
() -> new CoreException(ErrorType.NOT_FOUND, "찾고자 하는 상품이 존재하지 않습니다.")
47+
);
48+
49+
int likeCount = likeRepository.countByProductId(id);
50+
51+
return ProductInfo.from(product, likeCount);
52+
}
53+
54+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.loopers.application.product;
2+
3+
import com.loopers.domain.product.Product;
4+
5+
import java.math.BigDecimal;
6+
7+
public record ProductInfo(Long id, Long brandId, String name, BigDecimal price, int stock, int likeCount) {
8+
public static ProductInfo from(Product product, int likeCount) {
9+
return new ProductInfo(
10+
product.getId(),
11+
product.getBrandId(),
12+
product.getName(),
13+
product.getPrice(),
14+
product.getStock(),
15+
likeCount
16+
);
17+
}
18+
}

apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ public UserInfo signUp(UserV1Dto.SignUpRequest request) {
2828
}
2929

3030
@Transactional(readOnly = true)
31-
public UserInfo getUserByLoginId(String loginId) {
31+
public UserInfo findUserByLoginId(String loginId) {
3232
boolean isExist = userService.existsByLoginId(loginId);
3333

3434
if (!isExist) {
3535
throw new CoreException(ErrorType.NOT_FOUND, "해당 유저ID의 사용자가 존재하지 않습니다.");
3636
}
3737

38-
UserEntity userEntity = userService.getUserByLoginId(loginId);
38+
UserEntity userEntity = userService.findUserByLoginId(loginId);
3939
return UserInfo.from(userEntity);
4040
}
4141
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.loopers.domain.like;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import jakarta.persistence.Column;
5+
import jakarta.persistence.Entity;
6+
import jakarta.persistence.Table;
7+
import lombok.AccessLevel;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@Getter
12+
@Entity
13+
@Table(name = "product_like")
14+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
15+
public class Like extends BaseEntity {
16+
@Column(name = "ref_user_id", nullable = false)
17+
private Long userId;
18+
19+
@Column(name = "ref_product_id", nullable = false)
20+
private Long productId;
21+
22+
public Like(Long userId, Long productId) {
23+
this.userId = userId;
24+
this.productId = productId;
25+
}
26+
27+
public boolean isAlreadyLike(Like like) {
28+
if (like != null) {
29+
return true;
30+
}
31+
return false;
32+
}
33+
34+
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.loopers.domain.like;
2+
3+
import java.util.Optional;
4+
5+
public interface LikeRepository {
6+
Optional<Like> findByUserIdAndProductId(Long userId, Long productId);
7+
8+
Like save(Like like);
9+
10+
void delete(Like like);
11+
12+
int countByProductId(Long productId);
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.loopers.domain.product;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import com.loopers.support.error.CoreException;
5+
import com.loopers.support.error.ErrorType;
6+
import jakarta.persistence.Column;
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Table;
9+
import lombok.AccessLevel;
10+
import lombok.Getter;
11+
import lombok.NoArgsConstructor;
12+
13+
import java.math.BigDecimal;
14+
15+
@Getter
16+
@Entity
17+
@Table(name = "product")
18+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
19+
public class Product extends BaseEntity {
20+
@Column(name = "ref_brand_id", nullable = false)
21+
private Long brandId;
22+
23+
@Column(name = "name", nullable = false)
24+
private String name;
25+
26+
@Column(name = "price", nullable = false)
27+
private BigDecimal price;
28+
29+
@Column(name = "stock", nullable = false)
30+
private int stock;
31+
32+
33+
public Product(Long brandId, String name, BigDecimal price, int stock) {
34+
this.brandId = brandId;
35+
this.name = name;
36+
this.price = price;
37+
this.stock = stock;
38+
}
39+
40+
public Product changePrice(BigDecimal newPrice) {
41+
this.price = newPrice;
42+
return this;
43+
}
44+
45+
public Product decreaseStock(int amount) {
46+
if (amount <= 0) {
47+
throw new CoreException(ErrorType.BAD_REQUEST, "재고 감소량은 0보다 커야 합니다.");
48+
}
49+
if (this.stock < amount) {
50+
throw new CoreException(ErrorType.BAD_REQUEST, "재고가 부족합니다.");
51+
}
52+
this.stock -= amount;
53+
54+
return this;
55+
}
56+
57+
public boolean isInStock(int quantity) {
58+
return this.stock >= quantity;
59+
}
60+
61+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.loopers.domain.product;
2+
3+
import com.loopers.interfaces.api.product.ProductV1Dto;
4+
5+
import java.util.List;
6+
import java.util.Optional;
7+
8+
public interface ProductRepository {
9+
Optional<Product> findById(Long id);
10+
11+
Product save(Product product);
12+
13+
List<Product> findAll();
14+
15+
List<Product> searchProductsByCondition(ProductV1Dto.SearchProductRequest request);
16+
}

apps/commerce-api/src/main/java/com/loopers/domain/user/UserEntity.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
package com.loopers.domain.user;
22

33
import com.loopers.domain.BaseEntity;
4+
import com.loopers.support.error.CoreException;
5+
import com.loopers.support.error.ErrorType;
6+
import jakarta.persistence.Column;
47
import jakarta.persistence.Entity;
8+
import jakarta.persistence.EnumType;
9+
import jakarta.persistence.Enumerated;
510
import jakarta.persistence.Table;
611
import lombok.AccessLevel;
712
import lombok.Getter;
813
import lombok.NoArgsConstructor;
914

15+
import java.math.BigDecimal;
16+
1017
@Entity
11-
@Table(name = "user")
18+
@Table(name = "users")
1219
@Getter
1320
@NoArgsConstructor(access = AccessLevel.PROTECTED)
1421
public class UserEntity extends BaseEntity {
22+
@Column(unique = true, nullable = false)
1523
private String loginId;
24+
25+
@Column(unique = true, nullable = false)
1626
private String email;
27+
28+
@Enumerated(EnumType.STRING)
29+
@Column(nullable = false)
1730
private Gender gender;
31+
1832
private String birth;
33+
1934
private String password;
2035

36+
private BigDecimal pointBalance = BigDecimal.ZERO;
37+
2138
public UserEntity(String loginId, String email, Gender gender, String birth, String password) {
2239
UserValidator.validateLoginId(loginId);
2340
UserValidator.validateEmail(email);
@@ -31,4 +48,21 @@ public UserEntity(String loginId, String email, Gender gender, String birth, Str
3148
this.password = password;
3249
}
3350

51+
public void addPoints(BigDecimal amount) {
52+
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
53+
throw new CoreException(ErrorType.BAD_REQUEST, "포인트는 반드시 0보다 커야 합니다.");
54+
}
55+
this.pointBalance = this.pointBalance.add(amount);
56+
}
57+
58+
public void deductPoints(BigDecimal amount) {
59+
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
60+
throw new CoreException(ErrorType.BAD_REQUEST, "포인트는 반드시 0보다 커야 합니다.");
61+
}
62+
if (this.pointBalance.compareTo(amount) < 0) {
63+
throw new CoreException(ErrorType.BAD_REQUEST, "포인트 잔액이 부족합니다.");
64+
}
65+
this.pointBalance = this.pointBalance.subtract(amount);
66+
}
67+
3468
}

0 commit comments

Comments
 (0)