diff --git a/src/main/java/com/gamzabat/algohub/AlgohubApplication.java b/src/main/java/com/gamzabat/algohub/AlgohubApplication.java index 8eed1696..71b940e5 100644 --- a/src/main/java/com/gamzabat/algohub/AlgohubApplication.java +++ b/src/main/java/com/gamzabat/algohub/AlgohubApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; +@EnableScheduling @SpringBootApplication public class AlgohubApplication { diff --git a/src/main/java/com/gamzabat/algohub/feature/notification/enums/NotificationMessage.java b/src/main/java/com/gamzabat/algohub/feature/notification/enums/NotificationMessage.java new file mode 100644 index 00000000..aff2f95c --- /dev/null +++ b/src/main/java/com/gamzabat/algohub/feature/notification/enums/NotificationMessage.java @@ -0,0 +1,15 @@ +package com.gamzabat.algohub.feature.notification.enums; + +public enum NotificationMessage { + PROBLEM_STARTED("[%s] 문제가 시작되었습니다! 지금 도전해보세요!"); + + private final String message; + + NotificationMessage(String message) { + this.message = message; + } + + public String format(String... args) { + return String.format(message, args); + } +} diff --git a/src/main/java/com/gamzabat/algohub/feature/problem/repository/ProblemRepository.java b/src/main/java/com/gamzabat/algohub/feature/problem/repository/ProblemRepository.java index 8f869b90..c886d35f 100644 --- a/src/main/java/com/gamzabat/algohub/feature/problem/repository/ProblemRepository.java +++ b/src/main/java/com/gamzabat/algohub/feature/problem/repository/ProblemRepository.java @@ -21,6 +21,8 @@ public interface ProblemRepository extends JpaRepository { List findAllByStudyGroupAndStartDateAfter(StudyGroup studyGroup, LocalDate startDate); + List findAllByStartDate(LocalDate startDate); + @Query("SELECT COUNT(p) FROM Problem p WHERE p.studyGroup.id = :groupId") Long countProblemsByGroupId(@Param("groupId") Long groupId); } diff --git a/src/main/java/com/gamzabat/algohub/feature/problem/service/ProblemService.java b/src/main/java/com/gamzabat/algohub/feature/problem/service/ProblemService.java index df397efe..41c728c2 100644 --- a/src/main/java/com/gamzabat/algohub/feature/problem/service/ProblemService.java +++ b/src/main/java/com/gamzabat/algohub/feature/problem/service/ProblemService.java @@ -12,6 +12,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; @@ -22,6 +23,7 @@ import com.gamzabat.algohub.constants.BOJResultConstants; import com.gamzabat.algohub.exception.ProblemValidationException; import com.gamzabat.algohub.exception.StudyGroupValidationException; +import com.gamzabat.algohub.feature.notification.enums.NotificationMessage; import com.gamzabat.algohub.feature.notification.service.NotificationService; import com.gamzabat.algohub.feature.problem.domain.Problem; import com.gamzabat.algohub.feature.problem.dto.CreateProblemRequest; @@ -80,13 +82,9 @@ public void createProblem(User user, CreateProblemRequest request) { .endDate(request.endDate()) .build()); - List members = groupMemberRepository.findAllByStudyGroup(group); - List users = members.stream().map(member -> member.getUser().getEmail()).toList(); - try { - notificationService.sendList(users, "새로운 과제가 등록되었습니다.", group, null); - } catch (Exception e) { - log.info("failed to send notification", e); - } + if (request.startDate().equals(LocalDate.now())) + sendProblemStartedNotification(group, title); + log.info("success to create problem"); } @@ -251,6 +249,15 @@ public List getQueuedProblemList(User user, Long groupId) { return responseList; } + @Scheduled(cron = "0 0 0 * * *") + public void dailyProblemScheduler() { + LocalDate now = LocalDate.now(); + List problems = problemRepository.findAllByStartDate(now); + for (Problem problem : problems) { + sendProblemStartedNotification(problem.getStudyGroup(), problem.getTitle()); + } + } + private Problem getProblem(Long problemId) { return problemRepository.findById(problemId) .orElseThrow(() -> new ProblemValidationException(HttpStatus.NOT_FOUND.value(), "존재하지 않는 문제 입니다.")); @@ -321,4 +328,15 @@ private Integer calculateAccuracy(Integer submitMemberCount, Integer correctCoun private Boolean isInProgress(Problem problem) { return problem.getEndDate() != null && !LocalDate.now().isAfter(problem.getEndDate()); } + + private void sendProblemStartedNotification(StudyGroup group, String title) { + List members = groupMemberRepository.findAllByStudyGroup(group); + List users = members.stream().map(member -> member.getUser().getEmail()).toList(); + try { + String message = NotificationMessage.PROBLEM_STARTED.format(title); + notificationService.sendList(users, message, group, null); + } catch (Exception e) { + log.warn("failed to send notification", e); + } + } } diff --git a/src/test/java/com/gamzabat/algohub/service/ProblemServiceTest.java b/src/test/java/com/gamzabat/algohub/service/ProblemServiceTest.java index e4bfc750..195687fc 100644 --- a/src/test/java/com/gamzabat/algohub/service/ProblemServiceTest.java +++ b/src/test/java/com/gamzabat/algohub/service/ProblemServiceTest.java @@ -125,8 +125,8 @@ void createProblem_Success() { CreateProblemRequest request = CreateProblemRequest.builder() .groupId(10L) .link("https://www.acmicpc.net/problem/1000") - .startDate(LocalDate.now().minusDays(7)) - .endDate(LocalDate.now()) + .startDate(LocalDate.now().plusDays(3)) + .endDate(LocalDate.now().plusDays(10)) .build(); when(groupRepository.findById(10L)).thenReturn(Optional.ofNullable(group)); String apiResult = "[{\"titleKo\":\"A+B\",\"level\":1}]"; @@ -143,9 +143,8 @@ void createProblem_Success() { assertThat(result.getNumber()).isEqualTo(1000); assertThat(result.getTitle()).isEqualTo("A+B"); assertThat(result.getLevel()).isEqualTo(1); - assertThat(result.getStartDate()).isEqualTo(LocalDate.now().minusDays(7)); - assertThat(result.getEndDate()).isEqualTo(LocalDate.now()); - verify(notificationService, times(1)).sendList(any(), any(), any(), any()); + assertThat(result.getStartDate()).isEqualTo(LocalDate.now().plusDays(3)); + assertThat(result.getEndDate()).isEqualTo(LocalDate.now().plusDays(10)); } @Test @@ -155,8 +154,8 @@ void createProblem_SuccessByADMIN() { CreateProblemRequest request = CreateProblemRequest.builder() .groupId(10L) .link("https://www.acmicpc.net/problem/1000") - .startDate(LocalDate.now().minusDays(7)) - .endDate(LocalDate.now()) + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(10)) .build(); when(groupRepository.findById(10L)).thenReturn(Optional.ofNullable(group)); when(groupMemberRepository.findByUserAndStudyGroup(user3, group)).thenReturn(Optional.of(groupMember3)); @@ -173,8 +172,8 @@ void createProblem_SuccessByADMIN() { assertThat(result.getNumber()).isEqualTo(1000); assertThat(result.getTitle()).isEqualTo("A+B"); assertThat(result.getLevel()).isEqualTo(1); - assertThat(result.getStartDate()).isEqualTo(LocalDate.now().minusDays(7)); - assertThat(result.getEndDate()).isEqualTo(LocalDate.now()); + assertThat(result.getStartDate()).isEqualTo(LocalDate.now()); + assertThat(result.getEndDate()).isEqualTo(LocalDate.now().plusDays(10)); verify(notificationService, times(1)).sendList(any(), any(), any(), any()); } @@ -284,8 +283,8 @@ void createProblemSuccess_NotificationFailed() { CreateProblemRequest request = CreateProblemRequest.builder() .groupId(10L) .link("https://www.acmicpc.net/problem/1000") - .startDate(LocalDate.now().minusDays(7)) - .endDate(LocalDate.now()) + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(10)) .build(); when(groupRepository.findById(10L)).thenReturn(Optional.ofNullable(group)); String apiResult = "[{\"titleKo\":\"A+B\",\"level\":1}]"; @@ -729,4 +728,42 @@ void getQueuedProblemListFailed_4() { .hasFieldOrPropertyWithValue("error", "예정 문제를 조회할 권한이 없습니다. : 그룹의 방장과 부방장만 볼 수 있습니다."); } + @Test + @DisplayName("문제 시작 날짜가 오늘일 시 그룹 멤버들에게 알림 전송") + void sendProblemStartedNotification() { + // given + StudyGroup group2 = StudyGroup.builder().name("group2").build(); + User user11 = User.builder().email("email1").build(); + GroupMember groupMember11 = GroupMember.builder().user(user11).studyGroup(group2).build(); + + List group1Members = List.of(groupMember1, groupMember3, groupMember4); + List group2Members = List.of(groupMember11); + + List problems = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + problems.add(Problem.builder() + .studyGroup(group) + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(30)) + .title("started problem") + .build()); + } + for (int i = 5; i < 10; i++) { + problems.add(Problem.builder() + .studyGroup(group2) + .startDate(LocalDate.now()) + .endDate(LocalDate.now().plusDays(30)) + .title("started problem") + .build()); + } + + when(problemRepository.findAllByStartDate(LocalDate.now())).thenReturn(problems); + when(groupMemberRepository.findAllByStudyGroup(group)).thenReturn(group1Members); + when(groupMemberRepository.findAllByStudyGroup(group2)).thenReturn(group2Members); + // when + problemService.dailyProblemScheduler(); + // then + verify(notificationService, times(10)).sendList(anyList(), anyString(), any(StudyGroup.class), eq(null)); + } + } \ No newline at end of file