diff --git a/src/main/java/com/leets/commitatobe/domain/commit/domain/Commit.java b/src/main/java/com/leets/commitatobe/domain/commit/domain/Commit.java index 11233ed..4e6adea 100644 --- a/src/main/java/com/leets/commitatobe/domain/commit/domain/Commit.java +++ b/src/main/java/com/leets/commitatobe/domain/commit/domain/Commit.java @@ -32,6 +32,9 @@ public class Commit extends BaseTimeEntity { @JsonBackReference private User user; + @Column(name = "is_calculated") + private boolean isCalculated;//경험치 계산 여부를 나타낸다. + public static Commit create(LocalDateTime commitDate, Integer cnt, User user) { return Commit.builder() .commitDate(commitDate) @@ -49,5 +52,7 @@ public Commit(LocalDateTime commitDate, Integer cnt, User user) { public void updateCnt(Integer cnt) { this.cnt = this.cnt + cnt; + this.isCalculated=false; } + public void updateStatusToCalculated(boolean calculated){isCalculated=calculated;}//isCalculated 필드 설정 } diff --git a/src/main/java/com/leets/commitatobe/domain/commit/domain/repository/CommitRepository.java b/src/main/java/com/leets/commitatobe/domain/commit/domain/repository/CommitRepository.java index 16a2e7c..70eb16e 100644 --- a/src/main/java/com/leets/commitatobe/domain/commit/domain/repository/CommitRepository.java +++ b/src/main/java/com/leets/commitatobe/domain/commit/domain/repository/CommitRepository.java @@ -13,4 +13,6 @@ public interface CommitRepository extends JpaRepository { List findAllByUser(User user); Optional findByCommitDateAndUser(LocalDateTime commitDate, User user); + + List findAllByUserOrderByCommitDateAsc(User user); } \ No newline at end of file diff --git a/src/main/java/com/leets/commitatobe/domain/commit/usecase/ExpService.java b/src/main/java/com/leets/commitatobe/domain/commit/usecase/ExpService.java new file mode 100644 index 0000000..47d80bd --- /dev/null +++ b/src/main/java/com/leets/commitatobe/domain/commit/usecase/ExpService.java @@ -0,0 +1,82 @@ +package com.leets.commitatobe.domain.commit.usecase; + +import com.leets.commitatobe.domain.commit.domain.Commit; +import com.leets.commitatobe.domain.commit.domain.repository.CommitRepository; +import com.leets.commitatobe.domain.tier.domain.Tier; +import com.leets.commitatobe.domain.tier.domain.repository.TierRepository; +import com.leets.commitatobe.domain.user.domain.User; +import com.leets.commitatobe.domain.user.domain.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class ExpService { + private final CommitRepository commitRepository; + private final UserRepository userRepository; + private final TierRepository tierRepository; + + public void calculateAndSaveExp(String githubId){ + User user=userRepository.findByGithubId(githubId) + .orElseThrow(()->new UsernameNotFoundException("해당하는 깃허브 닉네임과 일치하는 유저를 찾을 수 없음: " +githubId)); + List commits=commitRepository.findAllByUserOrderByCommitDateAsc(user);//사용자의 모든 커밋을 날짜 오름차순으로 불러온다. + + int consecutiveDays=0;//연속 커밋 일수 + LocalDateTime lastCommitDate=null;//마지막 커밋 날짜 + int totalExp=user.getExp()!=null?user.getExp():0;//사용자의 현재 경험치, user.getExp()가 null인 경우 0으로 초기화 + int dailyBonusExp=100;//데일리 보너스 경험치 + int bonusExpIncrease=10;//보너스 경험치 증가량 + int totalCommitCount=0;//총 커밋 횟수 + int todayCommitCount=0;//오늘 커밋 횟수 + + LocalDate today=LocalDate.now();//오늘 날짜 + + for(Commit commit:commits){//각 커밋을 반복해서 계산 + if(commit.isCalculated()) continue;//이미 계산된 커밋 + LocalDateTime commitDate=commit.getCommitDate();//커밋날짜를 가져와 시간 설정 + + if(lastCommitDate != null && commitDate.isEqual(lastCommitDate.plusDays(1))){ + // 마지막 커밋 날짜가 존재하고, 현재 커밋 날짜가 마지막 커밋 날짜의 다음 날과 같으면 연속 커밋으로 간주합니다. + consecutiveDays++;//연속 커밋 일수 1 증가 + } + else{ + consecutiveDays=0;//연속 커밋 일수 초기화 + } + int commitExp=commit.getCnt()*5;//하루 커밋 경험치 + int bonusExp=dailyBonusExp+consecutiveDays*bonusExpIncrease;//보너스 경험치 계산 + totalExp+=commitExp+bonusExp;//총 경험치 업데이트 + totalCommitCount+=commit.getCnt();//총 커밋 횟수 + + if(commitDate.toLocalDate().isEqual(today)){ + todayCommitCount=commit.getCnt();//오늘날짜의 커밋 개수 카운트 + } + + commit.updateStatusToCalculated(true);//커밋 계산 여부를 true로 해서 다음 게산에서 제외 + lastCommitDate=commitDate;//마지막 커밋날짜를 현재 커밋날짜로 업데이트 + } + user.updateExp(totalExp);//사용자 경험치 업데이트 + Tier tier=determineTier(user.getExp());//경험치에 따른 티어 결정 + user.updateTier(tier); + user.updateConsecutiveCommitDays(consecutiveDays); + user.updateTotalCommitCount(totalCommitCount); + user.updateTodayCommitCount(todayCommitCount); + + commitRepository.saveAll(commits);//변경된 커밋 정보 데이터베이스에 저장 + userRepository.save(user);//변경된 사용자 정보 데이터베이스에 저장 + } + private Tier determineTier(Integer exp){ + return tierRepository.findAll() + .stream() + .filter(tier->tier.getRequiredExp()!=null&&tier.getRequiredExp()<=exp) + .max(Comparator.comparing(Tier::getRequiredExp)) + .orElseThrow(()->new RuntimeException("해당 경험치의 티어가 없음"+exp)); + } +} diff --git a/src/main/java/com/leets/commitatobe/domain/commit/usecase/FetchCommits.java b/src/main/java/com/leets/commitatobe/domain/commit/usecase/FetchCommits.java index c88bad8..6c7d949 100644 --- a/src/main/java/com/leets/commitatobe/domain/commit/usecase/FetchCommits.java +++ b/src/main/java/com/leets/commitatobe/domain/commit/usecase/FetchCommits.java @@ -31,6 +31,7 @@ public class FetchCommits { private final GitHubService gitHubService; // GitHub API 통신 private final LoginCommandServiceImpl loginCommandService; private final LoginQueryService loginQueryService; + private final ExpService expService; private final UserQueryService userQueryService; public CommitResponse execute(HttpServletRequest request) { @@ -79,6 +80,8 @@ public CommitResponse execute(HttpServletRequest request) { saveCommits(user); + expService.calculateAndSaveExp(gitHubId);//커밋 가져온 후 경험치 계산 및 저장 + } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/leets/commitatobe/domain/tier/domain/Tier.java b/src/main/java/com/leets/commitatobe/domain/tier/domain/Tier.java index 8a8fbc5..1ae858c 100644 --- a/src/main/java/com/leets/commitatobe/domain/tier/domain/Tier.java +++ b/src/main/java/com/leets/commitatobe/domain/tier/domain/Tier.java @@ -15,7 +15,6 @@ @Getter @Builder @NoArgsConstructor -@AllArgsConstructor public class Tier extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) @@ -25,9 +24,20 @@ public class Tier extends BaseTimeEntity { private String tierName; private String characterUrl; private String badgeUrl; + + @Column(nullable = false) private Integer requiredExp; @OneToMany(mappedBy = "tier") private List userList; + public Tier(UUID id,String tierName, String characterUrl, String badgeUrl, Integer requiredExp, ListuserList){ + this.id=id; + this.tierName=tierName; + this.characterUrl=characterUrl; + this.badgeUrl=badgeUrl; + this.requiredExp=requiredExp; + this.userList=userList; + } + } diff --git a/src/main/java/com/leets/commitatobe/domain/tier/domain/repository/TierRepository.java b/src/main/java/com/leets/commitatobe/domain/tier/domain/repository/TierRepository.java new file mode 100644 index 0000000..d3228a9 --- /dev/null +++ b/src/main/java/com/leets/commitatobe/domain/tier/domain/repository/TierRepository.java @@ -0,0 +1,11 @@ +package com.leets.commitatobe.domain.tier.domain.repository; + +import com.leets.commitatobe.domain.tier.domain.Tier; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface TierRepository extends JpaRepository { + Optional findByTierName(String tierName); +} diff --git a/src/main/java/com/leets/commitatobe/domain/user/domain/User.java b/src/main/java/com/leets/commitatobe/domain/user/domain/User.java index 042bc6d..95ca17d 100644 --- a/src/main/java/com/leets/commitatobe/domain/user/domain/User.java +++ b/src/main/java/com/leets/commitatobe/domain/user/domain/User.java @@ -5,10 +5,7 @@ import com.leets.commitatobe.domain.tier.domain.Tier; import com.leets.commitatobe.global.shared.entity.BaseTimeEntity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.List; import java.util.UUID; @@ -16,7 +13,6 @@ @Entity(name = "users") @Getter @Builder -@NoArgsConstructor @AllArgsConstructor public class User extends BaseTimeEntity { @@ -28,9 +24,6 @@ public class User extends BaseTimeEntity { @Column private String gitHubAccessToken; - @Column - private String refreshToken; - @Column(nullable = true) private String username; @@ -40,12 +33,21 @@ public class User extends BaseTimeEntity { @Column private String profileImage; - @Column + @Column(nullable = false) private Integer exp; @Column private Integer commitDays; + @Column + private Integer consecutiveCommitDays; + + @Column + private Integer totalCommitCount; + + @Column + private Integer todayCommitCount; + @OneToMany(mappedBy = "user") @JsonManagedReference private List commitList; @@ -53,4 +55,29 @@ public class User extends BaseTimeEntity { @ManyToOne @JoinColumn(name="tier_id") private Tier tier; + + public void updateExp(Integer exp){ + this.exp=exp; + } + + public void updateTier(Tier tier){ + this.tier=tier; + } + + public void updateConsecutiveCommitDays(Integer consecutiveCommitDays){ + this.consecutiveCommitDays=consecutiveCommitDays; + } + + public void updateTotalCommitCount(Integer totalCommitCount){ + this.totalCommitCount=totalCommitCount; + } + + public void updateTodayCommitCount(Integer todayCommitCount){ + this.todayCommitCount=todayCommitCount; + } + + public User(){ + this.exp=0; + } + } diff --git a/src/main/java/com/leets/commitatobe/domain/user/domain/repository/UserRepository.java b/src/main/java/com/leets/commitatobe/domain/user/domain/repository/UserRepository.java index a0ffecb..b688c95 100644 --- a/src/main/java/com/leets/commitatobe/domain/user/domain/repository/UserRepository.java +++ b/src/main/java/com/leets/commitatobe/domain/user/domain/repository/UserRepository.java @@ -1,7 +1,12 @@ package com.leets.commitatobe.domain.user.domain.repository; import com.leets.commitatobe.domain.user.domain.User; + +import java.util.List; import java.util.Optional; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.UUID; @@ -9,4 +14,7 @@ public interface UserRepository extends JpaRepository { Optional findByGithubId(String githubId); + + //경험치순으로 유저를 페이징하여 조회하는 메서드 + Page findAllByOrderByExpDesc(Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/com/leets/commitatobe/domain/user/presentation/UserController.java b/src/main/java/com/leets/commitatobe/domain/user/presentation/UserController.java index 2495819..983a31b 100644 --- a/src/main/java/com/leets/commitatobe/domain/user/presentation/UserController.java +++ b/src/main/java/com/leets/commitatobe/domain/user/presentation/UserController.java @@ -1,6 +1,18 @@ package com.leets.commitatobe.domain.user.presentation; + +import com.leets.commitatobe.domain.user.presentation.dto.response.UserRankResponse; +import com.leets.commitatobe.domain.user.presentation.dto.response.UserSearchResponse; +import com.leets.commitatobe.domain.user.usecase.UserQueryService; +import com.leets.commitatobe.global.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -8,5 +20,18 @@ @RequiredArgsConstructor @RequestMapping("/user") public class UserController { - + private final UserQueryService userQueryService; + @Operation( + summary = "유저 정보 검색", + description = "깃허브 아이디로 검색합니다." + ) + @GetMapping("/search") + public ApiResponse searchUsers(@RequestParam("githubId")String githubId){ + return ApiResponse.onSuccess(userQueryService.searchUsersByGithubId(githubId)); + } + @GetMapping("/ranking")//경험치 순으로 유저 정보 조회 엔드포인트 + public ApiResponse> getUsersByExp(@PageableDefault(size = 50,sort = "exp",direction = Sort.Direction.DESC) + Pageable pageable){//페이지네이션 설정(페이지:50, exp 내림차순) + return ApiResponse.onSuccess(userQueryService.getUsersByExp(pageable)); + } } diff --git a/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserRankResponse.java b/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserRankResponse.java new file mode 100644 index 0000000..d8714de --- /dev/null +++ b/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserRankResponse.java @@ -0,0 +1,11 @@ +package com.leets.commitatobe.domain.user.presentation.dto.response; + +import java.util.UUID; + +public record UserRankResponse( + String username, + Integer exp, + Integer consecutiveCommitDays, + String tierName +) { +} diff --git a/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserSearchResponse.java b/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserSearchResponse.java new file mode 100644 index 0000000..d5c5153 --- /dev/null +++ b/src/main/java/com/leets/commitatobe/domain/user/presentation/dto/response/UserSearchResponse.java @@ -0,0 +1,13 @@ +package com.leets.commitatobe.domain.user.presentation.dto.response; + +public record UserSearchResponse( + String username, + Integer exp, + String tierName, + String characterUrl, + String badgeUrl, + Integer consecutiveCommitDays, + Integer totalCommitCount, + Integer todayCommitCount +) { +} diff --git a/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryService.java b/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryService.java index 5fd59c0..b156883 100644 --- a/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryService.java +++ b/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryService.java @@ -1,8 +1,13 @@ package com.leets.commitatobe.domain.user.usecase; -import jakarta.servlet.http.HttpServletRequest; +import com.leets.commitatobe.domain.user.presentation.dto.response.UserRankResponse; +import com.leets.commitatobe.domain.user.presentation.dto.response.UserSearchResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; public interface UserQueryService { + UserSearchResponse searchUsersByGithubId(String GithubId);//유저 이름으로 유저 정보를 검색하는 메서드 + Page getUsersByExp(Pageable pageable);//경험치 순으로 페이징된 유저 정보를 조회 String getUserGitHubAccessToken(String githubId); } \ No newline at end of file diff --git a/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryServiceImpl.java b/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryServiceImpl.java index efae066..591cffc 100644 --- a/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryServiceImpl.java +++ b/src/main/java/com/leets/commitatobe/domain/user/usecase/UserQueryServiceImpl.java @@ -1,40 +1,80 @@ package com.leets.commitatobe.domain.user.usecase; -import static com.leets.commitatobe.global.response.code.status.ErrorStatus._USER_NOT_FOUND; - +import com.leets.commitatobe.domain.commit.usecase.ExpService; import com.leets.commitatobe.domain.login.usecase.LoginCommandService; +import com.leets.commitatobe.domain.tier.domain.Tier; import com.leets.commitatobe.domain.user.domain.User; import com.leets.commitatobe.domain.user.domain.repository.UserRepository; +import com.leets.commitatobe.domain.user.presentation.dto.response.UserRankResponse; +import com.leets.commitatobe.domain.user.presentation.dto.response.UserSearchResponse; import com.leets.commitatobe.global.exception.ApiException; +import com.leets.commitatobe.global.response.code.status.ErrorStatus; import com.leets.commitatobe.global.utils.JwtProvider; -import java.util.Optional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Optional; + +import static com.leets.commitatobe.global.response.code.status.ErrorStatus._USER_NOT_FOUND; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class UserQueryServiceImpl implements UserQueryService { + private final UserRepository userRepository; + private final ExpService expService; private final JwtProvider jwtProvider; + private final LoginCommandService loginCommandService; - private final UserRepository userRepository; + @Override + @Transactional + public UserSearchResponse searchUsersByGithubId(String githubId) {// 유저 이름으로 유저 정보 검색 + User user=userRepository.findByGithubId(githubId) + .orElseThrow(()->new ApiException(ErrorStatus._USER_NOT_FOUND)); + expService.calculateAndSaveExp(user.getGithubId()); + Tier tier=user.getTier(); + return new UserSearchResponse( + user.getUsername(), + user.getExp(), + tier!=null?tier.getTierName():"Unranked", + tier!=null?tier.getCharacterUrl():null, + tier!=null?tier.getBadgeUrl():null, + user.getConsecutiveCommitDays(), + user.getTotalCommitCount(), + user.getTodayCommitCount() + ); + } - private final LoginCommandService loginCommandService; + @Override + public Page getUsersByExp(Pageable pageable){//경험치 순으로 페이징된 유저 정보 조회 + Page userPage= userRepository.findAllByOrderByExpDesc(pageable); + + return userPage.map(user->{// 각 사용자의 경험치 최신화 및 UserRankResponse 변환 + expService.calculateAndSaveExp(user.getGithubId());// 경험치 계산 및 저장 + Tier tier=user.getTier(); + return new UserRankResponse( + user.getUsername(), + user.getExp(), + user.getConsecutiveCommitDays(), + tier!=null?tier.getTierName():"Unranked"); + }); + } @Override public String getUserGitHubAccessToken(String githubId) { - Optional user = userRepository.findByGithubId(githubId); - - if(user == null){ - throw new ApiException(_USER_NOT_FOUND); - } + User user=userRepository.findByGithubId(githubId).orElseThrow(()->new ApiException(_USER_NOT_FOUND)); - String gitHubAccessToken = user.get().getGitHubAccessToken(); + String gitHubAccessToken = user.getGitHubAccessToken(); String decodedGitHubAccessToken = loginCommandService.decrypt(gitHubAccessToken); return decodedGitHubAccessToken; } + + + } \ No newline at end of file diff --git a/src/main/java/com/leets/commitatobe/global/response/code/status/ErrorStatus.java b/src/main/java/com/leets/commitatobe/global/response/code/status/ErrorStatus.java index 74af7b8..db4b16b 100644 --- a/src/main/java/com/leets/commitatobe/global/response/code/status/ErrorStatus.java +++ b/src/main/java/com/leets/commitatobe/global/response/code/status/ErrorStatus.java @@ -40,8 +40,8 @@ public enum ErrorStatus implements BaseErrorCode { // 디코딩 오류 _DECRYPT_ERROR(HttpStatus.BAD_REQUEST, "DECRYPT_001", "토큰 디코딩 과정에서 오류가 발생했습니다."), - // 유저 관련 - _USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER_001", "DB에서 유저를 불러오는 과정에서 오류가 발생했습니다."); + //검색 기능 관련 + _USER_NOT_FOUND(HttpStatus.NOT_FOUND,"USER_001","해당 유저가 존재하지 않습니다"); private final HttpStatus httpStatus; private final String code;