Skip to content

Commit

Permalink
Merge pull request #100 from SWM-M3PRO/feature/M3-415-updateAchievement
Browse files Browse the repository at this point in the history
M3-415 업적 진행 기능 구현
  • Loading branch information
koomin1227 authored Nov 2, 2024
2 parents 26a83da + bda233f commit 4637ae9
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -47,7 +44,5 @@ public class Achievement {

private LocalDateTime endAt;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "next_achievement_id")
private Achievement nextAchievement;
private Long nextAchievementId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.time.LocalDateTime;

import com.m3pro.groundflip.domain.entity.global.BaseTimeEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand All @@ -21,7 +23,7 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class UserAchievement {
public class UserAchievement extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -37,16 +39,17 @@ public class UserAchievement {
@Column(name = "current_value")
private Integer currentValue;

@Column(name = "created_at")
private LocalDateTime createdAt;

@Column(name = "modified_at")
private LocalDateTime modifiedAt;

@Column(name = "obtained_at")
private LocalDateTime obtainedAt;

@Column(name = "is_reward_received")
private Boolean isRewardReceived;

public void increaseCurrentValue() {
this.currentValue++;
}

public void setObtainedAt() {
this.obtainedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.m3pro.groundflip.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum AchievementCategoryId {
EXPLORER(1L),
CONQUEROR(2L),
RANKER(3L),
STEADY(4L),
SPECIAL(5L);

private final Long categoryId;
}
12 changes: 12 additions & 0 deletions src/main/java/com/m3pro/groundflip/enums/SpecialAchievement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.m3pro.groundflip.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum SpecialAchievement {
JOIN_GROUP(27L);

private final Long achievementId;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.m3pro.groundflip.repository;

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

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -27,4 +28,7 @@ List<AchievementElementInterface> findAllByCategory(
@Param("achievement_category_id") Long achievementCategoryId,
@Param("user_id") Long userId
);

@Query("SELECT a FROM Achievement a WHERE a.categoryId = :categoryId ORDER BY a.id ASC LIMIT 1")
Optional<Achievement> findByCategoryId(Long categoryId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,11 @@ void save(@Param("pixel_id") Long pixelId, @Param("user_id") Long userId,

boolean existsByPixelIdAndUserId(Long pixelId, Long userId);

@Query("SELECT COUNT(pu) > 0 FROM PixelUser pu WHERE pu.user.id = :userId AND FUNCTION('DATE', pu.createdAt) = FUNCTION('DATE', :currentDate)")
boolean existsByUserIdAndPixelIdForToday(
@Param("userId") Long userId,
@Param("currentDate") LocalDateTime currentDate
);

boolean existsByPixelIdAndCommunityId(Long pixelId, Long communityId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,13 @@ SELECT COUNT(ua) FROM UserAchievement ua
""")
List<UserAchievement> findAllByUserId(Long userId);

@Query("""
SELECT ua FROM UserAchievement ua
JOIN FETCH ua.achievement a
WHERE ua.user.id = :userId AND a.categoryId = :categoryId
ORDER BY ua.obtainedAt DESC
""")
List<UserAchievement> findAllByUserIdAndCategoryId(Long userId, Long categoryId);

Optional<UserAchievement> findByAchievementAndUserId(Achievement achievement, Long userId);
}
94 changes: 94 additions & 0 deletions src/main/java/com/m3pro/groundflip/service/AchievementManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.m3pro.groundflip.service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.m3pro.groundflip.domain.entity.Achievement;
import com.m3pro.groundflip.domain.entity.UserAchievement;
import com.m3pro.groundflip.enums.AchievementCategoryId;
import com.m3pro.groundflip.enums.SpecialAchievement;
import com.m3pro.groundflip.exception.AppException;
import com.m3pro.groundflip.exception.ErrorCode;
import com.m3pro.groundflip.repository.AchievementRepository;
import com.m3pro.groundflip.repository.UserAchievementRepository;
import com.m3pro.groundflip.repository.UserRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class AchievementManager {
private final AchievementRepository achievementRepository;
private final UserAchievementRepository userAchievementRepository;
private final UserRepository userRepository;

@Transactional
public void updateAccumulateAchievement(Long userId, AchievementCategoryId categoryId) {
UserAchievement achievementToUpdate = getAchievementToUpdate(categoryId.getCategoryId(), userId);

achievementToUpdate.increaseCurrentValue();

if (Objects.equals(achievementToUpdate.getCurrentValue(),
achievementToUpdate.getAchievement().getCompletionGoal())) {
completeAchievement(achievementToUpdate, userId);
}
}

private void completeAchievement(UserAchievement achievementToUpdate, Long userId) {
achievementToUpdate.setObtainedAt();
Long nextAchievementId = achievementToUpdate.getAchievement().getNextAchievementId();
if (nextAchievementId != null) {
UserAchievement nextAchievement = UserAchievement.builder()
.achievement(achievementRepository.getReferenceById(nextAchievementId))
.user(userRepository.getReferenceById(userId))
.currentValue(achievementToUpdate.getCurrentValue())
.isRewardReceived(false)
.build();
userAchievementRepository.save(nextAchievement);
}
}

private UserAchievement getAchievementToUpdate(Long categoryId, Long userId) {
List<UserAchievement> userAchievements = userAchievementRepository
.findAllByUserIdAndCategoryId(userId, categoryId);

if (userAchievements.isEmpty()) {
Achievement achievement = achievementRepository
.findByCategoryId(categoryId).orElseThrow(() -> new AppException(ErrorCode.ACHIEVEMENT_NOT_FOUND));

UserAchievement nextAchievement = UserAchievement.builder()
.achievement(achievement)
.user(userRepository.getReferenceById(userId))
.currentValue(0)
.isRewardReceived(false)
.build();
return userAchievementRepository.save(nextAchievement);
} else {
return userAchievements.get(userAchievements.size() - 1);
}
}

@Transactional
public void updateSpecialAchievement(Long userId, SpecialAchievement specialAchievement) {
Optional<UserAchievement> userAchievement = userAchievementRepository.findByAchievementAndUserId(
achievementRepository.getReferenceById(specialAchievement.getAchievementId()), userId);

if (userAchievement.isEmpty()) {
UserAchievement newUserAchievement = UserAchievement.builder()
.achievement(achievementRepository.getReferenceById(specialAchievement.getAchievementId()))
.user(userRepository.getReferenceById(userId))
.currentValue(1)
.obtainedAt(LocalDateTime.now())
.isRewardReceived(false)
.build();
userAchievementRepository.save(newUserAchievement);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.m3pro.groundflip.domain.entity.Community;
import com.m3pro.groundflip.domain.entity.User;
import com.m3pro.groundflip.domain.entity.UserCommunity;
import com.m3pro.groundflip.enums.SpecialAchievement;
import com.m3pro.groundflip.exception.AppException;
import com.m3pro.groundflip.exception.ErrorCode;
import com.m3pro.groundflip.repository.CommunityRankingRedisRepository;
Expand All @@ -37,6 +38,7 @@ public class CommunityService {
private final CommunityRepository communityRepository;
private final UserCommunityRepository userCommunityRepository;
private final CommunityRankingService communityRankingService;
private final AchievementManager achievementManager;
private final UserRepository userRepository;
private final UserRankingRedisRepository userRankingRedisRepository;
private final CommunityRankingRedisRepository communityRankingRedisRepository;
Expand Down Expand Up @@ -64,6 +66,7 @@ public CommunityInfoResponse getCommunityInfo(Long communityId) {
return CommunityInfoResponse.from(community, rank, memberCount, currentPixel, accumulatePixel);
}

@Transactional
public void signInCommunity(Long communityId, CommunitySignRequest communitySignRequest) {
User user = userRepository.findById(communitySignRequest.getUserId())
.orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));
Expand All @@ -83,6 +86,7 @@ public void signInCommunity(Long communityId, CommunitySignRequest communitySign
.build();

userCommunityRepository.save(userCommunity);
achievementManager.updateSpecialAchievement(user.getId(), SpecialAchievement.JOIN_GROUP);
}

public void signOutCommunity(Long communityId, CommunitySignRequest communitySignRequest) {
Expand Down
22 changes: 22 additions & 0 deletions src/main/java/com/m3pro/groundflip/service/PixelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;

import org.locationtech.jts.geom.Coordinate;
Expand All @@ -17,6 +18,7 @@
import com.m3pro.groundflip.domain.entity.PixelUser;
import com.m3pro.groundflip.domain.entity.Region;
import com.m3pro.groundflip.domain.entity.UserRegionCount;
import com.m3pro.groundflip.enums.AchievementCategoryId;
import com.m3pro.groundflip.exception.AppException;
import com.m3pro.groundflip.exception.ErrorCode;
import com.m3pro.groundflip.repository.CommunityRepository;
Expand Down Expand Up @@ -53,6 +55,7 @@ public class PixelManager {
private final UserRegionCountRepository userRegionCountRepository;
private final UserRepository userRepository;
private final CommunityRepository communityRepository;
private final AchievementManager achievementManager;

@Transactional
public void occupyPixel(PixelOccupyRequest pixelOccupyRequest) {
Expand Down Expand Up @@ -151,6 +154,10 @@ private void updateUserAccumulatePixelCount(Pixel targetPixel, Long userId) {
if (!pixelUserRepository.existsByPixelIdAndUserId(targetPixel.getId(), userId)) {
updateUserRegionCount(targetPixel, userId);
userRankingService.updateAccumulatedRanking(userId);
achievementManager.updateAccumulateAchievement(userId, AchievementCategoryId.EXPLORER);
}
if (!pixelUserRepository.existsByUserIdAndPixelIdForToday(userId, LocalDateTime.now())) {
achievementManager.updateAccumulateAchievement(userId, AchievementCategoryId.STEADY);
}
}

Expand Down Expand Up @@ -180,10 +187,25 @@ private void updateCommunityCurrentPixelCount(Pixel targetPixel, Long communityI
}

private void updatePixelOwnerUser(Pixel targetPixel, Long occupyingUserId) {
if (isLandTakenFromExistingUser(targetPixel, occupyingUserId)) {
achievementManager.updateAccumulateAchievement(occupyingUserId, AchievementCategoryId.CONQUEROR);
}
targetPixel.updateUserId(occupyingUserId);
targetPixel.updateUserOccupiedAtToNow();
}

private boolean isLandTakenFromExistingUser(Pixel targetPixel, Long occupyingUserId) {
Long originalOwnerUserId = targetPixel.getUserId();
LocalDateTime thisWeekStart = DateUtils.getThisWeekStartDate().atTime(0, 0);
LocalDateTime userOccupiedAt = targetPixel.getUserOccupiedAt();
if (!Objects.equals(originalOwnerUserId, occupyingUserId)) {
return originalOwnerUserId != null && !userOccupiedAt.isBefore(thisWeekStart);
} else {
return false;
}

}

private void updateRegionCount(Pixel targetPixel, boolean isCommunityUpdatable) {
if (targetPixel.getRegion() != null) {
LocalDate now = LocalDate.now();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class CommunityServiceTest {
@Mock
private UserCommunityRepository userCommunityRepository;

@Mock
private AchievementManager achievementManager;

@Mock
private UserRepository userRepository;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

import java.time.LocalDateTime;
import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -41,6 +42,8 @@ class PixelManagerTest {
@Mock
private CommunityRepository communityRepository;
@Mock
private AchievementManager achievementManager;
@Mock
private PixelRepository pixelRepository;
@Mock
private PixelUserRepository pixelUserRepository;
Expand All @@ -66,6 +69,7 @@ void occupyPixel() {
.x(222L)
.y(233L)
.userId(1L)
.userOccupiedAt(LocalDateTime.now())
.address("대한민국")
.build();
when(pixelRepository.findByXAndY(222L, 233L)).thenReturn(Optional.of(pixel));
Expand All @@ -88,6 +92,7 @@ void pixelUserInsertEventPublish() {
.x(222L)
.y(233L)
.userId(1L)
.userOccupiedAt(LocalDateTime.now())
.address("대한민국")
.build();
when(pixelRepository.findByXAndY(222L, 233L)).thenReturn(Optional.of(pixel));
Expand All @@ -108,6 +113,7 @@ void pixelAddressUpdateEventPublish() {
.x(222L)
.y(233L)
.userId(1L)
.userOccupiedAt(LocalDateTime.now())
.address(null)
.build();
when(pixelRepository.findByXAndY(222L, 233L)).thenReturn(Optional.of(pixel));
Expand All @@ -130,6 +136,7 @@ void pixelAddressUpdateEventNotPublish() {
.x(222L)
.y(233L)
.userId(1L)
.userOccupiedAt(LocalDateTime.now())
.address("대한민국 ")
.build();
when(pixelRepository.findByXAndY(222L, 233L)).thenReturn(Optional.of(pixel));
Expand Down Expand Up @@ -161,6 +168,7 @@ void pixelOccupyTestNotRegisteredPixel() {
.x(222L)
.y(233L)
.userId(1L)
.userOccupiedAt(LocalDateTime.now())
.address("대한민국 ")
.build();
double expectedLongitude = upper_left_lon + (233L * lon_per_pixel);
Expand Down

0 comments on commit 4637ae9

Please sign in to comment.