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
4 changes: 4 additions & 0 deletions src/main/java/cmf/commitField/CommitFieldApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableJpaAuditing
// 스케쥴링 활성화
// 테스트시에만 주석 풀기
@EnableScheduling
public class CommitFieldApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ public class CommitScheduler {

@Scheduled(fixedRate = 60000) // 1분마다 실행
public void updateUserCommits() {
log.info("🔍 updateUserCommits 실행중");
List<User> activeUsers = userRepository.findAll(); // 💫 변경 필요, 차후 active 상태인 user만 찾게끔 변경해야 함.

log.info("🔍 Active User Count: {}", activeUsers.size());

for (User user : activeUsers) {
Integer cachedCount = commitCacheService.getCachedCommitCount(user.getUsername());
int newCommitCount = githubService.getUserCommitCount(user.getUsername());

log.info("🔍 User: {}, Commit Count: {}", user.getUsername(), newCommitCount);

if (cachedCount == null || cachedCount != newCommitCount) { // 변화가 있을 때만 처리
commitCacheService.updateCachedCommitCount(user.getUsername(), newCommitCount);
redpandaProducer.sendCommitUpdate(user.getUsername(), newCommitCount);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cmf.commitField.domain.commit.sinceCommit.entity;

import cmf.commitField.global.jpa.BaseEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Entity
@Table(name = "commit_history")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CommitHistory extends BaseEntity {

@Column(nullable = false)
private String username; // GitHub 사용자명

@Column(nullable = false)
private int streak; // 연속 커밋 수

@Column(nullable = false)
private LocalDate commitDate; // 커밋한 날짜
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmf.commitField.domain.commit.sinceCommit.repositoty;

import cmf.commitField.domain.commit.sinceCommit.entity.CommitHistory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface CommitHistoryRepository extends JpaRepository<CommitHistory, Long> {
// 특정 유저의 최신 커밋 기록 조회
Optional<CommitHistory> findTopByUsernameOrderByCommitDateDesc(String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ public class CommitCacheService {
private final StringRedisTemplate redisTemplate;

public Integer getCachedCommitCount(String username) {
String key = "commit:" + username;
String value = redisTemplate.opsForValue().get(key);
return value != null ? Integer.parseInt(value) : null;
log.info("Redis Template: {}", redisTemplate);
String key = "commit:" + username; // Redis 키 생성 (ex: commit:hongildong)
String value = redisTemplate.opsForValue().get(key); // Redis에서 값 가져오기
return value != null ? Integer.parseInt(value) : null; // 값이 있으면 정수 변환, 없으면 null 반환
}

public void updateCachedCommitCount(String username, int count) {
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmf.commitField.domain.noti.noti.entity;

import cmf.commitField.domain.user.entity.User;
import cmf.commitField.global.jpa.BaseEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import static lombok.AccessLevel.PROTECTED;

@Entity
@NoArgsConstructor(access = PROTECTED)
@AllArgsConstructor(access = PROTECTED)
@SuperBuilder
@Getter
@Setter
public class Noti extends BaseEntity {
@ManyToOne
private User actor;
@ManyToOne
private User receiver;
private String relTypeCode;
private long relId;
private String typeCode;
private String type2Code;
private boolean read;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cmf.commitField.domain.noti.noti.eventListener;

import cmf.commitField.domain.noti.noti.service.NotiService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class NotiEventListener {
private final NotiService notiService;

// public void listenPost(PostCreatedEvent event){
// notiService.postCreated(event.getPost());
// }
//
// public void consume(ChatMessageDto message){
// System.out.println("Consumed message: " + message);
// }
//
// public void consumeChatRoom1DLT(byte[] in){
// String message = new String(in);
// System.out.println("Failed message: " + message);
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cmf.commitField.domain.noti.noti.repository;

import cmf.commitField.domain.noti.noti.entity.Noti;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface NotiRepository extends JpaRepository<Noti, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cmf.commitField.domain.noti.noti.service;

import cmf.commitField.domain.noti.noti.repository.NotiRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class NotiService {
private final NotiRepository notiRepository;

public void sendCommitStreakNotification(String username, int streakCount) {
log.info("🎉 {}님의 연속 커밋이 {}일로 증가했습니다!", username, streakCount);
// 알림을 DB 저장 또는 웹소켓 / 이메일 / 푸시 알림 전송 가능
}

// public CommitAnalysisResponseDto getCommitAnalysis(String owner, String repo, String username, LocalDateTime since, LocalDateTime until) {
// List<SinceCommitResponseDto> commits = getSinceCommits(owner, repo, since, until);
// StreakResult streakResult = calculateStreaks(commits);
//
// // 연속 커밋 수 Redis 업데이트 및 알림
// streakService.updateStreak(username, streakResult.currentStreak, streakResult.maxStreak);
//
// return new CommitAnalysisResponseDto(commits, streakResult.currentStreak, streakResult.maxStreak);
// }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cmf.commitField.domain.noti.streak.service;

import cmf.commitField.domain.noti.noti.service.NotiService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class StreakService {
private final RedisTemplate<String, String> redisTemplate;

private final NotiService notiService;

public void updateStreak(String username, int newCurrentStreak, int newMaxStreak) {
String currentStreakKey = "user:" + username + ":current_streak";
String maxStreakKey = "user:" + username + ":max_streak";

int prevCurrentStreak = getStreak(currentStreakKey);
int prevMaxStreak = getStreak(maxStreakKey);

// 연속 커밋이 증가했으면 Redis 업데이트 및 알림 발송
if (newCurrentStreak > prevCurrentStreak) {
// redis 업데이트
redisTemplate.opsForValue().set(currentStreakKey, String.valueOf(newCurrentStreak));

// 알림 발송
// notiService.sendCommitStreakNotification(username, newCurrentStreak);
}
}

private int getStreak(String key) {
String value = redisTemplate.opsForValue().get(key);
return (value != null) ? Integer.parseInt(value) : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import cmf.commitField.domain.season.repository.UserSeasonRepository;
import cmf.commitField.domain.user.entity.User;
import cmf.commitField.domain.user.repository.UserRepository;
import cmf.commitField.global.error.ErrorCode;
import cmf.commitField.global.exception.CustomException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand All @@ -23,7 +25,7 @@ public class UserSeasonService {
// 현재 시즌에 유저 랭크 추가하기 (SEED 등급)
public UserSeason addUserRank(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));

Season season = seasonService.getActiveSeason();

Expand All @@ -38,17 +40,17 @@ public UserSeason addUserRank(Long userId) {
// 유저의 모든 시즌 랭크 조회
public List<UserSeason> getUserRanks(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
return userSeasonRepository.findByUser(user);
}

// 특정 시즌의 유저 랭크 조회
public UserSeason getUserRankBySeason(Long userId, Long seasonId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
Season season = seasonRepository.findById(seasonId)
.orElseThrow(() -> new RuntimeException("Season not found"));
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_SEASON));
return userSeasonRepository.findByUserAndSeason(user, season)
.orElseThrow(() -> new RuntimeException("Rank not found for this season"));
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_SEASON_RANK));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public RedisConnectionFactory redisConnectionFactory() {
configuration.setPort(port);
return new LettuceConnectionFactory(configuration);
}

@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/cmf/commitField/global/error/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public enum ErrorCode {
// member
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다"),

// season
NOT_FOUND_SEASON(HttpStatus.NOT_FOUND, "시즌을 찾을 수 없습니다."),
NOT_FOUND_SEASON_RANK(HttpStatus.NOT_FOUND, "해당 시즌의 랭킹을 찾을 수 없습니다."),

//chatroom
NOT_FOUND_ROOM(HttpStatus.NOT_FOUND, "이미 삭제된 방이거나 방을 찾을 수 없습니다."),
ROOM_USER_FULL(HttpStatus.BAD_REQUEST, "방에 사용자가 다 차 있습니다."),
Expand Down