Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Enhance Reporting and Analytics Capabilities #102

Merged
merged 3 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Map<String, Object>> getLibraryOverview() {
return ResponseEntity.ok(analyticsService.getLibraryOverview());
}

@GetMapping("/popular-books")
public ResponseEntity<List<Map<String, Object>>> getPopularBooks(@RequestParam(defaultValue = "10") int limit) {
return ResponseEntity.ok(analyticsService.getPopularBooks(limit));
}

@GetMapping("/borrowing-trends")
public ResponseEntity<Map<String, Long>> 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<List<Map<String, Object>>> getMemberActivityReport() {
return ResponseEntity.ok(analyticsService.getMemberActivityReport());
}
}
45 changes: 45 additions & 0 deletions src/main/java/com/libraryman_api/analytics/AnalyticsService.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> getLibraryOverview() {
Map<String, Object> overview = new HashMap<>();
overview.put("totalBooks", bookRepository.count());
overview.put("totalMembers", memberRepository.count());
overview.put("totalBorrowings", borrowingRepository.count());
return overview;
}

public List<Map<String, Object>> getPopularBooks(int limit) {
return borrowingRepository.findMostBorrowedBooks(limit);
}

public Map<String, Long> getBorrowingTrends(LocalDate startDate, LocalDate endDate) {
return borrowingRepository.getBorrowingTrendsBetweenDates(startDate, endDate);
}

public List<Map<String, Object>> getMemberActivityReport() {
return memberRepository.getMemberActivityReport();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
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<Borrowings, Integer> {

// Underscore (_) used for property traversal, navigating from Borrowings to Members entity via 'member' property
Page<Borrowings> findByMember_memberId(int memberId, Pageable pageable);
Page<Borrowings> findByMember_memberId(int memberId, Pageable pageable); {
try {
Page<Borrowings> borrowings = borrowingRepository.findByMember_memberId(memberId, pageable);

List<Borrowings> findByBook_bookId(int bookId);
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)
List<Map<String, Object>> 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<String, Long> getBorrowingTrendsBetweenDates(LocalDate startDate, LocalDate endDate);
}
16 changes: 9 additions & 7 deletions src/main/java/com/libraryman_api/member/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,10 +15,9 @@ public interface MemberRepository extends JpaRepository<Members, Integer> {

Optional<Members> 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<Map<String, Object>> getMemberActivityReport();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -85,5 +85,4 @@ public CorsConfigurationSource corsConfigurationSource() {
public CorsFilter corsFilter() {
return new CorsFilter(corsConfigurationSource());
}

}