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
3 changes: 2 additions & 1 deletion src/main/java/org/runimo/runimo/common/GlobalConsts.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.runimo.runimo.common.scale.Distance;

import java.util.Set;

Expand All @@ -17,5 +18,5 @@ public final class GlobalConsts {
);

public static final String EMPTYFIELD = "EMPTY";

public static final Distance DISTANCE_UNIT = new Distance(1000L);
}
8 changes: 8 additions & 0 deletions src/main/java/org/runimo/runimo/common/scale/Distance.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ public Distance(Long amount) {
public Long getAmount() {
return amount;
}

public Distance add(Distance distance) {
return new Distance(this.amount + distance.amount);
}

public Distance divide(Distance divisor) {
return new Distance(this.amount / divisor.getAmount());
}
}
6 changes: 2 additions & 4 deletions src/main/java/org/runimo/runimo/item/domain/Egg.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.runimo.runimo.item.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -14,6 +11,7 @@
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DiscriminatorValue("EGG")
public class Egg extends Item {
public static final Egg EMPTY = new Egg(EMPTYFIELD, EMPTYFIELD, EMPTYFIELD, EMPTYFIELD, null, 0L);
@Column(name = "egg_type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.runimo.runimo.rewards.service.eggs.EggGrantService;
import org.runimo.runimo.rewards.service.dtos.RewardClaimCommand;
import org.runimo.runimo.rewards.service.dtos.RewardResponse;
import org.runimo.runimo.rewards.service.lovepoint.LoveGrantService;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.service.UserFinder;
import org.springframework.stereotype.Service;
Expand All @@ -24,16 +25,18 @@ public class RewardService {
private final RecordFinder recordFinder;
private final UserFinder userFinder;
private final EggGrantService eggGrantService;
private final LoveGrantService loveGrantService;


@Transactional
public RewardResponse claimReward(RewardClaimCommand command) {
RunningRecord runningRecord = recordFinder.findById(command.recordId())
.orElseThrow(NoSuchElementException::new);
validateRecord(runningRecord);
Egg grantedEgg = rewardEgg(command);
Long grantedLoveAmount = loveGrantService.grantLoveToUserWithDistance(runningRecord);
runningRecord.reward(command.userId());
//TODO: 애정 보상
return new RewardResponse(grantedEgg.getItemCode(), grantedEgg.getEggType());
return new RewardResponse(grantedEgg.getItemCode(), grantedEgg.getEggType(), grantedLoveAmount);
}

private Egg rewardEgg(RewardClaimCommand command) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

public record RewardResponse(
String eggCode,
EggType eggType
EggType eggType,
Long lovePointAmount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.runimo.runimo.rewards.service.lovepoint;

import lombok.RequiredArgsConstructor;

import org.runimo.runimo.common.scale.Distance;
import org.runimo.runimo.records.domain.RunningRecord;
import org.runimo.runimo.user.domain.LovePoint;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.service.LovePointProcessor;
import org.runimo.runimo.user.service.UserFinder;
import org.springframework.stereotype.Component;

import static org.runimo.runimo.common.GlobalConsts.DISTANCE_UNIT;


@Component
@RequiredArgsConstructor
public class LoveGrantService {
private final UserFinder userFinder;
private final LovePointProcessor lovePointProcessor;

public Long grantLoveToUserWithDistance(RunningRecord runningRecord) {
User user = userFinder.findUserById(runningRecord.getUserId())
.orElseThrow(IllegalStateException::new);
Long loveAmount = calculateLoveAmount(runningRecord.getTotalDistance());
LovePoint lovePoint = lovePointProcessor.updateLovePoint(user.getId(), loveAmount);
return lovePoint.getAmount();
}

private Long calculateLoveAmount(Distance distance) {
return distance.divide(DISTANCE_UNIT).getAmount();
}
}
37 changes: 37 additions & 0 deletions src/main/java/org/runimo/runimo/user/domain/LovePoint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.runimo.runimo.user.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.runimo.runimo.common.BaseEntity;

@Table(name = "user_love_point")
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LovePoint extends BaseEntity {
public static final LovePoint EMPTY = new LovePoint(null, 0L);
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "amount", nullable = false)
private Long amount;

@Builder
public LovePoint(Long userId, Long amount) {
this.userId = userId;
this.amount = amount;
}

public void add(long amount) {
this.amount += amount;
}

public void subtract(long amount) {
if (this.amount < amount) throw new IllegalStateException("Not enough love points");
this.amount -= amount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.runimo.runimo.user.repository;

import jakarta.persistence.LockModeType;
import jakarta.persistence.QueryHint;
import org.runimo.runimo.user.domain.LovePoint;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;

import java.util.Optional;

public interface LovePointRepository extends JpaRepository<LovePoint, Long> {
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({@QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000")})
@Query("select l from LovePoint l where l.userId = :id")
Optional<LovePoint> findByUserIdWithXLock(Long id);

Optional<LovePoint> findLovePointByUserId(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.runimo.runimo.user.service;

import org.runimo.runimo.user.domain.LovePoint;
import org.runimo.runimo.user.repository.LovePointRepository;
import org.springframework.stereotype.Component;

@Component
public class LovePointProcessor {

private final LovePointRepository lovePointRepository;

public LovePointProcessor(LovePointRepository lovePointRepository) {
this.lovePointRepository = lovePointRepository;
}

// 유저의 러브포인트를 업데이트한다. XLOCK을 걸어서 동시성 문제를 해결한다.
public LovePoint updateLovePoint(Long userId, Long loveAmount) {
LovePoint lp = lovePointRepository.findByUserIdWithXLock(userId)
.orElseThrow(IllegalStateException::new);
lp.add(loveAmount);
return lovePointRepository.save(lp);
}
}
15 changes: 12 additions & 3 deletions src/main/java/org/runimo/runimo/user/service/UserCreator.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.runimo.runimo.user.service;

import lombok.RequiredArgsConstructor;
import org.runimo.runimo.user.domain.OAuthInfo;
import org.runimo.runimo.user.domain.SocialProvider;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.domain.*;
import org.runimo.runimo.user.repository.LovePointRepository;
import org.runimo.runimo.user.repository.OAuthInfoRepository;
import org.runimo.runimo.user.repository.UserRepository;
import org.runimo.runimo.user.service.dtos.UserSignupCommand;
Expand All @@ -15,6 +14,7 @@
public class UserCreator {
private final UserRepository userRepository;
private final OAuthInfoRepository oAuthInfoRepository;
private final LovePointRepository lovePointRepository;

@Transactional
public User createUser(UserSignupCommand command) {
Expand All @@ -34,4 +34,13 @@ public OAuthInfo createUserOAuthInfo(User user, SocialProvider provider, String
.build();
return oAuthInfoRepository.save(oAuthInfo);
}

@Transactional
public LovePoint createLovePoint(Long userId) {
LovePoint lovePoint = LovePoint.builder()
.userId(userId)
.amount(0L)
.build();
return lovePointRepository.save(lovePoint);
}
}
7 changes: 7 additions & 0 deletions src/main/java/org/runimo/runimo/user/service/UserFinder.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.runimo.runimo.user.service;

import lombok.RequiredArgsConstructor;
import org.runimo.runimo.user.domain.LovePoint;
import org.runimo.runimo.user.domain.User;
import org.runimo.runimo.user.repository.LovePointRepository;
import org.runimo.runimo.user.repository.UserRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -13,6 +15,7 @@
public class UserFinder {

private final UserRepository userRepository;
private final LovePointRepository lovePointRepository;

@Transactional(readOnly = true)
public Optional<User> findUserByPublicId(final String publicId) {
Expand All @@ -23,4 +26,8 @@ public Optional<User> findUserByPublicId(final String publicId) {
public Optional<User> findUserById(final Long userId) {
return userRepository.findById(userId);
}

public Optional<LovePoint> findLovePointByUserId(Long userId) {
return lovePointRepository.findLovePointByUserId(userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class UserRegisterService {
public User register(UserSignupCommand command, String providerId) {
User savedUser = userCreator.createUser(command);
userCreator.createUserOAuthInfo(savedUser, command.provider(), providerId);
userCreator.createLovePoint(savedUser.getId());
userItemCreator.createAll(savedUser.getId());
eggGrantService.grantGreetingEggToUser(savedUser);
return savedUser;
Expand Down
11 changes: 11 additions & 0 deletions src/main/resources/sql/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ DROP TABLE IF EXISTS item_activity;
DROP TABLE IF EXISTS runimo;
DROP TABLE IF EXISTS items;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS user_love_point;

SET FOREIGN_KEY_CHECKS = 1;

Expand Down Expand Up @@ -37,6 +38,16 @@ CREATE TABLE `user_token`
FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
);

CREATE TABLE `user_love_point`
(
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`amount` BIGINT NOT NULL DEFAULT 0,
`created_at` TIMESTAMP,
`updated_at` TIMESTAMP,
`deleted_at` TIMESTAMP
);

CREATE TABLE `oauth_accounts`
(
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
Expand Down
1 change: 1 addition & 0 deletions src/test/java/org/runimo/runimo/CleanUpUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class CleanUpUtil {
"oauth_accounts",
"users",
"running_records",
"user_love_point"
};

@Autowired
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/org/runimo/runimo/rewards/RewardTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.jdbc.Sql;

import java.time.LocalDateTime;

Expand Down Expand Up @@ -58,6 +59,7 @@ void tearDown() {
}

@Test
@Sql(scripts = "/sql/egg_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
void 보상_요청_테스트() {
RecordCreateCommand recordCreateCommand = new RecordCreateCommand(
savedUser.getId(),
Expand Down
Loading