From 72ac746f517f38e32a7f71248306f50eeb949422 Mon Sep 17 00:00:00 2001 From: Sanchit Chauhan <150342364+sanchitc05@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:03:10 +0000 Subject: [PATCH 1/3] [FEATURE] Enhance Reporting and Analytics Capabilities Fixes #95 --- .../analytics/AnalyticsController.java | 42 +++++++++++++++++ .../analytics/AnalyticsService.java | 45 +++++++++++++++++++ .../borrowing/BorrowingRepository.java | 20 ++++++--- .../member/MemberRepository.java | 16 ++++--- .../security/config/WebConfiguration.java | 3 +- 5 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/libraryman_api/analytics/AnalyticsController.java create mode 100644 src/main/java/com/libraryman_api/analytics/AnalyticsService.java 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 From f1d7044a6d4a79d2a59f1e73edc028fd547e7e7f Mon Sep 17 00:00:00 2001 From: Sanchit Chauhan <150342364+sanchitc05@users.noreply.github.com> Date: Tue, 5 Nov 2024 22:15:55 +0530 Subject: [PATCH 2/3] Update BorrowingRepository.java --- .../borrowing/BorrowingRepository.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java index 3b0a6c2..cbabd93 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java @@ -11,6 +11,19 @@ @Repository public interface BorrowingRepository extends JpaRepository { +public Page getAllBorrowingsOfMember(int memberId, Pageable pageable) { +try { +Page borrowings = borrowingRepository.findByMember_memberId(memberId, pageable); + +if (borrowings.isEmpty()) { + throw new ResourceNotFoundException("Member didn't borrow any book"); +} +return borrowings.map(this::EntityToDto); +} catch (PropertyReferenceException ex) { + throw new InvalidSortFieldException("The specified 'sortBy' value is invalid."); +} +} + @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) @@ -21,4 +34,4 @@ public interface BorrowingRepository extends JpaRepository "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 +} From 51c05e37e71b2a37bff56a52e8c8d1413f15e0f9 Mon Sep 17 00:00:00 2001 From: Sanchit Chauhan <150342364+sanchitc05@users.noreply.github.com> Date: Sat, 9 Nov 2024 19:43:48 +0530 Subject: [PATCH 3/3] Update BorrowingRepository.java --- .../java/com/libraryman_api/borrowing/BorrowingRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java index cbabd93..3acc4ef 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingRepository.java @@ -11,7 +11,7 @@ @Repository public interface BorrowingRepository extends JpaRepository { -public Page getAllBorrowingsOfMember(int memberId, Pageable pageable) { +Page findByMember_memberId(int memberId, Pageable pageable); { try { Page borrowings = borrowingRepository.findByMember_memberId(memberId, pageable);