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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.HashMap;
import java.util.Map;

import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
Expand All @@ -26,6 +28,13 @@ public ResponseEntity<?> handleValidationException(MethodArgumentNotValidExcepti
map.put("message", ErrorCode.INPUT_REQUIRED_PARAMETER.errorMessage);
return ResponseEntity.status(ErrorCode.INPUT_REQUIRED_PARAMETER.statusCode).body(map);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleValidationException(ConstraintViolationException e) {
Map<String, Object> map = new HashMap<>();
map.put("code", ErrorCode.INPUT_REQUIRED_PARAMETER.CustomErrorCode);
map.put("message", ErrorCode.INPUT_REQUIRED_PARAMETER.errorMessage);
return ResponseEntity.status(ErrorCode.INPUT_REQUIRED_PARAMETER.statusCode).body(map);
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
Expand Down
1 change: 1 addition & 0 deletions src/main/java/NextLevel/demo/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public enum ErrorCode {

// funding
NOT_ENOUGH_POINT(HttpStatus.BAD_REQUEST, "05001","not enough point left:%s, need:%s"),
ALREADY_USED_COUPON(HttpStatus.BAD_REQUEST, "05002","already used coupon"),

// option

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import NextLevel.demo.funding.dto.response.ResponseCouponDto;
import NextLevel.demo.funding.service.CouponService;
import NextLevel.demo.util.jwt.JWTUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
Expand All @@ -21,7 +22,7 @@ public class CouponController {
private final CouponService couponService;

@PostMapping("/admin/coupon/add")
public ResponseEntity addCoupon(@RequestBody RequestAddCouponDto dto) {
public ResponseEntity addCoupon(@RequestBody @Valid RequestAddCouponDto dto) {
if(dto.getUserId()==null)
dto.setUserId(JWTUtil.getUserIdFromSecurityContext());
couponService.addCoupon(dto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ public class FundingController {
public ResponseEntity<?> funding(@RequestBody @Valid RequestFundingDto dto) {
dto.setUserId(JWTUtil.getUserIdFromSecurityContext());

if(dto.getFree() != null)
fundingService.freeFunding(dto.getFree());
if(dto.getOption() != null)
fundingService.optionFunding(dto.getOption());
if(dto.getFree() != null)
fundingService.freeFunding(dto.getFree());

return ResponseEntity.ok().body(new SuccessResponse("success", null));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import NextLevel.demo.funding.entity.FreeFundingEntity;
import NextLevel.demo.project.project.entity.ProjectEntity;
import NextLevel.demo.user.entity.UserEntity;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
Expand All @@ -12,7 +15,10 @@
@NoArgsConstructor
public class RequestFreeFundingDto {

@JsonAlias("price")
@NotNull
private Long freePrice;
@NotNull
private Long projectId;

private Long userId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ public class FreeFundingEntity {
@JoinColumn(name = "project_id")
private ProjectEntity project;

public void updatePrice(long price) {this.price += price;}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class OptionFundingEntity extends BasedEntity {
@Column
private long count;

public void updateCount(int count) {
public void updateCount(long count) {
this.count += count;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Optional;

public interface FreeFundingRepository extends JpaRepository<FreeFundingEntity, Long> {

@Query("select sum(f.price) from FreeFundingEntity f where f.project.id = :projectId")
Expand All @@ -13,4 +16,14 @@ public interface FreeFundingRepository extends JpaRepository<FreeFundingEntity,
@Query("select count(ff) from FreeFundingEntity ff where ff.project.id = :projectId")
Long getFundingCount(@Param("projectId") Long projectId);

@Query("select ff from FreeFundingEntity ff where ff.user.id = :userId and ff.project.id = :projectId")
Optional<FreeFundingEntity> findByProjectIdAndUserId(@Param("projectId") long projectId, @Param("userId") long userId);

//for rollback funding
@Query("select ff from FreeFundingEntity ff left join fetch ff.user where ff.project.id = :projectId")
List<FreeFundingEntity> findAllWithUserByProject(@Param("projectId") Long projectId);

@Query("select ff from FreeFundingEntity ff where ff.user.id = :userId")
List<FreeFundingEntity> findAllByUser(@Param("userId") Long userId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface OptionFundingRepository extends JpaRepository<OptionFundingEntity, Long> {

Expand All @@ -20,4 +23,16 @@ public interface OptionFundingRepository extends JpaRepository<OptionFundingEnti
"where of.option.project.id = :projectId ")
Long getTotalFundingCount(@Param("projectId") Long projectId);

@Query("select of from OptionFundingEntity of where of.option.id = :optionId and of.user.id = :userId")
Optional<OptionFundingEntity> findByOptionIdAndUserId(@Param("optionId") long optionId, @Param("userId") long userId);

// for rollback funding
@Query("select of from OptionFundingEntity of left join fetch of.user where of.option.id = :optionId")
List<OptionFundingEntity> findAllWithUserByOption(@Param("optionId") Long optionId);

@Query("select of from OptionFundingEntity of left join fetch of.user where of.option.project.id = :projectId")
List<OptionFundingEntity> findAllWithUserByProject(@Param("projectId") Long projectId);

@Query("select of from OptionFundingEntity of where of.user.id = :userId")
List<OptionFundingEntity> findAllByUser(@Param("userId") Long userId);
}
10 changes: 3 additions & 7 deletions src/main/java/NextLevel/demo/funding/service/CouponService.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,14 @@ public long useCoupon(long userId, long couponId, OptionFundingEntity optionFund
if(!coupon.getUser().getId().equals(userId))
throw new CustomException(ErrorCode.NOT_AUTHOR);

if(coupon.getOptionFunding() != null)
throw new CustomException(ErrorCode.ALREADY_USED_COUPON);

price -= coupon.getPrice();

coupon.updateProjectFundingEntity(optionFunding);

return price>0?price:0;
}

public long rollBackUseCoupon(CouponEntity coupon, long price) {
price -= coupon.getPrice();
coupon.rollBackUseCoupon();
couponRepository.save(coupon);
return price;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package NextLevel.demo.funding.service;

import NextLevel.demo.funding.entity.CouponEntity;
import NextLevel.demo.funding.entity.FreeFundingEntity;
import NextLevel.demo.funding.entity.OptionFundingEntity;
import NextLevel.demo.funding.repository.CouponRepository;
import NextLevel.demo.funding.repository.FreeFundingRepository;
import NextLevel.demo.funding.repository.OptionFundingRepository;
import NextLevel.demo.option.OptionEntity;
import NextLevel.demo.project.project.entity.ProjectEntity;
import NextLevel.demo.user.entity.UserEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class FundingRollbackService {

private final CouponRepository couponRepository;
private final OptionFundingRepository optionFundingRepository;
private final FreeFundingRepository freeFundingRepository;

private long rollbackCoupon(CouponEntity coupon, long price) {
price -= coupon.getPrice();
coupon.rollBackUseCoupon();
couponRepository.save(coupon); // transaction내부에서는 필요 없음? (transaction에 영속성 밖이라 적용이 안될려나?)
return price>0?price:0;
}

public void rollbackOptionFunding(UserEntity user, OptionFundingEntity optionFunding) {
long price = optionFunding.getCount() * optionFunding.getOption().getPrice();

if(optionFunding.getCoupon() != null){
price = rollbackCoupon(optionFunding.getCoupon(), price);
}

optionFundingRepository.deleteById(optionFunding.getId());
user.updatePoint(+price);
}

public void rollbackFreeFunding(UserEntity user, FreeFundingEntity freeFunding) {
long price = freeFunding.getPrice();
freeFundingRepository.deleteById(freeFunding.getId());
user.updatePoint(+price);
}

// option 삭제 / 변경 시 사용 (option의 funding들만 rollback 시켜줌
public void rollbackByOption(OptionEntity option) {
// option의 funding의 모든 user에게
List<OptionFundingEntity> optionFundingList = optionFundingRepository.findAllWithUserByOption(option.getId());
optionFundingList.forEach((funding)-> rollbackOptionFunding(funding.getUser(), funding));
}

// project 삭제시 사용
public void rollbackByProject(ProjectEntity project) {
List<OptionFundingEntity> optionFundingList = optionFundingRepository.findAllWithUserByProject(project.getId());
List<FreeFundingEntity> freeFundingList = freeFundingRepository.findAllWithUserByProject(project.getId());

optionFundingList.forEach(optionFunding -> rollbackOptionFunding(optionFunding.getUser(), optionFunding));
freeFundingList.forEach(freeFunding -> rollbackFreeFunding(freeFunding.getUser(), freeFunding));
}

// user의 모든 funding을 rollback 시킴 (user delete시 data 무결성 때문에 rollback기능은 필요함!)
public void rollbackByUser(UserEntity user) {
List<OptionFundingEntity> optionFundingList = optionFundingRepository.findAllByUser(user.getId());
List<FreeFundingEntity> freeFundingList = freeFundingRepository.findAllByUser(user.getId());

optionFundingList.forEach(optionFunding -> rollbackOptionFunding(user, optionFunding));
freeFundingList.forEach(freeFunding -> rollbackFreeFunding(user, freeFunding));
}

}
39 changes: 26 additions & 13 deletions src/main/java/NextLevel/demo/funding/service/FundingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import NextLevel.demo.funding.dto.request.RequestCancelFundingDto;
import NextLevel.demo.funding.dto.request.RequestFreeFundingDto;
import NextLevel.demo.funding.dto.request.RequestOptionFundingDto;
import NextLevel.demo.funding.entity.CouponEntity;
import NextLevel.demo.funding.entity.FreeFundingEntity;
import NextLevel.demo.option.OptionEntity;
import NextLevel.demo.funding.entity.OptionFundingEntity;
Expand All @@ -16,14 +15,19 @@
import NextLevel.demo.project.project.service.ProjectValidateService;
import NextLevel.demo.user.entity.UserEntity;
import NextLevel.demo.user.service.UserValidateService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
@Validated
public class FundingService {

private final UserValidateService userValidateService;
Expand All @@ -33,6 +37,8 @@ public class FundingService {
private final OptionFundingRepository optionFundingRepository;
private final FreeFundingRepository freeFundingRepository;

private final FundingRollbackService fundingRollbackService;

private final CouponService couponService;

@Transactional
Expand All @@ -43,7 +49,8 @@ public void cancelFreeFunding(RequestCancelFundingDto dto) {
);
if(!user.getId().equals(funding.getUser().getId()))
throw new CustomException(ErrorCode.NOT_AUTHOR);
freeFundingRepository.deleteById(dto.getId());

fundingRollbackService.rollbackFreeFunding(user, funding);
}

@Transactional
Expand All @@ -52,21 +59,15 @@ public void cancelOptionFunding(RequestCancelFundingDto dto) {
OptionFundingEntity funding = optionFundingRepository.findById(dto.getId()).orElseThrow(
()->{return new CustomException(ErrorCode.NOT_FOUND, "optionFunding");}
);
long price = funding.getCount() * funding.getOption().getPrice();

if(!user.getId().equals(funding.getUser().getId()))
throw new CustomException(ErrorCode.NOT_AUTHOR);

if(funding.getCoupon() != null){
price = couponService.rollBackUseCoupon(funding.getCoupon(), price);
}

optionFundingRepository.deleteById(dto.getId());
user.updatePoint(+price);
fundingRollbackService.rollbackOptionFunding(user, funding);
}

@Transactional
public void optionFunding(RequestOptionFundingDto dto) {
public void optionFunding(@Valid RequestOptionFundingDto dto) {
UserEntity user = userValidateService.getUserInfo(dto.getUserId());
OptionEntity option = optionValidateService.getOption(dto.getOptionId());

Expand All @@ -80,19 +81,31 @@ public void optionFunding(RequestOptionFundingDto dto) {
if(totalPrice > user.getPoint())
throw new CustomException(ErrorCode.NOT_ENOUGH_POINT, String.valueOf(user.getPoint()), String.valueOf(totalPrice));

optionFundingRepository.save(entity);
Optional<OptionFundingEntity> oldOptionFundingOpt = optionFundingRepository.findByOptionIdAndUserId(dto.getOptionId(), dto.getUserId());

if(oldOptionFundingOpt.isPresent())
oldOptionFundingOpt.get().updateCount(dto.getCount());
else
optionFundingRepository.save(entity);

user.updatePoint(-totalPrice);
}

@Transactional
public void freeFunding(RequestFreeFundingDto dto) {
public void freeFunding(@Valid RequestFreeFundingDto dto) {
UserEntity user = userValidateService.getUserInfo(dto.getUserId());
ProjectEntity project = projectValidateService.getProjectEntity(dto.getProjectId());

if(dto.getFreePrice() > user.getPoint())
throw new CustomException(ErrorCode.NOT_ENOUGH_POINT, String.valueOf(user.getPoint()), String.valueOf(dto.getFreePrice()));

freeFundingRepository.save(dto.toEntity(user, project));
Optional<FreeFundingEntity> oldFreeFundingOpt = freeFundingRepository.findByProjectIdAndUserId(project.getId(), dto.getUserId());

if(oldFreeFundingOpt.isPresent())
oldFreeFundingOpt.get().updatePrice(dto.getFreePrice());
else
freeFundingRepository.save(dto.toEntity(user, project));

user.updatePoint(-dto.getFreePrice());
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/NextLevel/demo/option/OptionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import NextLevel.demo.exception.CustomException;
import NextLevel.demo.exception.ErrorCode;
import NextLevel.demo.funding.service.FundingRollbackService;
import NextLevel.demo.project.project.entity.ProjectEntity;

import java.util.List;
Expand All @@ -19,6 +20,7 @@ public class OptionService {

private final OptionRepository optionRepository;
private final ProjectValidateService projectValidateService;
private final FundingRollbackService fundingRollbackService;

public void add(SaveOptionRequestDto dto){
ProjectEntity project = projectValidateService.validateAuthor(dto.getProjectId(), dto.getUserId());
Expand All @@ -32,6 +34,7 @@ public void update(SaveOptionRequestDto dto){
()->{return new CustomException(ErrorCode.NOT_FOUND, "option");}
);
projectValidateService.validateAuthor(option.getProject().getId(), dto.getUserId());
fundingRollbackService.rollbackByOption(option);
option.update(dto);
}

Expand All @@ -41,6 +44,7 @@ public void delete(Long optionId, Long userId){
()->{return new CustomException(ErrorCode.NOT_FOUND, "option");}
);
projectValidateService.validateAuthor(option.getProject().getId(), userId);
fundingRollbackService.rollbackByOption(option);
optionRepository.deleteById(option.getId());
}

Expand Down
Loading
Loading