diff --git a/backend/src/main/java/com/openisle/controller/PointHistoryController.java b/backend/src/main/java/com/openisle/controller/PointHistoryController.java index a547d309a..1a4235e3a 100644 --- a/backend/src/main/java/com/openisle/controller/PointHistoryController.java +++ b/backend/src/main/java/com/openisle/controller/PointHistoryController.java @@ -7,9 +7,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @RestController @@ -25,4 +27,10 @@ public class PointHistoryController { .map(pointHistoryMapper::toDto) .collect(Collectors.toList()); } + + @GetMapping("/trend") + public List> trend(Authentication auth, + @RequestParam(value = "days", defaultValue = "30") int days) { + return pointService.trend(auth.getName(), days); + } } diff --git a/backend/src/main/java/com/openisle/repository/PointHistoryRepository.java b/backend/src/main/java/com/openisle/repository/PointHistoryRepository.java index ac1ee7096..ac62b7df3 100644 --- a/backend/src/main/java/com/openisle/repository/PointHistoryRepository.java +++ b/backend/src/main/java/com/openisle/repository/PointHistoryRepository.java @@ -4,9 +4,12 @@ import com.openisle.model.PointHistory; import com.openisle.model.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.time.LocalDateTime; import java.util.List; public interface PointHistoryRepository extends JpaRepository { List findByUserOrderByIdDesc(User user); long countByUser(User user); + + List findByUserAndCreatedAtAfterOrderByCreatedAtDesc(User user, LocalDateTime createdAt); } diff --git a/backend/src/main/java/com/openisle/service/PointService.java b/backend/src/main/java/com/openisle/service/PointService.java index 2e5fa48b7..086f2c7a9 100644 --- a/backend/src/main/java/com/openisle/service/PointService.java +++ b/backend/src/main/java/com/openisle/service/PointService.java @@ -7,6 +7,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -173,4 +177,25 @@ public class PointService { return pointHistoryRepository.findByUserOrderByIdDesc(user); } + public List> trend(String userName, int days) { + if (days < 1) days = 1; + User user = userRepository.findByUsername(userName).orElseThrow(); + LocalDate end = LocalDate.now(); + LocalDate start = end.minusDays(days - 1L); + var histories = pointHistoryRepository.findByUserAndCreatedAtAfterOrderByCreatedAtDesc( + user, start.atStartOfDay()); + int idx = 0; + int balance = user.getPoint(); + List> result = new ArrayList<>(); + for (LocalDate day = end; !day.isBefore(start); day = day.minusDays(1)) { + result.add(Map.of("date", day.toString(), "value", balance)); + while (idx < histories.size() && histories.get(idx).getCreatedAt().toLocalDate().isEqual(day)) { + balance -= histories.get(idx).getAmount(); + idx++; + } + } + Collections.reverse(result); + return result; + } + } diff --git a/backend/src/test/java/com/openisle/controller/PointHistoryControllerTest.java b/backend/src/test/java/com/openisle/controller/PointHistoryControllerTest.java new file mode 100644 index 000000000..ca28563cf --- /dev/null +++ b/backend/src/test/java/com/openisle/controller/PointHistoryControllerTest.java @@ -0,0 +1,65 @@ +package com.openisle.controller; + +import com.openisle.config.CustomAccessDeniedHandler; +import com.openisle.config.SecurityConfig; +import com.openisle.service.PointService; +import com.openisle.mapper.PointHistoryMapper; +import com.openisle.service.JwtService; +import com.openisle.repository.UserRepository; +import com.openisle.model.User; +import com.openisle.model.Role; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(PointHistoryController.class) +@AutoConfigureMockMvc +@Import({SecurityConfig.class, CustomAccessDeniedHandler.class}) +class PointHistoryControllerTest { + @Autowired + private MockMvc mockMvc; + + @MockBean + private JwtService jwtService; + @MockBean + private UserRepository userRepository; + @MockBean + private PointService pointService; + @MockBean + private PointHistoryMapper pointHistoryMapper; + + @Test + void trendReturnsSeries() throws Exception { + Mockito.when(jwtService.validateAndGetSubject("token")).thenReturn("user"); + User user = new User(); + user.setUsername("user"); + user.setPassword("p"); + user.setEmail("u@example.com"); + user.setRole(Role.USER); + Mockito.when(userRepository.findByUsername("user")).thenReturn(Optional.of(user)); + List> data = List.of( + Map.of("date", java.time.LocalDate.now().minusDays(1).toString(), "value", 100), + Map.of("date", java.time.LocalDate.now().toString(), "value", 110) + ); + Mockito.when(pointService.trend(Mockito.eq("user"), Mockito.anyInt())).thenReturn(data); + + mockMvc.perform(get("/api/point-histories/trend").param("days", "2") + .header("Authorization", "Bearer token")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].value").value(100)) + .andExpect(jsonPath("$[1].value").value(110)); + } +} diff --git a/frontend_nuxt/pages/about/stats.vue b/frontend_nuxt/pages/about/stats.vue index 7b881d67d..331680de1 100644 --- a/frontend_nuxt/pages/about/stats.vue +++ b/frontend_nuxt/pages/about/stats.vue @@ -33,23 +33,11 @@