Skip to content

Commit

Permalink
Merge pull request #20 from TingSHI2015/Add-Alarm-function-backend
Browse files Browse the repository at this point in the history
Add alarm function backend
  • Loading branch information
TingSHI2015 authored Jul 5, 2024
2 parents b75a3b6 + 25c66ca commit 0fe00ba
Show file tree
Hide file tree
Showing 14 changed files with 266 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class BackendApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.tingshi2015.backend.reminder;

import org.springframework.stereotype.Service;

@Service
public class NotificationService {
public void sendNotification(Reminder reminder){
System.out.println("Reminder Alert: " + reminder.name() + " at " + reminder.time() + ", " + reminder.date());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package com.github.tingshi2015.backend.reminder;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@ public Reminder putAReminder(@RequestBody ReminderDTO updateReminder, @PathVaria
// return reminderService.getReminderById(id);
// }

@GetMapping("/upcoming")
public List<Reminder> getUpcomingReminders(){
return reminderService.getUpcomingReminders();

}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package com.github.tingshi2015.backend.reminder;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;

@Repository
public interface ReminderRepository extends MongoRepository<Reminder, String> {

// @Query("{'date': ?0, 'time': {$gte: ?1, $lt: ?2}}")
// List<Reminder> findRemindersByDateAndTime(LocalDate date, LocalTime startTime, LocalTime endTime);


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.tingshi2015.backend.reminder;

import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class ReminderScheduler {
private final ReminderService reminderService;
private final NotificationService notificationService;

@Scheduled(fixedRate = 60000) //check every 60 seconds
public void checkReminders(){
List<Reminder> upcomingReminders = reminderService.getUpcomingReminders();
upcomingReminders.forEach(notificationService::sendNotification);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -13,13 +16,13 @@ public class ReminderService {
private final ReminderRepository reminderRepository;
private final IdService idService;

private ReminderDTO convertToDTO(Reminder reminder){
return new ReminderDTO(reminder.name(), reminder.time(),reminder.date());
}
// private ReminderDTO convertToDTO(Reminder reminder){
// return new ReminderDTO(reminder.name(), reminder.time(),reminder.date());
// }

private Reminder convertToEntity(ReminderDTO reminderDTO){
private Reminder convertToEntity(ReminderDTO reminderDTO) {
String id = idService.randomId();
return new Reminder(id, reminderDTO.name(),reminderDTO.time(),reminderDTO.date());
return new Reminder(id, reminderDTO.name(), reminderDTO.time(), reminderDTO.date());
}

public List<Reminder> getAllReminders() {
Expand All @@ -32,22 +35,46 @@ public Reminder createAReminder(ReminderDTO reminderDTO) {
}

public void deleteAReminder(String id) {
if (!reminderRepository.existsById(id)){
if (!reminderRepository.existsById(id)) {
throw new NoSuchElementException("Reminder with id: " + id + " not found. Can't delete!");
}
reminderRepository.deleteById(id);
}

public Reminder updateAReminder(ReminderDTO updateReminder, String id) {
if(!reminderRepository.existsById(id)){
if (!reminderRepository.existsById(id)) {
throw new NoSuchElementException("Reminder with id: " + id + " not found. Can't update!");
}
Reminder reminderToUpdate = new Reminder(id, updateReminder.name(), updateReminder.time(), updateReminder.date());
return reminderRepository.save(reminderToUpdate);
}


// public Reminder getReminderById(String id) {
// return reminderRepository.findById(id)
// .orElseThrow(()-> new NoSuchElementException("Reminder with id: " + id + " not found. Can't getReminderById"));
// }

//----------inefficient method with "filter"!---------------
public List<Reminder> getUpcomingReminders() {
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now().withSecond(0).withNano(0); //ignore second & Nano-second

return reminderRepository.findAll().stream()
.filter(reminder -> reminder.date() != null && reminder.time() != null)
.filter(reminder -> reminder.date().equals(today) && reminder.time().withSecond(0).withNano(0).equals(now))
.collect(Collectors.toList());
}

//---------efficient method with "@Query"------------------
/*
public List<Reminder> getUpcomingReminders() {
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now().withSecond(0).withNano(0); //ignore second & Nano-second
LocalTime nextMinute = now.plusMinutes(1);
return reminderRepository.findRemindersByDateAndTime(today, now, nextMinute);
}
*/

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
.oauth2Login(o -> o.defaultSuccessUrl(appUrl))
.logout(l -> l
.logoutUrl("/logout")
.logoutUrl("/api/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.clearAuthentication(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.time.LocalDate;
import java.time.LocalTime;


@SpringBootTest
@AutoConfigureMockMvc
class ReminderControllerIntegrationTest {
Expand Down Expand Up @@ -134,4 +135,39 @@ void putAReminder() throws Exception {

}

}
@Test
@DirtiesContext
void getUpcomingReminders() throws Exception {
//GIVEN
LocalTime upcomingReminder1LocalTime = LocalTime.now().withSecond(0).withNano(0);
LocalDate upcomingReminder1LocalDate = LocalDate.now();
LocalDate upcomingReminder2LocalDate = LocalDate.now();
LocalTime upcomingReminder2LocalTime = LocalTime.now().withSecond(0).withNano(0);
Reminder upcomingReminder1 = new Reminder("id1", "Drink Water!", upcomingReminder1LocalTime, upcomingReminder1LocalDate);
Reminder upcomingReminder2 = new Reminder("id2", "Call Mama & Papa!",upcomingReminder2LocalTime, upcomingReminder2LocalDate);
reminderRepository.save(upcomingReminder1);
reminderRepository.save(upcomingReminder2);

//WHEN
mockMvc.perform(MockMvcRequestBuilders.get("/api/reminders/upcoming"))
//THEN
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().json("""
[
{
"id": "id1",
"name": "Drink Water!"
},
{
"id": "id2",
"name": "Call Mama & Papa!"
}
]
"""))
.andExpect(MockMvcResultMatchers.jsonPath("*.time").isNotEmpty())
.andExpect(MockMvcResultMatchers.jsonPath("*.date").isNotEmpty());
}
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.tingshi2015.backend.reminder;

import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.List;

import static org.mockito.Mockito.*;

class ReminderSchedulerUnitTest {
ReminderService reminderService = mock(ReminderService.class);
NotificationService notificationService = mock(NotificationService.class);
ReminderScheduler reminderScheduler = new ReminderScheduler(reminderService, notificationService);


@Test
void checkReminders() {
//GIVEN
Reminder reminder1 = new Reminder("id1", "Drink Water!", LocalTime.now().withSecond(0).withNano(0), LocalDate.now());
Reminder reminder2 = new Reminder("id2", "Call Mama & Papa!",LocalTime.now().withSecond(0).withNano(0), LocalDate.now());

List<Reminder> reminders = List.of(reminder1, reminder2);

when(reminderService.getUpcomingReminders()).thenReturn(reminders);
//doNothing().when(notificationService).sendNotification(reminder1); //---can be omitted!
//doNothing().when(notificationService).sendNotification(reminder2); //---can be omitted!

//WHEN
reminderScheduler.checkReminders();

//THEN
verify(notificationService, times(1)).sendNotification(reminder1);
verify(notificationService, times(1)).sendNotification(reminder2);


}

}




Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.NoSuchElementException;


import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -117,4 +118,42 @@ void updateAReminder_withInvalidId_shouldThrowException(){
assertEquals("Reminder with id: " + id + " not found. Can't update!", exception.getMessage());
}


@Test
void getUpcomingReminders_whenTimeAndDateAreNotNull(){
//GIVEN
Reminder upcomingReminder1 = new Reminder("id1", "Drink Water!", LocalTime.now().withSecond(0).withNano(0), LocalDate.now());
Reminder upcomingReminder2 = new Reminder("id2", "Call Mama & Papa!",LocalTime.now().withSecond(0).withNano(0), LocalDate.now());
List<Reminder> upcomingReminders = List.of(upcomingReminder1, upcomingReminder2);

when(reminderRepository.findAll()).thenReturn(upcomingReminders);

//WHEN
List<Reminder> actual = reminderService.getUpcomingReminders();

//THEN
verify(reminderRepository).findAll();
assertEquals(upcomingReminders, actual);
}

@Test
void getUpcomingReminders_whenTimeOrDateAreNull(){
//GIVEN
Reminder reminderWithoutNull = new Reminder("id1", "Buy milk!", LocalTime.now().withSecond(0).withNano(0), LocalDate.now());
Reminder reminderWithNullDate = new Reminder("id2", "Drink Water!", LocalTime.now().withSecond(0).withNano(0), null);
Reminder reminderWithNullTime = new Reminder("id3", "Call Mama & Papa!",null, LocalDate.now());
Reminder reminderWithNullDateAndTime = new Reminder("id4", "Call Mama & Papa!",null, null);
List<Reminder> reminders = List.of(reminderWithoutNull, reminderWithNullDate, reminderWithNullTime, reminderWithNullDateAndTime);

when(reminderRepository.findAll()).thenReturn(reminders);

//WHEN
List<Reminder> actual = reminderService.getUpcomingReminders();

//THEN
verify(reminderRepository).findAll();
List<Reminder> expected = List.of(reminderWithoutNull);
assertEquals(expected, actual);
}

}
2 changes: 1 addition & 1 deletion frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Header(props: Readonly<HeaderProps>){
const navigate = useNavigate();

const logout = () => {
axios.get("/logout")
axios.get("/api/logout")
.then(()=>{navigate("/login")})
.catch(error => console.error("Logout failed",error))
}
Expand Down
60 changes: 60 additions & 0 deletions frontend/src/components/UpcomingReminderGallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {useEffect, useState} from "react";
import {Reminder} from "../types/Reminder.ts";
import axios from "axios";

export default function UpcomingReminderGallery() {
const [upcomingReminders, setUpcomingReminders] = useState<Reminder[]>([]);
const [showUpcomingReminder, setShowUpcomingReminder] = useState<boolean>(false);

useEffect(() => {

getUpcomingReminders();

const interval = setInterval(() => {
getUpcomingReminders();
}, 60000);

return () => clearInterval(interval); //Cleanup the interval on unmount

}, []);


const getUpcomingReminders = () => {
axios.get("/api/reminders/upcoming")
.then(response => {
if(response.data.length > 0) {
//setUpcomingReminders(response.data);
setUpcomingReminders(preReminders => [...preReminders, ...response.data]);
setShowUpcomingReminder(true);
}
})
.catch(error => console.error("Error getting upcoming reminders", error))
.finally(() => {console.log("getUpcomingReminder_successful")})
}

const handleClose = () => {
setShowUpcomingReminder(false);
setUpcomingReminders([]);
}

return(
<div>
{showUpcomingReminder && (
<div>
<h3>Upcoming Reminders</h3>
<ul>
{upcomingReminders.map(reminder => (
<li key={reminder.id}>
{reminder.name} at {reminder.date} {reminder.time}
</li>
))}
</ul>
<button onClick={handleClose}>Close</button>
</div>
)
}
</div>

)

}
Loading

0 comments on commit 0fe00ba

Please sign in to comment.