Skip to content

Commit 1973620

Browse files
authored
Merge pull request #98 from CommitField/feat/#81
feat : μ•Œλ¦Ό 데이터 μ €μž₯ 및 ν”„λ‘ νŠΈ 연동 ν…ŒμŠ€νŠΈ (#18)
2 parents b0ce704 + 230c89e commit 1973620

File tree

12 files changed

+248
-36
lines changed

12 files changed

+248
-36
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package cmf.commitField.domain.noti.noti.controller;
2+
3+
import cmf.commitField.domain.noti.noti.dto.NotiDto;
4+
import cmf.commitField.domain.noti.noti.entity.Noti;
5+
import cmf.commitField.domain.noti.noti.service.NotiService;
6+
import cmf.commitField.domain.user.entity.User;
7+
import cmf.commitField.domain.user.repository.UserRepository;
8+
import cmf.commitField.domain.user.service.CustomOAuth2UserService;
9+
import cmf.commitField.global.error.ErrorCode;
10+
import cmf.commitField.global.exception.CustomException;
11+
import cmf.commitField.global.globalDto.GlobalResponse;
12+
import lombok.RequiredArgsConstructor;
13+
import lombok.extern.slf4j.Slf4j;
14+
import org.springframework.security.core.Authentication;
15+
import org.springframework.security.core.context.SecurityContextHolder;
16+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
17+
import org.springframework.security.oauth2.core.user.OAuth2User;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.RequestMapping;
20+
import org.springframework.web.bind.annotation.RestController;
21+
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.stream.Collectors;
25+
26+
@RestController
27+
@RequestMapping("/api/notifications")
28+
@RequiredArgsConstructor
29+
@Slf4j
30+
public class ApiV1NotiController {
31+
private final NotiService notiService;
32+
private final CustomOAuth2UserService customOAuth2UserService;
33+
private final UserRepository userRepository;
34+
35+
@GetMapping("")
36+
public GlobalResponse<List<NotiDto>> getNoti() {
37+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
38+
log.info("getNoti - userRequest: {}", authentication);
39+
40+
if (authentication instanceof OAuth2AuthenticationToken) {
41+
OAuth2User principal = (OAuth2User) authentication.getPrincipal();
42+
Map<String, Object> attributes = principal.getAttributes();
43+
String username = (String) attributes.get("login"); // GitHub ID
44+
User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
45+
List<Noti> notis = notiService.getNotReadNoti(user);
46+
47+
List<NotiDto> notiDtos = notis.stream()
48+
.map(NotiDto::new)
49+
.collect(Collectors.toList());
50+
return GlobalResponse.success(notiDtos);
51+
}
52+
53+
return GlobalResponse.error(ErrorCode.LOGIN_REQUIRED);
54+
}
55+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmf.commitField.domain.noti.noti.dto;
2+
3+
import cmf.commitField.domain.noti.noti.entity.Noti;
4+
import lombok.Getter;
5+
6+
import java.time.LocalDateTime;
7+
import java.time.format.DateTimeFormatter;
8+
import java.time.temporal.ChronoUnit;
9+
10+
@Getter
11+
public class NotiDto {
12+
private String message;
13+
private String formattedCreatedAt; // λ³€ν™˜λœ λ‚ μ§œλ₯Ό μ €μž₯ν•  ν•„λ“œ
14+
15+
public NotiDto(Noti noti) {
16+
this.message = noti.getMessage();
17+
this.formattedCreatedAt = formatCreatedAt(noti.getCreatedAt()); // λ³€ν™˜λœ λ‚ μ§œ μ €μž₯
18+
}
19+
20+
private String formatCreatedAt(LocalDateTime createdAt) {
21+
LocalDateTime today = LocalDateTime.now();
22+
long daysBetween = ChronoUnit.DAYS.between(createdAt, today);
23+
24+
if (daysBetween == 0) {
25+
return "였늘";
26+
} else if (daysBetween == 1) {
27+
return "μ–΄μ œ";
28+
} else if (daysBetween == 2) {
29+
return "1일 μ „";
30+
} else if (daysBetween == 3) {
31+
return "2일 μ „";
32+
} else if (daysBetween == 4) {
33+
return "3일 μ „";
34+
} else {
35+
return createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
36+
}
37+
}
38+
}

β€Žsrc/main/java/cmf/commitField/domain/noti/noti/entity/Noti.javaβ€Ž

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
public class Noti extends BaseEntity {
2525
@Enumerated(EnumType.STRING)
2626
private NotiType typeCode; // μ•Œλ¦Ό νƒ€μž…
27+
@Enumerated(EnumType.STRING)
2728
private NotiDetailType type2Code; // μ•Œλ¦Ό μ„ΈλΆ€ νƒ€μž…
2829
@ManyToOne
2930
private User receiver; // μ•Œλ¦Όμ„ λ°›λŠ” μ‚¬λžŒ
@@ -32,7 +33,7 @@ public class Noti extends BaseEntity {
3233
private String message; // μ•Œλ¦Ό λ©”μ‹œμ§€
3334

3435
// TODO: μ•Œλ¦Όμ΄ μ—°κ²°λœ 객체 μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€ κ³ λ―Ό ν•„μš”.
35-
// private String relTypeCode; // μ•Œλ¦Όμ΄ μ—°κ²°λœ μ‹€μ œ 객체 μœ ν˜•
36-
// private long relId; // μ•Œλ¦Ό 객체의 Id
36+
private String relTypeCode; // μ•Œλ¦Όμ΄ μ—°κ²°λœ μ‹€μ œ 객체 μœ ν˜•
37+
private long relId; // μ•Œλ¦Ό 객체의 Id
3738

3839
}

β€Žsrc/main/java/cmf/commitField/domain/noti/noti/entity/NotiMessageTemplates.javaβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
public class NotiMessageTemplates {
66
// μ•Œλ¦Ό λ©”μ‹œμ§€ ν…œν”Œλ¦Ώμ„ μ €μž₯ν•˜λŠ” λ§΅
77
private static final Map<NotiDetailType, String> TEMPLATES = Map.of(
8-
NotiDetailType.ACHIEVEMENT_COMPLETED, "πŸŽ‰ {0}λ‹˜μ΄ '{1}' 업적을 λ‹¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€!",
8+
NotiDetailType.ACHIEVEMENT_COMPLETED, "πŸŽ‰ {0}λ‹˜μ΄ [{1}] 업적을 λ‹¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€!",
99
NotiDetailType.STREAK_CONTINUED, "πŸ”₯ {0}λ‹˜μ˜ 연속 컀밋이 {1}일째 이어지고 μžˆμŠ΅λ‹ˆλ‹€!",
1010
NotiDetailType.STREAK_BROKEN, "😒 {0}λ‹˜μ˜ 연속 컀밋 기둝이 λŠκ²ΌμŠ΅λ‹ˆλ‹€. λ‹€μŒλ²ˆμ—” 더 였래 μœ μ§€ν•΄λ΄μš”!",
11-
NotiDetailType.SEASON_START, "πŸš€ μƒˆλ‘œμš΄ μ‹œμ¦Œ '{0}'이 μ‹œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€! λž­ν‚Ή κ²½μŸμ„ μ€€λΉ„ν•˜μ„Έμš”!"
11+
NotiDetailType.SEASON_START, "πŸš€ μƒˆλ‘œμš΄ [{0}] μ‹œμ¦Œ 이 μ‹œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€! λž­ν‚Ή κ²½μŸμ„ μ€€λΉ„ν•˜μ„Έμš”!"
1212
);
1313

1414
// μ•Œλ¦Ό λ©”μ‹œμ§€ ν…œν”Œλ¦Ώμ„ λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package cmf.commitField.domain.noti.noti.repository;
22

33
import cmf.commitField.domain.noti.noti.entity.Noti;
4+
import cmf.commitField.domain.user.entity.User;
45
import org.springframework.data.jpa.repository.JpaRepository;
56
import org.springframework.stereotype.Repository;
67

8+
import java.util.List;
9+
import java.util.Optional;
10+
711
@Repository
812
public interface NotiRepository extends JpaRepository<Noti, Long> {
13+
Optional<List<Noti>> findNotiByReceiverAndRelId(User receiver, long season);
14+
Optional<List<Noti>> findNotiByReceiverAndIsRead(User receiver, boolean read);
915
}

β€Žsrc/main/java/cmf/commitField/domain/noti/noti/service/NotiService.javaβ€Ž

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import cmf.commitField.domain.noti.noti.entity.NotiMessageTemplates;
66
import cmf.commitField.domain.noti.noti.entity.NotiType;
77
import cmf.commitField.domain.noti.noti.repository.NotiRepository;
8+
import cmf.commitField.domain.season.entity.Season;
9+
import cmf.commitField.domain.season.repository.SeasonRepository;
10+
import cmf.commitField.domain.season.service.SeasonService;
811
import cmf.commitField.domain.user.entity.User;
912
import cmf.commitField.domain.user.repository.UserRepository;
1013
import cmf.commitField.global.error.ErrorCode;
@@ -15,6 +18,7 @@
1518
import org.springframework.transaction.annotation.Transactional;
1619

1720
import java.text.MessageFormat;
21+
import java.util.List;
1822

1923
@Service
2024
@RequiredArgsConstructor
@@ -23,44 +27,57 @@
2327
public class NotiService {
2428
private final NotiRepository notiRepository;
2529
private final UserRepository userRepository;
30+
private final SeasonRepository seasonRepository;
31+
private final SeasonService seasonService;
2632

2733
// μ•Œλ¦Ό λ©”μ‹œμ§€ 생성
2834
public static String generateMessage(NotiDetailType type, Object... params) {
2935
String template = NotiMessageTemplates.getTemplate(type);
30-
return MessageFormat.format(template, params);
36+
log.info("generateMessage - params: {}", params);
37+
log.info("generateMessage - template: {}", template); // template 자체λ₯Ό 좜λ ₯
38+
String message = MessageFormat.format(template, params); // params 배열을 κ·ΈλŒ€λ‘œ 전달
39+
log.info("generateMessage - message: {}", message);
40+
return message;
3141
}
3242

33-
// 연속 컀밋 μ•Œλ¦Ό 생성
34-
@Transactional
35-
public Noti createCommitStreak(String username, NotiType type, NotiDetailType detailType, Object... params) {
36-
// λ©”μ‹œμ§€ 생성
37-
String message = NotiService.generateMessage(detailType, params);
3843

39-
// μ‚¬μš©μž 쑰회 (μ—†μœΌλ©΄ μ˜ˆμ™Έ 처리)
40-
User user = userRepository.findByUsername(username).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
41-
42-
// μ•Œλ¦Ό 객체 생성 ν›„ μ €μž₯
43-
Noti noti = Noti.builder()
44-
.typeCode(type)
45-
.type2Code(detailType)
46-
.receiver(user)
47-
.isRead(false)
48-
.message(message)
49-
.build();
44+
public List<Noti> getNotReadNoti(User receiver) {
45+
log.info("getNotReadNoti - receiver: {}", receiver);
46+
List<Noti> notis = notiRepository.findNotiByReceiverAndIsRead(receiver, false).orElse(null);
47+
log.info("getNotReadNoti - notis: {}", notis);
48+
return notis;
49+
}
5050

51-
return notiRepository.save(noti);
51+
public List<Noti> getSeasonNotiCheck(User receiver, long seasonId) {
52+
log.info("getSeasonNotiCheck - receiver: {}, seasonId: {}", receiver, seasonId);
53+
return notiRepository.findNotiByReceiverAndRelId(receiver, seasonId)
54+
.orElseThrow(() -> new CustomException(ErrorCode.ERROR_CHECK)); // μ•Œλ¦Όμ΄ 없을 경우 μ˜ˆμ™Έ λ°œμƒ
5255
}
5356

57+
// μƒˆ μ‹œμ¦Œ μ•Œλ¦Ό 생성
58+
@Transactional
59+
public void createNewSeason(Season season) {
60+
log.info("createNewSeason - season: {}", season.getName());
61+
// λ©”μ‹œμ§€ 생성
62+
String message = NotiService.generateMessage(NotiDetailType.SEASON_START, season.getName());
63+
log.info("createNewSeason - message: {}", message);
5464

65+
// λͺ¨λ“  μ‚¬μš©μž 쑰회
66+
Iterable<User> users = userRepository.findAll();
5567

56-
// public CommitAnalysisResponseDto getCommitAnalysis(String owner, String repo, String username, LocalDateTime since, LocalDateTime until) {
57-
// List<SinceCommitResponseDto> commits = getSinceCommits(owner, repo, since, until);
58-
// StreakResult streakResult = calculateStreaks(commits);
59-
//
60-
// // 연속 컀밋 수 Redis μ—…λ°μ΄νŠΈ 및 μ•Œλ¦Ό
61-
// streakService.updateStreak(username, streakResult.currentStreak, streakResult.maxStreak);
62-
//
63-
// return new CommitAnalysisResponseDto(commits, streakResult.currentStreak, streakResult.maxStreak);
64-
// }
68+
// λͺ¨λ“  μœ μ € μ•Œλ¦Ό 객체 생성
69+
users.forEach(user -> {
70+
Noti noti = Noti.builder()
71+
.typeCode(NotiType.SEASON)
72+
.type2Code(NotiDetailType.SEASON_START)
73+
.receiver(user)
74+
.isRead(false)
75+
.message(message)
76+
.relId(season.getId())
77+
.relTypeCode(season.getModelName())
78+
.build();
6579

80+
notiRepository.save(noti);
81+
});
82+
}
6683
}

β€Žsrc/main/java/cmf/commitField/domain/user/entity/User.javaβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import cmf.commitField.domain.heart.entity.Heart;
77
import cmf.commitField.domain.pet.entity.Pet;
88
import cmf.commitField.global.jpa.BaseEntity;
9+
import com.fasterxml.jackson.annotation.JsonIgnore;
910
import jakarta.persistence.*;
1011
import lombok.AllArgsConstructor;
1112
import lombok.Getter;
@@ -73,18 +74,23 @@ public static Tier getLevelByExp(int exp) {
7374

7475

7576
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
77+
@JsonIgnore
7678
private List<ChatRoom> chatRooms = new ArrayList<>();
7779

7880
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
81+
@JsonIgnore
7982
private List<UserChatRoom> userChatRooms = new ArrayList<>();
8083

8184
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
85+
@JsonIgnore
8286
private List<ChatMsg> chatMsgs = new ArrayList<>();
8387

8488
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
89+
@JsonIgnore
8590
private List<Pet> pets = new ArrayList<>();
8691

8792
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
93+
@JsonIgnore
8894
private List<Heart> hearts;
8995

9096
public User(String username, String email, String nickname, String avatarUrl, Boolean status, List<ChatRoom> cr, List<UserChatRoom> ucr, List<ChatMsg> cmsg){

β€Žsrc/main/java/cmf/commitField/domain/user/service/CustomOAuth2UserService.javaβ€Ž

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
import cmf.commitField.domain.commit.scheduler.CommitCacheService;
44
import cmf.commitField.domain.commit.totalCommit.service.TotalCommitService;
5+
import cmf.commitField.domain.noti.noti.service.NotiService;
56
import cmf.commitField.domain.pet.entity.Pet;
67
import cmf.commitField.domain.pet.repository.PetRepository;
8+
import cmf.commitField.domain.season.entity.Season;
9+
import cmf.commitField.domain.season.service.SeasonService;
710
import cmf.commitField.domain.user.entity.CustomOAuth2User;
811
import cmf.commitField.domain.user.entity.User;
912
import cmf.commitField.domain.user.repository.UserRepository;
1013
import jakarta.servlet.http.HttpServletRequest;
1114
import jakarta.servlet.http.HttpSession;
1215
import lombok.RequiredArgsConstructor;
16+
import lombok.extern.slf4j.Slf4j;
17+
import org.springframework.data.redis.core.StringRedisTemplate;
1318
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
1419
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
1520
import org.springframework.security.oauth2.core.user.OAuth2User;
@@ -22,12 +27,16 @@
2227

2328
@Service
2429
@RequiredArgsConstructor
30+
@Slf4j
2531
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
2632
private final UserRepository userRepository;
2733
private final PetRepository petRepository;
2834
private final HttpServletRequest request; // HttpServletRequestλ₯Ό μ£Όμž… λ°›μŒ.
2935
private final CommitCacheService commitCacheService;
3036
private final TotalCommitService totalCommitService;
37+
private final NotiService notiService;
38+
private final SeasonService seasonService;
39+
private final StringRedisTemplate redisTemplate;
3140

3241

3342
@Override
@@ -79,6 +88,17 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) {
7988
commitCacheService.updateCachedCommitCount(user.getUsername(),0);
8089
}
8190

91+
// μ‹œμ¦Œ μ•Œλ¦Ό 처리
92+
// μ•Œλ¦Ό ν…Œμ΄λΈ”μ—μ„œ Active인 μ‹œμ¦Œμ˜ μ•Œλ¦Όμ„ ν•΄λ‹Ή μœ μ €κ°€ κ°€μ§€κ³  μžˆλŠ”μ§€ 체크
93+
String season_key = "season_active:" + user.getUsername();
94+
Season season = seasonService.getActiveSeason();
95+
if(notiService.getSeasonNotiCheck(user, season.getId()).isEmpty()){
96+
log.info("User {} does not have season noti", user.getUsername());
97+
// κ°€μ§€κ³  μžˆμ§€ μ•Šλ‹€λ©΄ μ•Œλ¦Όμ„ μΆ”κ°€
98+
notiService.createNewSeason(season);
99+
// redisTemplate.opsForValue().set(season_key, String.valueOf(count), Duration.ofHours(3)); // 3μ‹œκ°„ 캐싱
100+
}
101+
82102
// μ„Έμ…˜μ— μ‚¬μš©μž 정보 μ €μž₯
83103
HttpSession session = request.getSession();
84104
session.setAttribute("username", user.getUsername());

β€Žsrc/main/java/cmf/commitField/global/error/ErrorCode.javaβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public enum ErrorCode {
6767
// Lock
6868
FAILED_GET_LOCK(HttpStatus.LOCKED, "락을 νšλ“ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."),
6969

70+
// Check
71+
ERROR_CHECK(HttpStatus.BAD_REQUEST, "μ—λŸ¬ 체크"),
72+
7073
//Heart
7174
NOT_EXIST_ROOM_HEART(HttpStatus.BAD_REQUEST, "ν•΄λ‹Ή μ±„νŒ…λ°©μ— μ’‹μ•„μš”κ°€ 눌러져 μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€."),
7275
ALREADY_HEART_TO_ROOM(HttpStatus.BAD_REQUEST, "이미 ν•΄λ‹Ή μ±„νŒ…λ°©μ— μ’‹μ•„μš”λ₯Ό λˆ„λ₯΄μ…¨μŠ΅λ‹ˆλ‹€."),

β€Žsrc/main/java/cmf/commitField/global/scheduler/SeasonScheduler.javaβ€Ž

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// java/cmf/commitField/global/scheduler/SeasonScheduler.java
12
package cmf.commitField.global.scheduler;
23

34
import cmf.commitField.domain.season.entity.Rank;
@@ -29,8 +30,8 @@ public class SeasonScheduler {
2930
private final UserRepository userRepository;
3031
private final SeasonService seasonService;
3132

32-
// 맀일 μžμ •λ§ˆλ‹€ μ‹œμ¦Œ 확인 및 생성
33-
@Scheduled(cron = "0 0 0 * * *")
33+
// λ§€λ…„ 3, 6, 9, 12μ›” 1일 μžμ •λ§ˆλ‹€ μ‹œμ¦Œ 확인 및 생성
34+
@Scheduled(cron = "0 0 0 1 3,6,9,12 *")
3435
public void checkAndCreateNewSeason() {
3536
LocalDate today = LocalDate.now();
3637
String seasonName = today.getYear() + " " + getSeasonName(today.getMonthValue());
@@ -43,7 +44,7 @@ public void checkAndCreateNewSeason() {
4344
LocalDateTime startDate = getSeasonStartDate(today.getYear(), today.getMonth());
4445
LocalDateTime endDate = startDate.plusMonths(3).minusSeconds(1);
4546

46-
//ν˜„μž¬ ν™œμ„± μ‹œμ¦Œ μ’…λ£Œ
47+
// ν˜„μž¬ ν™œμ„± μ‹œμ¦Œ μ’…λ£Œ
4748
if (activeSeason != null) {
4849
activeSeason.setStatus(SeasonStatus.INACTIVE);
4950
seasonRepository.save(activeSeason);
@@ -55,6 +56,9 @@ public void checkAndCreateNewSeason() {
5556
// λͺ¨λ“  μœ μ €μ˜ 랭크 μ΄ˆκΈ°ν™”
5657
resetUserRanks(newSeason);
5758

59+
// μƒˆ μ‹œμ¦Œ μ‹œμž‘ μ•Œλ¦Ό μ €μž₯ 및 μ•Œλ¦Ό 전솑
60+
// String message = notiService.createNewSeason(newSeason.getName());
61+
// notiWebSocketHandler.sendNotificationToAllUsers(message);
5862
System.out.println("μƒˆ μ‹œμ¦Œ 생성: " + newSeason.getName());
5963
} else {
6064
System.out.println("이미 ν™œμ„±ν™”λœ μ‹œμ¦Œ: " + activeSeason.getName());
@@ -98,4 +102,4 @@ private LocalDateTime getSeasonStartDate(int year, Month month) {
98102
};
99103
return LocalDateTime.of(year, startMonth, 1, 0, 0);
100104
}
101-
}
105+
}

0 commit comments

Comments
Β (0)