From 419bf777ca7a4a56bfc37e0d463936eff1b6651b Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 12 Dec 2025 11:08:24 -0300 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20Feito=20o=20servi=C3=A7o=20para=20d?= =?UTF-8?q?esabilitar=20todos=20os=20futuros=20envios=20de=20e-mails=20e?= =?UTF-8?q?=20lembretes=20para=20o=20reminder.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++ src/docs/asciidoc/index-en-US.adoc | 12 ++-- src/docs/asciidoc/index-pt-BR.adoc | 12 ++-- .../exception/PastDueDateException.java | 7 ++ .../reminder/service/ReminderService.java | 17 +++++ .../schedule/service/JobService.java | 47 +++++++++++-- .../reminder/service/ReminderServiceTest.java | 69 +++++++++++++++++++ 7 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java diff --git a/pom.xml b/pom.xml index 9eac736..8839364 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,12 @@ test + + org.slf4j + slf4j-api + 1.7.36 + + org.quartz-scheduler quartz diff --git a/src/docs/asciidoc/index-en-US.adoc b/src/docs/asciidoc/index-en-US.adoc index f8c8549..67413ba 100644 --- a/src/docs/asciidoc/index-en-US.adoc +++ b/src/docs/asciidoc/index-en-US.adoc @@ -82,12 +82,6 @@ A `POST` request will create a new reminder. operation::create-reminder[snippets='http-request,curl-request,http-response,request-body'] -==== Create a reminder (Internal failure) - -A `POST` request to create a reminder may result in an internal failure. - -operation::create-reminder-scheduler-error[snippets='http-response'] - ==== Create a reminder (past date) operation::create-reminder-past-date[snippets='http-response'] @@ -118,6 +112,12 @@ operation::update-reminder-not-found[snippets='http-response'] operation::update-reminder-past-date[snippets='http-response'] +==== Disable a reminder (Internal failure) + +A `PATCH` request to disable a reminder. + +operation::disable-reminder-error[snippets='http-response'] + [[resources_reminders_delete]] ==== Delete a reminder diff --git a/src/docs/asciidoc/index-pt-BR.adoc b/src/docs/asciidoc/index-pt-BR.adoc index f4ffc43..99307c7 100644 --- a/src/docs/asciidoc/index-pt-BR.adoc +++ b/src/docs/asciidoc/index-pt-BR.adoc @@ -82,12 +82,6 @@ Uma requisição `POST` criará um novo lembrete. operation::create-reminder[snippets='http-request,curl-request,http-response,request-body'] -==== Criar um lembrete (Falha interna) - -Uma requisição `POST` para criar um lembrete pode resultar em falha interna. - -operation::create-reminder-scheduler-error[snippets='http-response'] - ==== Criar um lembrete (data no passado) operation::create-reminder-past-date[snippets='http-response'] @@ -118,6 +112,12 @@ operation::update-reminder-not-found[snippets='http-response'] operation::update-reminder-past-date[snippets='http-response'] +==== Desabilitar um lembrete + +Uma requisição `PATCH` para desabilitar um lembrete. + +operation::disable-reminder-error[snippets='http-response'] + [[resources_reminders_delete]] ==== Deletar um lembrete diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java new file mode 100644 index 0000000..0f40468 --- /dev/null +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java @@ -0,0 +1,7 @@ +package br.com.springnoobs.reminderapi.reminder.exception; + +public class PastDueDateException extends RuntimeException { + public PastDueDateException(String message) { + super(message); + } +} diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java index d9c7e58..32eb38a 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java @@ -5,6 +5,7 @@ import br.com.springnoobs.reminderapi.reminder.dto.response.ReminderResponseDTO; import br.com.springnoobs.reminderapi.reminder.entity.Reminder; import br.com.springnoobs.reminderapi.reminder.exception.NotFoundException; +import br.com.springnoobs.reminderapi.reminder.exception.PastDueDateException; import br.com.springnoobs.reminderapi.reminder.exception.ReminderSchedulerException; import br.com.springnoobs.reminderapi.reminder.mapper.ReminderMapper; import br.com.springnoobs.reminderapi.reminder.repository.ReminderRepository; @@ -35,6 +36,10 @@ public ReminderService(ReminderRepository repository, UserService userService, J @Transactional public ReminderResponseDTO create(CreateReminderRequestDTO dto) { try { + if (dto.dueDate().isBefore(Instant.now())) { + throw new PastDueDateException("DueDate should be a date in the future!"); + } + Reminder reminder = new Reminder(); BeanUtils.copyProperties(dto, reminder); @@ -72,6 +77,10 @@ public ReminderResponseDTO update(Long id, UpdateReminderRequestDTO dto) { .findById(id) .orElseThrow(() -> new NotFoundException("Reminder with ID: " + id + " not found")); + if (dto.dueDate().isBefore(Instant.now())) { + throw new PastDueDateException("DueDate should be a date in the future!"); + } + BeanUtils.copyProperties(dto, reminder); jobService.updateReminderSchedules(reminder); @@ -106,4 +115,12 @@ public void registerReminderExecution(Reminder reminder) { repository.save(reminder); } + + public void disableReminderNotifications(Long id) throws SchedulerException { + Reminder reminder = repository + .findById(id) + .orElseThrow(() -> new NotFoundException("Reminder with ID: " + id + " not found")); + + jobService.unscheduleJobTriggers(reminder.getId()); + } } diff --git a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java index 0602854..c6b1c4a 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java @@ -7,19 +7,23 @@ import java.time.Duration; import java.time.Instant; import java.util.Date; +import java.util.Optional; import org.quartz.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class JobService { - private static final String TRIGGER_NAME = "reminder-trigger"; - private static final String JOB_NAME = "reminder-job"; - private static final String JOB_GROUP = "reminders"; + private static final String REMINDER_TRIGGER_NAME = "reminder-trigger"; + private static final String REMINDER_JOB_NAME = "reminder-job"; + private static final String REMINDER_JOB_GROUP = "reminders"; private static final String RETRY_EMAIL_JOB_NAME = "retry-email-job"; private static final String RETRY_EMAIL_TRIGGER_NAME = "retry-email-trigger"; private static final String RETRY_EMAIL_GROUP = "email-retry"; + private static final Logger logger = LoggerFactory.getLogger(JobService.class); private final Scheduler scheduler; @@ -35,7 +39,7 @@ public void initializeRetryEmailJob() throws SchedulerException { public void scheduleJob(Reminder reminder) throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob(ReminderJob.class) - .withIdentity(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) + .withIdentity(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) .usingJobData("reminder-id", reminder.getId()) .storeDurably() .build(); @@ -52,7 +56,7 @@ public void updateReminderSchedules(Reminder reminder) throws SchedulerException } public void deleteReminderSchedules(Long reminderId) throws SchedulerException { - JobKey jobKey = new JobKey(JOB_NAME + "-" + reminderId, JOB_GROUP); + JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); if (scheduler.checkExists(jobKey)) { scheduler.deleteJob(jobKey); @@ -72,8 +76,8 @@ private void scheduleTrigger(Reminder reminder, String suffix, Instant fireTime) } Trigger trigger = TriggerBuilder.newTrigger() - .withIdentity(TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, JOB_GROUP) - .forJob(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) + .withIdentity(REMINDER_TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, REMINDER_JOB_GROUP) + .forJob(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) .startAt(Date.from(fireTime)) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow()) .build(); @@ -102,4 +106,33 @@ public void scheduleRetryEmailJob() throws SchedulerException { scheduler.scheduleJob(trigger); } + + private Optional findJobKey(Long reminderId) { + try { + JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); + + if (scheduler.checkExists(jobKey)) { + return Optional.of(jobKey); + } + } catch (SchedulerException e) { + return Optional.empty(); + } + return Optional.empty(); + } + + public void unscheduleJobTriggers(Long reminderId) throws SchedulerException { + Optional optionalJobKey = findJobKey(reminderId); + + if (optionalJobKey.isEmpty()) { + return; + } + JobKey jobKey = optionalJobKey.get(); + scheduler.getTriggersOfJob(jobKey).forEach(trigger -> { + try { + scheduler.unscheduleJob(trigger.getKey()); + } catch (SchedulerException e) { + logger.error("Error unscheduling job: {}", e.getMessage()); + } + }); + } } diff --git a/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java b/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java index 5e3a96e..f60e27f 100644 --- a/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java +++ b/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java @@ -9,6 +9,7 @@ import br.com.springnoobs.reminderapi.reminder.dto.response.ReminderResponseDTO; import br.com.springnoobs.reminderapi.reminder.entity.Reminder; import br.com.springnoobs.reminderapi.reminder.exception.NotFoundException; +import br.com.springnoobs.reminderapi.reminder.exception.PastDueDateException; import br.com.springnoobs.reminderapi.reminder.exception.ReminderSchedulerException; import br.com.springnoobs.reminderapi.reminder.repository.ReminderRepository; import br.com.springnoobs.reminderapi.schedule.service.JobService; @@ -144,6 +145,20 @@ void shouldCreateReminderWhenRequestIsValid() throws SchedulerException { verify(repository).save(any()); } + @Test + void shouldThrowDueDateExceptionWhenTryCreateReminderWithPastDueDate() { + // Arrange + Instant dueDate = Instant.now().minusSeconds(60); + + CreateUserRequestDTO createUserRequestDTO = new CreateUserRequestDTO( + "First Name", "Last Name", new ContactRequestDTO("email@test.com", "123456789")); + + CreateReminderRequestDTO request = new CreateReminderRequestDTO("Create", dueDate, createUserRequestDTO); + + // Act And Assert + assertThrows(PastDueDateException.class, () -> service.create(request)); + } + @Test void shouldThrowNotFoundExceptionWhenCreateReminderWithNonExistentUser() { // Arrange @@ -222,6 +237,27 @@ void shouldThrowNotFoundExceptionWhenTryUpdateReminderWithInvalidId() { assertThrows(NotFoundException.class, () -> service.update(1L, request)); } + @Test + void shouldThrowPastDueDateExceptionWhenTryUpdateReminderWithPastDueDate() { + // Arrange + long reminderId = 1L; + Instant dueDate = Instant.now().minusSeconds(60); + UpdateReminderRequestDTO request = new UpdateReminderRequestDTO("Update", dueDate); + + Reminder existingReminder = new Reminder(); + existingReminder.setTitle("Any Title"); + + when(repository.findById(reminderId)).thenReturn(Optional.of(existingReminder)); + + // Act & Assert + assertThrows(PastDueDateException.class, () -> service.update(reminderId, request)); + + // Verify that no further actions were taken + verify(repository).findById(reminderId); + verifyNoMoreInteractions(repository); + verifyNoInteractions(jobService); + } + @Test void shouldThrowReminderSchedulerExceptionWhenUpdateReminderFailsToSchedule() throws SchedulerException { // Arrange @@ -296,4 +332,37 @@ void shouldRegisterCompleteExecutionWhenReminderIsValid() { assertNotNull(reminder.getExecutedAt()); verify(repository).save(reminder); } + + @Test + void shouldDisableReminderNotificationsWhenReminderIdIsValid() throws SchedulerException { + // Arrange + long reminderId = 1L; + Reminder reminder = new Reminder(); + reminder.setId(reminderId); + + when(repository.findById(reminderId)).thenReturn(Optional.of(reminder)); + doNothing().when(jobService).unscheduleJobTriggers(reminderId); + + // Act + service.disableReminderNotifications(reminderId); + + // Assert + verify(repository).findById(reminderId); + verify(jobService).unscheduleJobTriggers(reminderId); + verifyNoMoreInteractions(repository, jobService); + } + + @Test + void shouldThrowNotFoundExceptionWhenDisablingNotificationsForInvalidReminderId() { + // Arrange + long invalidReminderId = 99L; + when(repository.findById(invalidReminderId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.disableReminderNotifications(invalidReminderId)); + + // Verify + verify(repository).findById(invalidReminderId); + verifyNoInteractions(jobService); + } } From 18065d75d3ededc7e015ce8c9af2bf535eda7a0b Mon Sep 17 00:00:00 2001 From: Joe Date: Fri, 12 Dec 2025 12:54:07 -0300 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20Feito=20ajuste=20no=20arquivo=20pom.?= =?UTF-8?q?xml,=20onde=20havia=20uma=20depend=C3=AAncia=20impedindo=20os?= =?UTF-8?q?=20testes=20da=20classe=20ReminderControllerTest.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ------ .../reminder/controller/ReminderExceptionHandler.java | 4 ---- 2 files changed, 10 deletions(-) diff --git a/pom.xml b/pom.xml index 8839364..9eac736 100644 --- a/pom.xml +++ b/pom.xml @@ -85,12 +85,6 @@ test - - org.slf4j - slf4j-api - 1.7.36 - - org.quartz-scheduler quartz diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java index 8867101..bbf783d 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java @@ -35,10 +35,6 @@ public ProblemDetail handleReminderSchedulerException(ServletWebRequest request, return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); } - /* - * Handles validation errors from annotations like @NotNull, @NotBlank, @Future, etc. Adding the error messages to the response body. - * Overridden because the default implementation does not include error messages in the response body. - */ @Override protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { From 9f64c9df932f6696812df7f96274ad1557ed32a1 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 13 Dec 2025 16:07:01 -0300 Subject: [PATCH 3/6] =?UTF-8?q?Revert=20"fix:=20Feito=20ajuste=20no=20arqu?= =?UTF-8?q?ivo=20pom.xml,=20onde=20havia=20uma=20depend=C3=AAncia=20impedi?= =?UTF-8?q?ndo=20os=20testes=20da=20classe=20ReminderControllerTest."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 18065d75d3ededc7e015ce8c9af2bf535eda7a0b. --- pom.xml | 6 ++++++ .../reminder/controller/ReminderExceptionHandler.java | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 9eac736..8839364 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,12 @@ test + + org.slf4j + slf4j-api + 1.7.36 + + org.quartz-scheduler quartz diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java index bbf783d..8867101 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/controller/ReminderExceptionHandler.java @@ -35,6 +35,10 @@ public ProblemDetail handleReminderSchedulerException(ServletWebRequest request, return ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()); } + /* + * Handles validation errors from annotations like @NotNull, @NotBlank, @Future, etc. Adding the error messages to the response body. + * Overridden because the default implementation does not include error messages in the response body. + */ @Override protected ResponseEntity handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) { From 6f8869188c37ae7a5b98fbb0bc887f9c821b611d Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 13 Dec 2025 16:07:04 -0300 Subject: [PATCH 4/6] =?UTF-8?q?Revert=20"feat:=20Feito=20o=20servi=C3=A7o?= =?UTF-8?q?=20para=20desabilitar=20todos=20os=20futuros=20envios=20de=20e-?= =?UTF-8?q?mails=20e=20lembretes=20para=20o=20reminder."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 419bf777ca7a4a56bfc37e0d463936eff1b6651b. --- pom.xml | 6 -- src/docs/asciidoc/index-en-US.adoc | 12 ++-- src/docs/asciidoc/index-pt-BR.adoc | 12 ++-- .../exception/PastDueDateException.java | 7 -- .../reminder/service/ReminderService.java | 17 ----- .../schedule/service/JobService.java | 47 ++----------- .../reminder/service/ReminderServiceTest.java | 69 ------------------- 7 files changed, 19 insertions(+), 151 deletions(-) delete mode 100644 src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java diff --git a/pom.xml b/pom.xml index 8839364..9eac736 100644 --- a/pom.xml +++ b/pom.xml @@ -85,12 +85,6 @@ test - - org.slf4j - slf4j-api - 1.7.36 - - org.quartz-scheduler quartz diff --git a/src/docs/asciidoc/index-en-US.adoc b/src/docs/asciidoc/index-en-US.adoc index 67413ba..f8c8549 100644 --- a/src/docs/asciidoc/index-en-US.adoc +++ b/src/docs/asciidoc/index-en-US.adoc @@ -82,6 +82,12 @@ A `POST` request will create a new reminder. operation::create-reminder[snippets='http-request,curl-request,http-response,request-body'] +==== Create a reminder (Internal failure) + +A `POST` request to create a reminder may result in an internal failure. + +operation::create-reminder-scheduler-error[snippets='http-response'] + ==== Create a reminder (past date) operation::create-reminder-past-date[snippets='http-response'] @@ -112,12 +118,6 @@ operation::update-reminder-not-found[snippets='http-response'] operation::update-reminder-past-date[snippets='http-response'] -==== Disable a reminder (Internal failure) - -A `PATCH` request to disable a reminder. - -operation::disable-reminder-error[snippets='http-response'] - [[resources_reminders_delete]] ==== Delete a reminder diff --git a/src/docs/asciidoc/index-pt-BR.adoc b/src/docs/asciidoc/index-pt-BR.adoc index 99307c7..f4ffc43 100644 --- a/src/docs/asciidoc/index-pt-BR.adoc +++ b/src/docs/asciidoc/index-pt-BR.adoc @@ -82,6 +82,12 @@ Uma requisição `POST` criará um novo lembrete. operation::create-reminder[snippets='http-request,curl-request,http-response,request-body'] +==== Criar um lembrete (Falha interna) + +Uma requisição `POST` para criar um lembrete pode resultar em falha interna. + +operation::create-reminder-scheduler-error[snippets='http-response'] + ==== Criar um lembrete (data no passado) operation::create-reminder-past-date[snippets='http-response'] @@ -112,12 +118,6 @@ operation::update-reminder-not-found[snippets='http-response'] operation::update-reminder-past-date[snippets='http-response'] -==== Desabilitar um lembrete - -Uma requisição `PATCH` para desabilitar um lembrete. - -operation::disable-reminder-error[snippets='http-response'] - [[resources_reminders_delete]] ==== Deletar um lembrete diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java deleted file mode 100644 index 0f40468..0000000 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/exception/PastDueDateException.java +++ /dev/null @@ -1,7 +0,0 @@ -package br.com.springnoobs.reminderapi.reminder.exception; - -public class PastDueDateException extends RuntimeException { - public PastDueDateException(String message) { - super(message); - } -} diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java index 32eb38a..d9c7e58 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java @@ -5,7 +5,6 @@ import br.com.springnoobs.reminderapi.reminder.dto.response.ReminderResponseDTO; import br.com.springnoobs.reminderapi.reminder.entity.Reminder; import br.com.springnoobs.reminderapi.reminder.exception.NotFoundException; -import br.com.springnoobs.reminderapi.reminder.exception.PastDueDateException; import br.com.springnoobs.reminderapi.reminder.exception.ReminderSchedulerException; import br.com.springnoobs.reminderapi.reminder.mapper.ReminderMapper; import br.com.springnoobs.reminderapi.reminder.repository.ReminderRepository; @@ -36,10 +35,6 @@ public ReminderService(ReminderRepository repository, UserService userService, J @Transactional public ReminderResponseDTO create(CreateReminderRequestDTO dto) { try { - if (dto.dueDate().isBefore(Instant.now())) { - throw new PastDueDateException("DueDate should be a date in the future!"); - } - Reminder reminder = new Reminder(); BeanUtils.copyProperties(dto, reminder); @@ -77,10 +72,6 @@ public ReminderResponseDTO update(Long id, UpdateReminderRequestDTO dto) { .findById(id) .orElseThrow(() -> new NotFoundException("Reminder with ID: " + id + " not found")); - if (dto.dueDate().isBefore(Instant.now())) { - throw new PastDueDateException("DueDate should be a date in the future!"); - } - BeanUtils.copyProperties(dto, reminder); jobService.updateReminderSchedules(reminder); @@ -115,12 +106,4 @@ public void registerReminderExecution(Reminder reminder) { repository.save(reminder); } - - public void disableReminderNotifications(Long id) throws SchedulerException { - Reminder reminder = repository - .findById(id) - .orElseThrow(() -> new NotFoundException("Reminder with ID: " + id + " not found")); - - jobService.unscheduleJobTriggers(reminder.getId()); - } } diff --git a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java index c6b1c4a..0602854 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java @@ -7,23 +7,19 @@ import java.time.Duration; import java.time.Instant; import java.util.Date; -import java.util.Optional; import org.quartz.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class JobService { - private static final String REMINDER_TRIGGER_NAME = "reminder-trigger"; - private static final String REMINDER_JOB_NAME = "reminder-job"; - private static final String REMINDER_JOB_GROUP = "reminders"; + private static final String TRIGGER_NAME = "reminder-trigger"; + private static final String JOB_NAME = "reminder-job"; + private static final String JOB_GROUP = "reminders"; private static final String RETRY_EMAIL_JOB_NAME = "retry-email-job"; private static final String RETRY_EMAIL_TRIGGER_NAME = "retry-email-trigger"; private static final String RETRY_EMAIL_GROUP = "email-retry"; - private static final Logger logger = LoggerFactory.getLogger(JobService.class); private final Scheduler scheduler; @@ -39,7 +35,7 @@ public void initializeRetryEmailJob() throws SchedulerException { public void scheduleJob(Reminder reminder) throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob(ReminderJob.class) - .withIdentity(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) + .withIdentity(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) .usingJobData("reminder-id", reminder.getId()) .storeDurably() .build(); @@ -56,7 +52,7 @@ public void updateReminderSchedules(Reminder reminder) throws SchedulerException } public void deleteReminderSchedules(Long reminderId) throws SchedulerException { - JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); + JobKey jobKey = new JobKey(JOB_NAME + "-" + reminderId, JOB_GROUP); if (scheduler.checkExists(jobKey)) { scheduler.deleteJob(jobKey); @@ -76,8 +72,8 @@ private void scheduleTrigger(Reminder reminder, String suffix, Instant fireTime) } Trigger trigger = TriggerBuilder.newTrigger() - .withIdentity(REMINDER_TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, REMINDER_JOB_GROUP) - .forJob(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) + .withIdentity(TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, JOB_GROUP) + .forJob(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) .startAt(Date.from(fireTime)) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow()) .build(); @@ -106,33 +102,4 @@ public void scheduleRetryEmailJob() throws SchedulerException { scheduler.scheduleJob(trigger); } - - private Optional findJobKey(Long reminderId) { - try { - JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); - - if (scheduler.checkExists(jobKey)) { - return Optional.of(jobKey); - } - } catch (SchedulerException e) { - return Optional.empty(); - } - return Optional.empty(); - } - - public void unscheduleJobTriggers(Long reminderId) throws SchedulerException { - Optional optionalJobKey = findJobKey(reminderId); - - if (optionalJobKey.isEmpty()) { - return; - } - JobKey jobKey = optionalJobKey.get(); - scheduler.getTriggersOfJob(jobKey).forEach(trigger -> { - try { - scheduler.unscheduleJob(trigger.getKey()); - } catch (SchedulerException e) { - logger.error("Error unscheduling job: {}", e.getMessage()); - } - }); - } } diff --git a/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java b/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java index f60e27f..5e3a96e 100644 --- a/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java +++ b/src/test/java/br/com/springnoobs/reminderapi/reminder/service/ReminderServiceTest.java @@ -9,7 +9,6 @@ import br.com.springnoobs.reminderapi.reminder.dto.response.ReminderResponseDTO; import br.com.springnoobs.reminderapi.reminder.entity.Reminder; import br.com.springnoobs.reminderapi.reminder.exception.NotFoundException; -import br.com.springnoobs.reminderapi.reminder.exception.PastDueDateException; import br.com.springnoobs.reminderapi.reminder.exception.ReminderSchedulerException; import br.com.springnoobs.reminderapi.reminder.repository.ReminderRepository; import br.com.springnoobs.reminderapi.schedule.service.JobService; @@ -145,20 +144,6 @@ void shouldCreateReminderWhenRequestIsValid() throws SchedulerException { verify(repository).save(any()); } - @Test - void shouldThrowDueDateExceptionWhenTryCreateReminderWithPastDueDate() { - // Arrange - Instant dueDate = Instant.now().minusSeconds(60); - - CreateUserRequestDTO createUserRequestDTO = new CreateUserRequestDTO( - "First Name", "Last Name", new ContactRequestDTO("email@test.com", "123456789")); - - CreateReminderRequestDTO request = new CreateReminderRequestDTO("Create", dueDate, createUserRequestDTO); - - // Act And Assert - assertThrows(PastDueDateException.class, () -> service.create(request)); - } - @Test void shouldThrowNotFoundExceptionWhenCreateReminderWithNonExistentUser() { // Arrange @@ -237,27 +222,6 @@ void shouldThrowNotFoundExceptionWhenTryUpdateReminderWithInvalidId() { assertThrows(NotFoundException.class, () -> service.update(1L, request)); } - @Test - void shouldThrowPastDueDateExceptionWhenTryUpdateReminderWithPastDueDate() { - // Arrange - long reminderId = 1L; - Instant dueDate = Instant.now().minusSeconds(60); - UpdateReminderRequestDTO request = new UpdateReminderRequestDTO("Update", dueDate); - - Reminder existingReminder = new Reminder(); - existingReminder.setTitle("Any Title"); - - when(repository.findById(reminderId)).thenReturn(Optional.of(existingReminder)); - - // Act & Assert - assertThrows(PastDueDateException.class, () -> service.update(reminderId, request)); - - // Verify that no further actions were taken - verify(repository).findById(reminderId); - verifyNoMoreInteractions(repository); - verifyNoInteractions(jobService); - } - @Test void shouldThrowReminderSchedulerExceptionWhenUpdateReminderFailsToSchedule() throws SchedulerException { // Arrange @@ -332,37 +296,4 @@ void shouldRegisterCompleteExecutionWhenReminderIsValid() { assertNotNull(reminder.getExecutedAt()); verify(repository).save(reminder); } - - @Test - void shouldDisableReminderNotificationsWhenReminderIdIsValid() throws SchedulerException { - // Arrange - long reminderId = 1L; - Reminder reminder = new Reminder(); - reminder.setId(reminderId); - - when(repository.findById(reminderId)).thenReturn(Optional.of(reminder)); - doNothing().when(jobService).unscheduleJobTriggers(reminderId); - - // Act - service.disableReminderNotifications(reminderId); - - // Assert - verify(repository).findById(reminderId); - verify(jobService).unscheduleJobTriggers(reminderId); - verifyNoMoreInteractions(repository, jobService); - } - - @Test - void shouldThrowNotFoundExceptionWhenDisablingNotificationsForInvalidReminderId() { - // Arrange - long invalidReminderId = 99L; - when(repository.findById(invalidReminderId)).thenReturn(Optional.empty()); - - // Act & Assert - assertThrows(NotFoundException.class, () -> service.disableReminderNotifications(invalidReminderId)); - - // Verify - verify(repository).findById(invalidReminderId); - verifyNoInteractions(jobService); - } } From 299034f69a904ab21ead70eea9327541ed80ac94 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 13 Dec 2025 16:15:38 -0300 Subject: [PATCH 5/6] fix: feito ajustes nas classes JobService e ReminderService. --- .../reminder/service/ReminderService.java | 8 ++++ .../schedule/service/JobService.java | 47 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java index d9c7e58..ff4d94c 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/reminder/service/ReminderService.java @@ -106,4 +106,12 @@ public void registerReminderExecution(Reminder reminder) { repository.save(reminder); } + + public void disableReminderNotifications(Long id) throws SchedulerException { + Reminder reminder = repository + .findById(id) + .orElseThrow(() -> new NotFoundException("Reminder with ID: " + id + " not found")); + + jobService.unscheduleReminderJobTriggers(reminder.getId()); + } } diff --git a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java index 0602854..d9cff6e 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java +++ b/src/main/java/br/com/springnoobs/reminderapi/schedule/service/JobService.java @@ -7,19 +7,23 @@ import java.time.Duration; import java.time.Instant; import java.util.Date; +import java.util.Optional; import org.quartz.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class JobService { - private static final String TRIGGER_NAME = "reminder-trigger"; - private static final String JOB_NAME = "reminder-job"; - private static final String JOB_GROUP = "reminders"; + private static final String REMINDER_TRIGGER_NAME = "reminder-trigger"; + private static final String REMINDER_JOB_NAME = "reminder-job"; + private static final String REMINDER_JOB_GROUP = "reminders"; private static final String RETRY_EMAIL_JOB_NAME = "retry-email-job"; private static final String RETRY_EMAIL_TRIGGER_NAME = "retry-email-trigger"; private static final String RETRY_EMAIL_GROUP = "email-retry"; + private static final Logger logger = LoggerFactory.getLogger(JobService.class); private final Scheduler scheduler; @@ -35,7 +39,7 @@ public void initializeRetryEmailJob() throws SchedulerException { public void scheduleJob(Reminder reminder) throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob(ReminderJob.class) - .withIdentity(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) + .withIdentity(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) .usingJobData("reminder-id", reminder.getId()) .storeDurably() .build(); @@ -52,7 +56,7 @@ public void updateReminderSchedules(Reminder reminder) throws SchedulerException } public void deleteReminderSchedules(Long reminderId) throws SchedulerException { - JobKey jobKey = new JobKey(JOB_NAME + "-" + reminderId, JOB_GROUP); + JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); if (scheduler.checkExists(jobKey)) { scheduler.deleteJob(jobKey); @@ -72,8 +76,8 @@ private void scheduleTrigger(Reminder reminder, String suffix, Instant fireTime) } Trigger trigger = TriggerBuilder.newTrigger() - .withIdentity(TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, JOB_GROUP) - .forJob(JOB_NAME + "-" + reminder.getId(), JOB_GROUP) + .withIdentity(REMINDER_TRIGGER_NAME + "-" + reminder.getId() + "-" + suffix, REMINDER_JOB_GROUP) + .forJob(REMINDER_JOB_NAME + "-" + reminder.getId(), REMINDER_JOB_GROUP) .startAt(Date.from(fireTime)) .withSchedule(SimpleScheduleBuilder.simpleSchedule().withMisfireHandlingInstructionFireNow()) .build(); @@ -102,4 +106,33 @@ public void scheduleRetryEmailJob() throws SchedulerException { scheduler.scheduleJob(trigger); } + + private Optional findReminderJobKey(Long reminderId) { + try { + JobKey jobKey = new JobKey(REMINDER_JOB_NAME + "-" + reminderId, REMINDER_JOB_GROUP); + + if (scheduler.checkExists(jobKey)) { + return Optional.of(jobKey); + } + } catch (SchedulerException e) { + return Optional.empty(); + } + return Optional.empty(); + } + + public void unscheduleReminderJobTriggers(Long reminderId) throws SchedulerException { + Optional optionalJobKey = findReminderJobKey(reminderId); + + if (optionalJobKey.isEmpty()) { + return; + } + JobKey jobKey = optionalJobKey.get(); + scheduler.getTriggersOfJob(jobKey).forEach(trigger -> { + try { + scheduler.unscheduleJob(trigger.getKey()); + } catch (SchedulerException e) { + logger.error("Error unscheduling job: {}", e.getMessage()); + } + }); + } } From dac15ef74e284696d7b32b9afad7aead59987904 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 13 Dec 2025 19:16:51 +0000 Subject: [PATCH 6/6] =?UTF-8?q?Aplica=20formata=C3=A7=C3=A3o=20do=20spotle?= =?UTF-8?q?ss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/br/com/springnoobs/reminderapi/user/entity/User.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/br/com/springnoobs/reminderapi/user/entity/User.java b/src/main/java/br/com/springnoobs/reminderapi/user/entity/User.java index e197cd1..db91604 100644 --- a/src/main/java/br/com/springnoobs/reminderapi/user/entity/User.java +++ b/src/main/java/br/com/springnoobs/reminderapi/user/entity/User.java @@ -2,7 +2,6 @@ import br.com.springnoobs.reminderapi.reminder.entity.Reminder; import jakarta.persistence.*; - import java.util.ArrayList; import java.util.List;