diff --git a/src/main/java/cmf/commitField/domain/commit/sinceCommit/entity/CommitHistory.java b/src/main/java/cmf/commitField/domain/commit/sinceCommit/entity/CommitHistory.java deleted file mode 100644 index 8e5854b..0000000 --- a/src/main/java/cmf/commitField/domain/commit/sinceCommit/entity/CommitHistory.java +++ /dev/null @@ -1,27 +0,0 @@ -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; // 커밋한 날짜 -} - diff --git a/src/main/java/cmf/commitField/domain/commit/sinceCommit/repositoty/CommitHistoryRepository.java b/src/main/java/cmf/commitField/domain/commit/sinceCommit/repositoty/CommitHistoryRepository.java deleted file mode 100644 index 083a80a..0000000 --- a/src/main/java/cmf/commitField/domain/commit/sinceCommit/repositoty/CommitHistoryRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -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 { - // 특정 유저의 최신 커밋 기록 조회 - Optional findTopByUsernameOrderByCommitDateDesc(String username); -} diff --git a/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/SinceCommitService.java b/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/SinceCommitService.java index f1ad2d9..be1f3ec 100644 --- a/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/SinceCommitService.java +++ b/src/main/java/cmf/commitField/domain/commit/sinceCommit/service/SinceCommitService.java @@ -2,8 +2,6 @@ import cmf.commitField.domain.commit.sinceCommit.dto.CommitAnalysisResponseDto; import cmf.commitField.domain.commit.sinceCommit.dto.SinceCommitResponseDto; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java b/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java index 3825472..59572fc 100644 --- a/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java +++ b/src/main/java/cmf/commitField/domain/noti/noti/entity/Noti.java @@ -3,12 +3,15 @@ import cmf.commitField.domain.user.entity.User; import cmf.commitField.global.jpa.BaseEntity; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.SuperBuilder; +import org.hibernate.annotations.ColumnDefault; import static lombok.AccessLevel.PROTECTED; @@ -19,13 +22,16 @@ @Getter @Setter public class Noti extends BaseEntity { + @Enumerated(EnumType.STRING) + private NotiType typeCode; // 알림 타입 + private NotiDetailType type2Code; // 알림 세부 타입 @ManyToOne - private User actor; - @ManyToOne - private User receiver; - private String relTypeCode; - private long relId; - private String typeCode; - private String type2Code; - private boolean read; + private User receiver; // 알림을 받는 사람 + @ColumnDefault("false") + private boolean isRead; // 읽음 상태 + private String message; // 알림 메시지 + + // TODO: 알림이 연결된 객체 어떻게 처리할지 고민 필요. +// private String relTypeCode; // 알림이 연결된 실제 객체 유형 +// private long relId; // 알림 객체의 Id } diff --git a/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiDetailType.java b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiDetailType.java new file mode 100644 index 0000000..2c168e9 --- /dev/null +++ b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiDetailType.java @@ -0,0 +1,13 @@ +package cmf.commitField.domain.noti.noti.entity; + +public enum NotiDetailType { + // 업적 + ACHIEVEMENT_COMPLETED, // 업적 달성 + + // 연속 + STREAK_CONTINUED, // 연속 커밋 이어짐 + STREAK_BROKEN, // 연속 커밋 끊김 + + // 시즌 + SEASON_START // 시즌 시작 +} diff --git a/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiMessageTemplates.java b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiMessageTemplates.java new file mode 100644 index 0000000..bb6064f --- /dev/null +++ b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiMessageTemplates.java @@ -0,0 +1,18 @@ +package cmf.commitField.domain.noti.noti.entity; + +import java.util.Map; + +public class NotiMessageTemplates { + // 알림 메시지 템플릿을 저장하는 맵 + private static final Map TEMPLATES = Map.of( + NotiDetailType.ACHIEVEMENT_COMPLETED, "🎉 {0}님이 '{1}' 업적을 달성했습니다!", + NotiDetailType.STREAK_CONTINUED, "🔥 {0}님의 연속 커밋이 {1}일째 이어지고 있습니다!", + NotiDetailType.STREAK_BROKEN, "😢 {0}님의 연속 커밋 기록이 끊겼습니다. 다음번엔 더 오래 유지해봐요!", + NotiDetailType.SEASON_START, "🚀 새로운 시즌 '{0}'이 시작되었습니다! 랭킹 경쟁을 준비하세요!" + ); + + // 알림 메시지 템플릿을 반환하는 메서드 + public static String getTemplate(NotiDetailType type) { + return TEMPLATES.getOrDefault(type, "알림 메시지가 없습니다."); + } +} diff --git a/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiType.java b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiType.java new file mode 100644 index 0000000..a1cce36 --- /dev/null +++ b/src/main/java/cmf/commitField/domain/noti/noti/entity/NotiType.java @@ -0,0 +1,10 @@ +package cmf.commitField.domain.noti.noti.entity; + +public enum NotiType { + RANK, // 랭킹 + ACHIEVEMENT, // 업적 + ABSENCE, // 부재 + STREAK, // 연속 + SEASON, // 시즌 + NOTICE // 공지 +} diff --git a/src/main/java/cmf/commitField/domain/noti/noti/eventListener/NotiEventListener.java b/src/main/java/cmf/commitField/domain/noti/noti/eventListener/NotiEventListener.java index 0eb3ba2..0766f13 100644 --- a/src/main/java/cmf/commitField/domain/noti/noti/eventListener/NotiEventListener.java +++ b/src/main/java/cmf/commitField/domain/noti/noti/eventListener/NotiEventListener.java @@ -1,24 +1,35 @@ package cmf.commitField.domain.noti.noti.eventListener; +import cmf.commitField.domain.noti.noti.entity.NotiDetailType; +import cmf.commitField.domain.noti.noti.entity.NotiType; import cmf.commitField.domain.noti.noti.service.NotiService; +import cmf.commitField.global.chat.ChatMessageDto; +import cmf.commitField.global.event.CommitHistoryEvent; import lombok.RequiredArgsConstructor; +import org.springframework.kafka.annotation.KafkaListener; 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); -// } + // 예시 + // CommitHistoryEvent 이벤트가 발생하면 notiService.createCommitStreak() 메서드를 실행 + public void listenCommitStreak(CommitHistoryEvent event){ + notiService.createCommitStreak(event.getUsername(), NotiType.STREAK, NotiDetailType.STREAK_CONTINUED); + } + + @KafkaListener(topics = "chat-room-1", groupId = "1") + public void consume(ChatMessageDto message){ + System.out.println("Consumed message: " + message); + } + + @KafkaListener(topics = "chat-room-1-dlt", groupId = "1") + public void consumeChatRoom1DLT(byte[] in){ + String message = new String(in); + System.out.println("Failed message: " + message); + } } diff --git a/src/main/java/cmf/commitField/domain/noti/noti/service/NotiService.java b/src/main/java/cmf/commitField/domain/noti/noti/service/NotiService.java index fda1ccb..4882ef3 100644 --- a/src/main/java/cmf/commitField/domain/noti/noti/service/NotiService.java +++ b/src/main/java/cmf/commitField/domain/noti/noti/service/NotiService.java @@ -1,23 +1,58 @@ package cmf.commitField.domain.noti.noti.service; +import cmf.commitField.domain.noti.noti.entity.Noti; +import cmf.commitField.domain.noti.noti.entity.NotiDetailType; +import cmf.commitField.domain.noti.noti.entity.NotiMessageTemplates; +import cmf.commitField.domain.noti.noti.entity.NotiType; import cmf.commitField.domain.noti.noti.repository.NotiRepository; +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 lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.text.MessageFormat; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @Slf4j public class NotiService { private final NotiRepository notiRepository; + private final UserRepository userRepository; + + // 알림 메시지 생성 + public static String generateMessage(NotiDetailType type, Object... params) { + String template = NotiMessageTemplates.getTemplate(type); + return MessageFormat.format(template, params); + } + + // 연속 커밋 알림 생성 + @Transactional + public Noti createCommitStreak(String username, NotiType type, NotiDetailType detailType, Object... params) { + // 메시지 생성 + String message = NotiService.generateMessage(detailType, params); - public void sendCommitStreakNotification(String username, int streakCount) { - log.info("🎉 {}님의 연속 커밋이 {}일로 증가했습니다!", username, streakCount); - // 알림을 DB 저장 또는 웹소켓 / 이메일 / 푸시 알림 전송 가능 + // 사용자 조회 (없으면 예외 처리) + User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER)); + + // 알림 객체 생성 후 저장 + Noti noti = Noti.builder() + .typeCode(type) + .type2Code(detailType) + .receiver(user) + .isRead(false) + .message(message) + .build(); + + return notiRepository.save(noti); } + + // public CommitAnalysisResponseDto getCommitAnalysis(String owner, String repo, String username, LocalDateTime since, LocalDateTime until) { // List commits = getSinceCommits(owner, repo, since, until); // StreakResult streakResult = calculateStreaks(commits); diff --git a/src/main/java/cmf/commitField/global/chat/ChatMessageDto.java b/src/main/java/cmf/commitField/global/chat/ChatMessageDto.java new file mode 100644 index 0000000..3369791 --- /dev/null +++ b/src/main/java/cmf/commitField/global/chat/ChatMessageDto.java @@ -0,0 +1,12 @@ +package cmf.commitField.global.chat; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ChatMessageDto { + private String msg; +} diff --git a/src/main/java/cmf/commitField/global/event/CommitHistoryEvent.java b/src/main/java/cmf/commitField/global/event/CommitHistoryEvent.java new file mode 100644 index 0000000..61f556b --- /dev/null +++ b/src/main/java/cmf/commitField/global/event/CommitHistoryEvent.java @@ -0,0 +1,14 @@ +package cmf.commitField.global.event; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +// event 객체 참고 +@Getter +public class CommitHistoryEvent extends ApplicationEvent { + private final String username; + public CommitHistoryEvent(Object source, String username) { + super(source); + this.username = username; + } +} diff --git a/src/main/java/cmf/commitField/global/jpa/BaseEntity.java b/src/main/java/cmf/commitField/global/jpa/BaseEntity.java index 11787bf..b63afa2 100644 --- a/src/main/java/cmf/commitField/global/jpa/BaseEntity.java +++ b/src/main/java/cmf/commitField/global/jpa/BaseEntity.java @@ -35,4 +35,9 @@ public class BaseEntity { @CreatedDate @Getter private LocalDateTime modifiedAt; + + public String getModelName() { + String simpleName = this.getClass().getSimpleName(); + return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); + } } \ No newline at end of file