Skip to content

Commit 9c68743

Browse files
authored
Merge pull request #62 from Central-MakeUs/feature/#58-alarm-ringing-log
알람 울림 API, 푸시 알림 추가
2 parents 1e61902 + c21efdb commit 9c68743

29 files changed

+844
-236
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ repositories {
3232
mavenCentral()
3333
}
3434

35+
tasks.withType(Test).configureEach {
36+
useJUnitPlatform()
37+
}
38+
3539
dependencies {
3640
// --- Spring starters ---
3741
implementation 'org.springframework.boot:spring-boot-starter-web'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package akuma.whiplash.domains.alarm.application.dto.etc;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record RingingPushInfo(
7+
Long alarmId,
8+
Long memberId
9+
) {
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package akuma.whiplash.domains.alarm.application.dto.etc;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record RingingPushTargetDto(
7+
String token,
8+
Long alarmId,
9+
Long memberId
10+
) {
11+
}

src/main/java/akuma/whiplash/domains/alarm/application/mapper/AlarmMapper.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import akuma.whiplash.domains.alarm.persistence.entity.AlarmEntity;
1111
import akuma.whiplash.domains.alarm.persistence.entity.AlarmOccurrenceEntity;
1212
import akuma.whiplash.domains.alarm.persistence.entity.AlarmOffLogEntity;
13+
import akuma.whiplash.domains.alarm.persistence.entity.AlarmRingingLogEntity;
1314
import akuma.whiplash.domains.member.persistence.entity.MemberEntity;
1415
import akuma.whiplash.global.exception.ApplicationException;
1516
import java.time.DayOfWeek;
1617
import java.time.LocalDate;
18+
import java.time.LocalDateTime;
1719
import java.time.format.DateTimeFormatter;
1820
import java.util.List;
1921
import java.util.Objects;
@@ -78,6 +80,18 @@ public static AlarmOccurrenceEntity mapToAlarmOccurrenceForDate(AlarmEntity alar
7880
.build();
7981
}
8082

83+
public static AlarmRingingLogEntity mapToAlarmRingingLogEntity(
84+
AlarmOccurrenceEntity occurrence,
85+
int ringIndex,
86+
LocalDateTime ringedAt
87+
) {
88+
return AlarmRingingLogEntity.builder()
89+
.alarmOccurrence(occurrence)
90+
.ringIndex(ringIndex)
91+
.ringedAt(ringedAt)
92+
.build();
93+
}
94+
8195
public static CreateAlarmOccurrenceResponse mapToCreateAlarmOccurrenceResponse(Long occurrenceId) {
8296
return CreateAlarmOccurrenceResponse.builder()
8397
.occurrenceId(occurrenceId)

src/main/java/akuma/whiplash/domains/alarm/application/scheduler/AlarmReminderScheduler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.Set;
1616
import lombok.RequiredArgsConstructor;
1717
import lombok.extern.slf4j.Slf4j;
18+
import org.springframework.scheduling.annotation.Scheduled;
1819
import org.springframework.stereotype.Service;
1920

2021
@Slf4j
@@ -27,7 +28,8 @@ public class AlarmReminderScheduler {
2728
private final FcmService fcmService;
2829
private final AlarmCommandService alarmCommandService;
2930

30-
// @Scheduled(cron = "0 * * * * *") // 매 분
31+
// 매 분 마다 실행
32+
@Scheduled(cron = "0 * * * * *")
3133
public void sendPreAlarmNotifications() {
3234
log.info("[AlarmReminderScheduler.sendPreAlarmNotifications] 알람 울리기 1시간 전 푸시 알림 전송 스케줄러 시작");
3335
try {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package akuma.whiplash.domains.alarm.application.scheduler;
2+
3+
import akuma.whiplash.domains.alarm.application.dto.etc.RingingPushInfo;
4+
import akuma.whiplash.domains.alarm.application.dto.etc.RingingPushTargetDto;
5+
import akuma.whiplash.domains.alarm.domain.service.AlarmQueryService;
6+
import akuma.whiplash.infrastructure.firebase.FcmService;
7+
import akuma.whiplash.infrastructure.redis.RedisService;
8+
import java.util.List;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.scheduling.annotation.Scheduled;
12+
import org.springframework.stereotype.Service;
13+
14+
@Slf4j
15+
@Service
16+
@RequiredArgsConstructor
17+
public class AlarmRingingNotificationScheduler {
18+
19+
private final AlarmQueryService alarmQueryService;
20+
private final RedisService redisService;
21+
private final FcmService fcmService;
22+
23+
// 10초 간격으로 실행
24+
@Scheduled(fixedRate = 10000, zone = "Asia/Seoul")
25+
public void sendRingingAlarmNotifications() {
26+
log.info("[AlarmRingingNotificationScheduler.sendRingingAlarmNotifications] 알람 울림 푸시 알림 전송 스케줄러 시작");
27+
try {
28+
List<RingingPushInfo> infos = alarmQueryService.getRingingNotificationTargets();
29+
if (infos.isEmpty()) {
30+
return;
31+
}
32+
33+
List<RingingPushTargetDto> targets = infos.stream()
34+
.flatMap(info -> redisService.getFcmTokens(info.memberId()).stream()
35+
.map(token -> RingingPushTargetDto.builder()
36+
.token(token)
37+
.alarmId(info.alarmId())
38+
.memberId(info.memberId())
39+
.build()))
40+
.toList();
41+
42+
if (targets.isEmpty()) {
43+
return;
44+
}
45+
46+
fcmService.sendRingingNotifications(targets);
47+
} finally {
48+
log.info("[AlarmRingingNotificationScheduler.sendRingingAlarmNotifications] 알람 울림 푸시 알림 전송 스케줄러 종료");
49+
}
50+
}
51+
}

src/main/java/akuma/whiplash/domains/alarm/application/usecase/AlarmUseCase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public void checkinAlarm(Long memberId, Long alarmId, AlarmCheckinRequest reques
4141
alarmCommandService.checkinAlarm(memberId, alarmId, request);
4242
}
4343

44+
public void ringAlarm(Long memberId, Long alarmId) {
45+
alarmCommandService.ringAlarm(memberId, alarmId);
46+
}
47+
4448
public List<AlarmInfoPreviewResponse> getAlarms(Long memberId) {
4549
return alarmQueryService.getAlarms(memberId);
4650
}

src/main/java/akuma/whiplash/domains/alarm/domain/service/AlarmCommandService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ public interface AlarmCommandService {
1515
AlarmOffResultResponse alarmOff(Long memberId, Long alarmId, LocalDateTime clientNow);
1616
void removeAlarm(Long memberId, Long alarmId, String reason);
1717
void checkinAlarm(Long memberId, Long alarmId, AlarmCheckinRequest request);
18+
void ringAlarm(Long memberId, Long alarmId);
1819
void markReminderSent(Set<Long> occurrenceIds);
1920
}

src/main/java/akuma/whiplash/domains/alarm/domain/service/AlarmCommandServiceImpl.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import akuma.whiplash.domains.alarm.persistence.entity.AlarmEntity;
1414
import akuma.whiplash.domains.alarm.persistence.entity.AlarmOccurrenceEntity;
1515
import akuma.whiplash.domains.alarm.persistence.entity.AlarmOffLogEntity;
16+
import akuma.whiplash.domains.alarm.persistence.entity.AlarmRingingLogEntity;
1617
import akuma.whiplash.domains.alarm.persistence.repository.AlarmOccurrenceRepository;
1718
import akuma.whiplash.domains.alarm.persistence.repository.AlarmOffLogRepository;
1819
import akuma.whiplash.domains.alarm.persistence.repository.AlarmRepository;
@@ -245,6 +246,33 @@ public void checkinAlarm(Long memberId, Long alarmId, AlarmCheckinRequest reques
245246
occurrence.checkin(LocalDateTime.now());
246247
}
247248

249+
@Override
250+
public void ringAlarm(Long memberId, Long alarmId) {
251+
AlarmEntity alarm = findAlarmById(alarmId);
252+
validAlarmOwner(memberId, alarm.getMember().getId());
253+
254+
AlarmOccurrenceEntity occurrence = alarmOccurrenceRepository
255+
.findTopByAlarmIdAndDeactivateTypeInOrderByDateDescTimeDesc(
256+
alarmId,
257+
List.of(DeactivateType.NONE)
258+
)
259+
.orElseThrow(() -> ApplicationException.from(ALARM_OCCURRENCE_NOT_FOUND));
260+
261+
LocalDateTime now = LocalDateTime.now();
262+
LocalDateTime scheduledDateTime = LocalDateTime.of(occurrence.getDate(), occurrence.getTime());
263+
if (now.isBefore(scheduledDateTime)) {
264+
throw ApplicationException.from(NOT_ALARM_TIME);
265+
}
266+
267+
int ringIndex = occurrence.ring();
268+
AlarmRingingLogEntity log = AlarmMapper.mapToAlarmRingingLogEntity(
269+
occurrence,
270+
ringIndex,
271+
now
272+
);
273+
alarmRingingLogRepository.save(log);
274+
}
275+
248276
@Override
249277
public void markReminderSent(Set<Long> occurrenceIds) {
250278
if (occurrenceIds == null || occurrenceIds.isEmpty()) return;

src/main/java/akuma/whiplash/domains/alarm/domain/service/AlarmQueryService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package akuma.whiplash.domains.alarm.domain.service;
22

33
import akuma.whiplash.domains.alarm.application.dto.etc.OccurrencePushInfo;
4+
import akuma.whiplash.domains.alarm.application.dto.etc.RingingPushInfo;
45
import akuma.whiplash.domains.alarm.application.dto.response.AlarmInfoPreviewResponse;
56
import akuma.whiplash.domains.alarm.application.dto.response.AlarmRemainingOffCountResponse;
67
import java.time.LocalDateTime;
@@ -10,4 +11,5 @@ public interface AlarmQueryService {
1011
List<AlarmInfoPreviewResponse> getAlarms(Long memberId);
1112
List<OccurrencePushInfo> getPreNotificationTargets(LocalDateTime startInclusive, LocalDateTime endInclusive);
1213
AlarmRemainingOffCountResponse getWeeklyRemainingOffCount(Long memberId);
14+
List<RingingPushInfo> getRingingNotificationTargets();
1315
}

0 commit comments

Comments
 (0)