Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,8 @@ out/
### Kotlin ###
.kotlin

### OS ###
.DS_Store

/.claude
CLAUDE.md
13 changes: 13 additions & 0 deletions application/commerce-service/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
`java-library`
}

dependencies {
api(project(":domain"))

// @Service, @Transactional
implementation("org.springframework:spring-tx")
implementation("org.springframework:spring-context")

testImplementation(testFixtures(project(":domain")))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.loopers.application.example;

import com.loopers.domain.example.ExampleModel;
import com.loopers.domain.example.ExampleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class ExampleFacade {
private final ExampleService exampleService;

public ExampleInfo getExample(Long id) {
ExampleModel example = exampleService.getExample(id);
return ExampleInfo.from(example);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.loopers.application.example;

import com.loopers.domain.example.ExampleModel;

public record ExampleInfo(Long id, String name, String description) {
public static ExampleInfo from(ExampleModel model) {
return new ExampleInfo(
model.getId(),
model.getName(),
model.getDescription()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.loopers.application.service;

import com.loopers.application.service.dto.BrandCreateCommand;
import com.loopers.application.service.dto.BrandInfo;
import com.loopers.application.service.dto.BrandUpdateCommand;
import com.loopers.domain.catalog.BrandDeleteService;
import com.loopers.domain.catalog.brand.Brand;
import com.loopers.domain.catalog.brand.BrandExceptionMessage;
import com.loopers.domain.catalog.brand.BrandRepository;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class BrandService {

private final BrandRepository brandRepository;
private final BrandDeleteService brandDeleteService;

@Transactional
public void create(BrandCreateCommand command) {
if (brandRepository.existsByName(command.name())) {
throw new CoreException(ErrorType.CONFLICT,
BrandExceptionMessage.Brand.DUPLICATE_NAME.message());
}

Brand brand = Brand.register(command.name());
brandRepository.save(brand);
}

@Transactional(readOnly = true)
public BrandInfo getById(Long id) {
Brand brand = brandRepository.findById(id)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
BrandExceptionMessage.Brand.NOT_FOUND.message()));
return BrandInfo.from(brand);
}

@Transactional(readOnly = true)
public List<BrandInfo> getAll() {
return brandRepository.findAll().stream()
.map(BrandInfo::from)
.toList();
}

@Transactional(readOnly = true)
public List<BrandInfo> getActiveBrands() {
return brandRepository.findAllByDeletedAtIsNull().stream()
.map(BrandInfo::from)
.toList();
}

@Transactional
public void update(Long id, BrandUpdateCommand command) {
Brand brand = brandRepository.findById(id)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
BrandExceptionMessage.Brand.NOT_FOUND.message()));

if (brandRepository.existsByName(command.name())) {
throw new CoreException(ErrorType.CONFLICT,
BrandExceptionMessage.Brand.DUPLICATE_NAME.message());
}

brand.updateName(command.name());
}

@Transactional
public void delete(Long id) {
brandDeleteService.delete(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.loopers.application.service;

import com.loopers.application.service.dto.LikeRegisterCommand;
import com.loopers.application.service.dto.ProductInfo;
import com.loopers.domain.catalog.ActiveProductService;
import com.loopers.domain.catalog.brand.Brand;
import com.loopers.domain.catalog.brand.BrandRepository;
import com.loopers.domain.catalog.product.Product;
import com.loopers.domain.catalog.product.ProductRepository;
import com.loopers.domain.like.Like;
import com.loopers.domain.like.LikeExceptionMessage;
import com.loopers.domain.like.LikeRepository;
import com.loopers.domain.like.LikeSubjectType;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class LikeService {

private final LikeRepository likeRepository;
private final ActiveProductService activeProductService;
private final ProductRepository productRepository;
private final BrandRepository brandRepository;

// 좋아요를 등록한다

@Transactional
public void like(LikeRegisterCommand command) {
Product product = activeProductService.get(command.productId());

if (likeRepository.existsByMemberIdAndSubjectTypeAndSubjectId(
command.memberId(), LikeSubjectType.PRODUCT, command.productId())) {
throw new CoreException(ErrorType.BAD_REQUEST,
LikeExceptionMessage.Like.ALREADY_LIKED.message());
}

likeRepository.save(Like.mark(command.memberId(), LikeSubjectType.PRODUCT, command.productId()));
product.increaseLikesCount();
}

// 좋아요를 취소한다

@Transactional
public void unlike(Long memberId, Long productId) {
Like like = likeRepository.findByMemberIdAndSubjectTypeAndSubjectId(
memberId, LikeSubjectType.PRODUCT, productId)
.orElseThrow(() -> new CoreException(ErrorType.BAD_REQUEST,
LikeExceptionMessage.Like.NOT_LIKED.message()));

likeRepository.delete(like);

productRepository.findById(productId)
.ifPresent(Product::decreaseLikesCount);
}

// 내 좋아요 목록을 조회한다

@Transactional(readOnly = true)
public List<ProductInfo> getMyLikes(Long memberId) {
List<Like> likes = likeRepository.findByMemberIdAndSubjectType(memberId, LikeSubjectType.PRODUCT);

List<Long> productIds = likes.stream()
.map(Like::getSubjectId)
.toList();

List<Product> activeProducts = productRepository.findAllByIdIn(productIds).stream()
.filter(product -> !product.isDeleted())
.toList();

List<Long> brandIds = activeProducts.stream()
.map(Product::getBrandId)
.distinct()
.toList();

Map<Long, Brand> brandMap = brandRepository.findAllByIdIn(brandIds).stream()
.collect(Collectors.toMap(Brand::getId, Function.identity()));

return activeProducts.stream()
.map(product -> ProductInfo.from(product, brandMap.get(product.getBrandId())))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.loopers.application.service;

import com.loopers.application.service.dto.MemberRegisterCommand;
import com.loopers.application.service.dto.MemberInfo;
import com.loopers.domain.member.Member;
import com.loopers.domain.member.MemberExceptionMessage;
import com.loopers.domain.member.MemberRepository;
import com.loopers.domain.member.PasswordEncryptor;
import com.loopers.domain.member.vo.*;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class MemberService {

private final MemberRepository memberRepository;
private final PasswordEncryptor passwordEncryptor;

@Transactional
public void register(MemberRegisterCommand request) {
boolean isLoginIdAlreadyExists = memberRepository.existsByLoginId(request.loginId());

if (isLoginIdAlreadyExists) {
throw new CoreException(ErrorType.CONFLICT, MemberExceptionMessage.LoginId.DUPLICATE_ID_EXISTS.message());
}

Member member = Member.register(
LoginId.of(request.loginId()),
Password.of(request.password(), request.birthdate(), passwordEncryptor),
MemberName.of(request.name()),
request.birthdate(),
Email.of(request.email())
);
memberRepository.save(member);
}

@Transactional(readOnly = true)
public MemberInfo getMyInfo(String userId, String password) {
Member member = memberRepository.findByLoginId(userId)
.orElseThrow(() -> new CoreException(ErrorType.UNAUTHORIZED, MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message()));

if (!member.matchesPassword(password, passwordEncryptor)) {
throw new CoreException(ErrorType.UNAUTHORIZED, MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message());
}

return new MemberInfo(
member.getId(),
member.getLoginId(),
member.getName(),
member.getBirthDate(),
member.getEmail()
);
}

@Transactional
public void updatePassword(String userId, String currentPassword, String newPassword) {
Member member = memberRepository.findByLoginId(userId)
.orElseThrow(() -> new CoreException(ErrorType.UNAUTHORIZED, MemberExceptionMessage.ExistsMember.CANNOT_LOGIN.message()));

if (!member.matchesPassword(currentPassword, passwordEncryptor)) {
throw new CoreException(ErrorType.UNAUTHORIZED, MemberExceptionMessage.Password.PASSWORD_INCORRECT.message());
}

member.updatePassword(newPassword, passwordEncryptor);
}
}
Loading