Skip to content

Commit 410c518

Browse files
committed
feat/#139
2 parents 38d14ab + 84a1943 commit 410c518

File tree

12 files changed

+158
-36
lines changed

12 files changed

+158
-36
lines changed

โ€Žsrc/main/java/cmf/commitField/domain/admin/controller/ApiV1PetImgController.java renamed to โ€Žsrc/main/java/cmf/commitField/domain/admin/admin/controller/ApiV1PetImgController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package cmf.commitField.domain.admin.controller;
1+
package cmf.commitField.domain.admin.admin.controller;
22

33
import cmf.commitField.global.aws.s3.S3Service;
44
import lombok.RequiredArgsConstructor;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cmf.commitField.domain.admin.notice.entity;
2+
3+
4+
import cmf.commitField.global.jpa.BaseEntity;
5+
import jakarta.persistence.Entity;
6+
import lombok.*;
7+
import lombok.experimental.SuperBuilder;
8+
9+
@Entity
10+
@SuperBuilder
11+
@Getter
12+
@Setter
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
15+
public class Notice extends BaseEntity {
16+
private String title;
17+
private String content;
18+
}

โ€Žsrc/main/java/cmf/commitField/domain/commit/scheduler/CommitUpdateService.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmf.commitField.domain.commit.scheduler;
22

33
import cmf.commitField.domain.commit.totalCommit.service.TotalCommitService;
4+
import cmf.commitField.domain.noti.noti.service.NotiService;
45
import cmf.commitField.domain.user.dto.UserInfoDto;
56
import cmf.commitField.domain.user.entity.Tier;
67
import cmf.commitField.domain.user.entity.User;
@@ -16,9 +17,9 @@
1617
public class CommitUpdateService {
1718
private final TotalCommitService totalCommitService;
1819
private final UserRepository userRepository;
19-
private final SimpMessagingTemplate messagingTemplate;
20+
private final NotiService notiService;
2021

21-
// ์œ ์ € ๋žญํ‚น ์ƒ์Šน ๋กœ์ง“
22+
// ์œ ์ € ๋žญํ‚น ์ƒ์Šน ๋กœ์ง
2223
public UserInfoDto updateUserTier(String username){
2324
User user = userRepository.findByUsername(username).get();
2425
long seasonCommitCount;
@@ -27,6 +28,8 @@ public UserInfoDto updateUserTier(String username){
2728
System.out.println(username+"์œ ์ € ๋ ˆ๋ฒจ ์—…! ํ˜„์žฌ ํ‹ฐ์–ด: "+user.getTier());
2829
userRepository.save(user);
2930

31+
notiService.createRankUpNoti(user);
32+
3033
return UserInfoDto.builder()
3134
.username(user.getUsername())
3235
.email(user.getEmail())

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/controller/ApiV1NotiController.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
public class ApiV1NotiController {
3131
private final NotiService notiService;
3232
private final UserRepository userRepository;
33-
private final ApplicationEventPublisher eventPublisher;
3433

3534
@GetMapping("")
3635
public GlobalResponse<List<NotiDto>> getNoti(@AuthenticationPrincipal OAuth2User oAuth2User) {
@@ -40,11 +39,6 @@ public GlobalResponse<List<NotiDto>> getNoti(@AuthenticationPrincipal OAuth2User
4039
return GlobalResponse.success(notis);
4140
}
4241

43-
@PostMapping("")
44-
public void createNoti() {
45-
46-
}
47-
4842
@PostMapping("/read")
4943
public GlobalResponse<Object> readNoti() {
5044
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/entity/NotiDetailType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
public enum NotiDetailType {
44
// ์—…์ 
5+
RANK_UP, // ๋žญํ‚น ์ƒ์Šน
56
ACHIEVEMENT_COMPLETED, // ์—…์  ๋‹ฌ์„ฑ
67

78
// ์—ฐ์†
89
STREAK_CONTINUED, // ์—ฐ์† ์ปค๋ฐ‹ ์ด์–ด์ง
910
STREAK_BROKEN, // ์—ฐ์† ์ปค๋ฐ‹ ๋Š๊น€
1011

11-
// ์‹œ์ฆŒ
12+
NOTICE_CREATED, // ์‹œ์ฆŒ
1213
SEASON_START // ์‹œ์ฆŒ ์‹œ์ž‘
1314
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ public class NotiMessageTemplates {
88
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}] ์‹œ์ฆŒ ์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๋žญํ‚น ๊ฒฝ์Ÿ์„ ์ค€๋น„ํ•˜์„ธ์š”!",
12+
NotiDetailType.RANK_UP, "๐Ÿ“ˆ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! {0}๋‹˜์˜ ๋žญํ‚น์ด {1}(์œผ)๋กœ ์ƒ์Šนํ–ˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽŠ",
13+
NotiDetailType.NOTICE_CREATED, "๐Ÿ“ข ๊ณต์ง€์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค: {0}"
1214
);
1315

1416
// ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ํ…œํ”Œ๋ฆฟ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์„œ๋“œ

โ€Žsrc/main/java/cmf/commitField/domain/noti/noti/repository/NotiRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import cmf.commitField.domain.noti.noti.dto.NotiDto;
44
import cmf.commitField.domain.noti.noti.entity.Noti;
5+
import cmf.commitField.domain.noti.noti.entity.NotiDetailType;
6+
import cmf.commitField.domain.noti.noti.entity.NotiType;
57
import cmf.commitField.domain.user.entity.User;
68
import io.lettuce.core.dynamic.annotation.Param;
79
import org.springframework.data.jpa.repository.JpaRepository;
810
import org.springframework.data.jpa.repository.Query;
911
import org.springframework.stereotype.Repository;
1012

13+
import java.time.LocalDateTime;
1114
import java.util.List;
1215
import java.util.Optional;
1316

@@ -18,4 +21,7 @@ public interface NotiRepository extends JpaRepository<Noti, Long> {
1821
"FROM Noti n JOIN n.receiver u WHERE u.id = :receiverId AND n.isRead = :isRead")
1922
Optional<List<NotiDto>> findNotiDtoByReceiverId(@Param("receiverId") Long receiverId, @Param("isRead") boolean isRead);
2023
Optional<List<Noti>> findNotiByReceiver(User receiver);
24+
25+
// ์ตœ๊ทผ 10์ผ ๋‚ด ๋™์ผํ•œ ์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
26+
boolean existsByReceiverAndTypeCodeAndType2CodeAndCreatedAtAfter(User receiver, NotiType type, NotiDetailType detailType, LocalDateTime after);
2127
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package cmf.commitField.domain.noti.noti.service;
2+
3+
import cmf.commitField.domain.noti.noti.entity.NotiDetailType;
4+
import cmf.commitField.domain.noti.noti.entity.NotiType;
5+
import cmf.commitField.domain.noti.noti.repository.NotiRepository;
6+
import cmf.commitField.domain.user.entity.User;
7+
import cmf.commitField.domain.user.repository.UserRepository;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.springframework.scheduling.annotation.Scheduled;
11+
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
import java.time.LocalDateTime;
15+
import java.time.LocalTime;
16+
import java.util.List;
17+
18+
@Service
19+
@RequiredArgsConstructor
20+
@Slf4j
21+
public class CommitAbsenceNotiService {
22+
private final UserRepository userRepository;
23+
private final NotiService notiService;
24+
private final NotiRepository notiRepository;
25+
26+
// ๋งค์ผ 10์‹œ ์‹คํ–‰
27+
@Scheduled(cron = "0 0 10 * * *")
28+
@Transactional
29+
public void sendCommitAbsenceNoti() {
30+
log.info("์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ ์ „์†ก ์‹œ์ž‘");
31+
LocalDateTime today = LocalDateTime.now();
32+
LocalDateTime thresholdDate = today.minusDays(10); // 10์ผ ์ „ ๋‚ ์งœ ๊ณ„์‚ฐ
33+
34+
// ๋งˆ์ง€๋ง‰ ์ปค๋ฐ‹์ด 10์ผ ์ด์ƒ ์ง€๋‚œ ์‚ฌ์šฉ์ž ์ฐพ๊ธฐ
35+
List<User> inactiveUsers = userRepository.findUsersWithLastCommitBefore(thresholdDate);
36+
37+
for (User user : inactiveUsers) {
38+
if (!hasRecentAbsenceNoti(user)) { // ์ตœ๊ทผ ์•Œ๋ฆผ์ด ์—†๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ƒ์„ฑ
39+
notiService.createStreakBrokenNoti(user);
40+
log.info("์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ ์ „์†ก: {}", user.getUsername());
41+
}
42+
}
43+
}
44+
45+
// ์ตœ๊ทผ 10์ผ ๋‚ด ์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ์ด ์žˆ์—ˆ๋Š”์ง€ ํ™•์ธ
46+
private boolean hasRecentAbsenceNoti(User user) {
47+
log.info("์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ ํ™•์ธ: {}", user.getUsername());
48+
LocalDateTime checkDate = LocalDateTime.now().minusDays(10);
49+
return notiRepository.existsByReceiverAndTypeCodeAndType2CodeAndCreatedAtAfter(
50+
user, NotiType.STREAK, NotiDetailType.STREAK_BROKEN, checkDate.toLocalDate().atTime(LocalTime.MIN)
51+
);
52+
}
53+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cmf.commitField.domain.noti.noti.service;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.stereotype.Service;
6+
7+
@Service
8+
@RequiredArgsConstructor
9+
@Slf4j
10+
public class CommitSteakNotiService {
11+
private final NotiService notiService;
12+
13+
}

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

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import cmf.commitField.domain.noti.noti.entity.NotiType;
88
import cmf.commitField.domain.noti.noti.event.NotiEvent;
99
import cmf.commitField.domain.noti.noti.repository.NotiRepository;
10+
import cmf.commitField.domain.season.entity.Rank;
1011
import cmf.commitField.domain.season.entity.Season;
1112
import cmf.commitField.domain.user.entity.User;
1213
import cmf.commitField.domain.user.repository.UserRepository;
@@ -38,34 +39,39 @@ public static String generateMessage(NotiDetailType type, Object... params) {
3839
return message;
3940
}
4041

42+
// ์•Œ๋ฆผ ์ƒ์„ฑ
4143
@Transactional
42-
public void createNoti(User receiver) {
43-
System.out.println("์•Œ๋ฆผ ์ƒ์„ฑ");
44-
String message = NotiService.generateMessage(NotiDetailType.STREAK_BROKEN, receiver.getNickname());
44+
public void createNoti(User receiver, NotiType notiType, NotiDetailType notiDetailType, Long relId, String relTypeCode, Object... params) {
45+
// ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
46+
String message = NotiService.generateMessage(notiDetailType, params);
4547

48+
// ์•Œ๋ฆผ ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ
4649
Noti noti = Noti.builder()
47-
.typeCode(NotiType.STREAK)
48-
.type2Code(NotiDetailType.STREAK_BROKEN)
50+
.typeCode(notiType)
51+
.type2Code(notiDetailType)
4952
.receiver(receiver)
5053
.isRead(false)
5154
.message(message)
55+
.relId(relId)
56+
.relTypeCode(relTypeCode)
5257
.build();
5358

59+
notiRepository.save(noti);
60+
61+
// WebSocket ์ด๋ฒคํŠธ ๋ฐœ์ƒ
5462
List<NotiDto> notis = new ArrayList<>();
55-
Noti savedNoti = notiRepository.save(noti);
56-
notis.add(new NotiDto(savedNoti.getId(), savedNoti.getMessage(), savedNoti.getCreatedAt()));
63+
notis.add(new NotiDto(noti.getId(), noti.getMessage(), noti.getCreatedAt()));
5764
NotiEvent event = new NotiEvent(this, receiver.getUsername(), notis, "์ƒˆ๋กœ์šด ์•Œ๋ฆผ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
5865
eventPublisher.publishEvent(event);
5966
}
6067

61-
68+
// ์•Œ๋ฆผ ์กฐํšŒ
6269
public List<NotiDto> getNotReadNoti(User receiver) {
63-
System.out.println("์•Œ๋ฆผ ์กฐํšŒ");
6470
List<NotiDto> notis = notiRepository.findNotiDtoByReceiverId(receiver.getId(), false).orElse(null);
65-
System.out.println("์•Œ๋ฆผ ์กฐํšŒ ๋");
6671
return notis;
6772
}
6873

74+
// ์‹œ์ฆŒ ์•Œ๋ฆผ ํ™•์ธ
6975
public List<Noti> getSeasonNotiCheck(User receiver, long seasonId) {
7076
return notiRepository.findNotiByReceiverAndRelId(receiver, seasonId)
7177
.orElseThrow(() -> new CustomException(ErrorCode.ERROR_CHECK)); // ์•Œ๋ฆผ์ด ์—†์„ ๊ฒฝ์šฐ ์˜ˆ์™ธ ๋ฐœ์ƒ
@@ -74,23 +80,37 @@ public List<Noti> getSeasonNotiCheck(User receiver, long seasonId) {
7480
// ์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ
7581
@Transactional
7682
public void createNewSeasonNoti(Season season, User user) {
77-
System.out.println("์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ");
78-
// ๋ฉ”์‹œ์ง€ ์ƒ์„ฑ
79-
String message = NotiService.generateMessage(NotiDetailType.SEASON_START, season.getName());
83+
createNoti(user, NotiType.SEASON, NotiDetailType.SEASON_START, season.getId(), season.getModelName(), season.getName());
84+
}
8085

81-
Noti noti = Noti.builder()
82-
.typeCode(NotiType.SEASON)
83-
.type2Code(NotiDetailType.SEASON_START)
84-
.receiver(user)
85-
.isRead(false)
86-
.message(message)
87-
.relId(season.getId())
88-
.relTypeCode(season.getModelName())
89-
.build();
86+
// ๋žญํ‚น ์—… ์•Œ๋ฆผ ์ƒ์„ฑ
87+
@Transactional
88+
public void createRankUpNoti(User user) {
89+
createNoti(user, NotiType.RANK, NotiDetailType.RANK_UP, 0L, null, getDisplayName(user), user.getTier().name());
90+
}
9091

91-
notiRepository.save(noti);
92+
// ์—ฐ์† ์ปค๋ฐ‹ ์ถ•ํ•˜ ์•Œ๋ฆผ ์ƒ์„ฑ
93+
@Transactional
94+
public void createStreakCommitNoti(User user, String days) {
95+
createNoti(user, NotiType.STREAK, NotiDetailType.STREAK_CONTINUED, 0L, null, getDisplayName(user), days);
96+
}
97+
98+
// ์ปค๋ฐ‹ ๋ถ€์žฌ ์•Œ๋ฆผ ์ƒ์„ฑ
99+
@Transactional
100+
public void createStreakBrokenNoti(User user) {
101+
createNoti(user, NotiType.STREAK, NotiDetailType.STREAK_BROKEN, 0L, null, getDisplayName(user));
102+
}
92103

93-
System.out.println("์ƒˆ ์‹œ์ฆŒ ์•Œ๋ฆผ ์ƒ์„ฑ ๋");
104+
// ์—…์  ์•Œ๋ฆผ ์ƒ์„ฑ
105+
@Transactional
106+
public void createAchievementNoti(User user, String achievementName) {
107+
createNoti(user, NotiType.ACHIEVEMENT, NotiDetailType.ACHIEVEMENT_COMPLETED, 0L, null, getDisplayName(user), achievementName);
108+
}
109+
110+
// ๊ณต์ง€์‚ฌํ•ญ ์•Œ๋ฆผ ์ƒ์„ฑ
111+
@Transactional
112+
public void createNoticeNoti(User user, String noticeTitle) {
113+
createNoti(user, NotiType.NOTICE, NotiDetailType.NOTICE_CREATED, 0L, null, noticeTitle);
94114
}
95115

96116
// ์ฝ์Œ ์ฒ˜๋ฆฌ
@@ -104,4 +124,8 @@ public List<Noti> read(User receiver) {
104124
System.out.println("์•Œ๋ฆผ ์ฝ์Œ ์ฒ˜๋ฆฌ ๋");
105125
return notis;
106126
}
127+
128+
private String getDisplayName(User user) {
129+
return user.getNickname() != null ? user.getNickname() : user.getUsername();
130+
}
107131
}

โ€Žsrc/main/java/cmf/commitField/domain/user/repository/UserRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import org.springframework.data.repository.query.Param;
88
import org.springframework.stereotype.Repository;
99

10+
import java.time.LocalDate;
11+
import java.time.LocalDateTime;
12+
import java.util.List;
1013
import java.util.Optional;
1114

1215
@Repository
@@ -18,4 +21,7 @@ public interface UserRepository extends JpaRepository<User, Long> {
1821
@Modifying
1922
@Query("UPDATE User u SET u.status = :status WHERE u.username = :username")
2023
void updateStatus(@Param("username") String username, @Param("status") boolean status);
24+
25+
@Query("SELECT u FROM User u WHERE u.lastCommitted <= :date")
26+
List<User> findUsersWithLastCommitBefore(@Param("date") LocalDateTime date);
2127
}

โ€Žsrc/main/java/cmf/commitField/global/scheduler/NotiTestScheduler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package cmf.commitField.global.scheduler;
22

3+
import cmf.commitField.domain.noti.noti.entity.NotiDetailType;
4+
import cmf.commitField.domain.noti.noti.entity.NotiType;
35
import cmf.commitField.domain.noti.noti.service.NotiService;
46
import cmf.commitField.domain.user.entity.User;
57
import cmf.commitField.domain.user.repository.UserRepository;
@@ -22,6 +24,6 @@ public void test() {
2224
System.out.println("test ์‹คํ–‰");
2325

2426
User user = userRepository.findById(1L).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
25-
notiService.createNoti(user);
27+
notiService.createStreakBrokenNoti(user);
2628
}
2729
}

0 commit comments

Comments
ย (0)