diff --git a/src/main/java/com/libraryman_api/analytics/AnalyticsController.java b/src/main/java/com/libraryman_api/analytics/AnalyticsController.java new file mode 100644 index 0000000..04723bc --- /dev/null +++ b/src/main/java/com/libraryman_api/analytics/AnalyticsController.java @@ -0,0 +1,42 @@ +package com.libraryman_api.analytics; + +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/analytics") +public class AnalyticsController { + + private final AnalyticsService analyticsService; + + public AnalyticsController(AnalyticsService analyticsService) { + this.analyticsService = analyticsService; + } + + @GetMapping("/overview") + public ResponseEntity> getLibraryOverview() { + return ResponseEntity.ok(analyticsService.getLibraryOverview()); + } + + @GetMapping("/popular-books") + public ResponseEntity>> getPopularBooks(@RequestParam(defaultValue = "10") int limit) { + return ResponseEntity.ok(analyticsService.getPopularBooks(limit)); + } + + @GetMapping("/borrowing-trends") + public ResponseEntity> getBorrowingTrends( + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { + return ResponseEntity.ok(analyticsService.getBorrowingTrends(startDate, endDate)); + } + + @GetMapping("/member-activity") + public ResponseEntity>> getMemberActivityReport() { + return ResponseEntity.ok(analyticsService.getMemberActivityReport()); + } +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/analytics/AnalyticsService.java b/src/main/java/com/libraryman_api/analytics/AnalyticsService.java new file mode 100644 index 0000000..7008ec0 --- /dev/null +++ b/src/main/java/com/libraryman_api/analytics/AnalyticsService.java @@ -0,0 +1,45 @@ +package com.libraryman_api.analytics; + +import com.libraryman_api.book.BookRepository; +import com.libraryman_api.borrowing.BorrowingRepository; +import com.libraryman_api.member.MemberRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class AnalyticsService { + + private final BookRepository bookRepository; + private final BorrowingRepository borrowingRepository; + private final MemberRepository memberRepository; + + public AnalyticsService(BookRepository bookRepository, BorrowingRepository borrowingRepository, MemberRepository memberRepository) { + this.bookRepository = bookRepository; + this.borrowingRepository = borrowingRepository; + this.memberRepository = memberRepository; + } + + public Map getLibraryOverview() { + Map overview = new HashMap<>(); + overview.put("totalBooks", bookRepository.count()); + overview.put("totalMembers", memberRepository.count()); + overview.put("totalBorrowings", borrowingRepository.count()); + return overview; + } + + public List> getPopularBooks(int limit) { + return borrowingRepository.findMostBorrowedBooks(limit); + } + + public Map getBorrowingTrends(LocalDate startDate, LocalDate endDate) { + return borrowingRepository.getBorrowingTrendsBetweenDates(startDate, endDate); + } + + public List> getMemberActivityReport() { + return memberRepository.getMemberActivityReport(); + } +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java index e718aaf..3b0a6c2 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java @@ -1,18 +1,24 @@ package com.libraryman_api.borrowing; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.util.List; +import java.util.Map; @Repository public interface BorrowingRepository extends JpaRepository { - // Underscore (_) used for property traversal, navigating from Borrowings to Members entity via 'member' property - Page findByMember_memberId(int memberId, Pageable pageable); - - List findByBook_bookId(int bookId); -} + @Query(value = "SELECT b.title AS bookTitle, COUNT(*) AS borrowCount " + + "FROM borrowings br JOIN books b ON br.book_id = b.book_id " + + "GROUP BY b.book_id ORDER BY borrowCount DESC LIMIT :limit", nativeQuery = true) + List> findMostBorrowedBooks(int limit); + @Query("SELECT FUNCTION('DATE', b.borrowDate) as date, COUNT(*) as count " + + "FROM Borrowings b " + + "WHERE b.borrowDate BETWEEN :startDate AND :endDate " + + "GROUP BY FUNCTION('DATE', b.borrowDate)") + Map getBorrowingTrendsBetweenDates(LocalDate startDate, LocalDate endDate); +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/member/MemberRepository.java b/src/main/java/com/libraryman_api/member/MemberRepository.java index 2c02216..62b0641 100644 --- a/src/main/java/com/libraryman_api/member/MemberRepository.java +++ b/src/main/java/com/libraryman_api/member/MemberRepository.java @@ -1,8 +1,11 @@ package com.libraryman_api.member; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Map; import java.util.Optional; @Repository @@ -12,10 +15,9 @@ public interface MemberRepository extends JpaRepository { Optional findByUsername(String username); -/** - * SELECT SUM(amount) AS totalFines - * FROM fines - * WHERE memberId = [memberId] AND paid = false; - */ -} - + @Query(value = "SELECT m.name AS memberName, COUNT(b.borrowing_id) AS borrowCount, " + + "SUM(CASE WHEN b.return_date IS NULL THEN 1 ELSE 0 END) AS currentBorrowings " + + "FROM members m LEFT JOIN borrowings b ON m.member_id = b.member_id " + + "GROUP BY m.member_id ORDER BY borrowCount DESC", nativeQuery = true) + List> getMemberActivityReport(); +} \ No newline at end of file diff --git a/src/main/java/com/libraryman_api/security/config/WebConfiguration.java b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java index 413db45..89627c1 100644 --- a/src/main/java/com/libraryman_api/security/config/WebConfiguration.java +++ b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java @@ -37,10 +37,10 @@ public SecurityFilterChain web(HttpSecurity http) throws Exception { .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests((request) -> request // make sure it is in order to access the proper Url - .requestMatchers("/api/signup").permitAll() .requestMatchers("/api/login").permitAll() .requestMatchers("/api/logout").permitAll() + .requestMatchers("/api/analytics/**").hasAnyRole("ADMIN", "LIBRARIAN") // New line for analytics .anyRequest().authenticated() ) .logout(logout -> logout @@ -85,5 +85,4 @@ public CorsConfigurationSource corsConfigurationSource() { public CorsFilter corsFilter() { return new CorsFilter(corsConfigurationSource()); } - } \ No newline at end of file