diff --git a/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardController.java b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardController.java new file mode 100644 index 00000000..ff94d76f --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardController.java @@ -0,0 +1,171 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.security.access.prepost.PreAuthorize; +import java.util.List; + +/** + * API Controller for Elementary Leaderboard + * CORS configured for public access without authentication + * + * Endpoints: + * - GET /api/elementary-leaderboard - Get all elementary scores + * - POST /api/elementary-leaderboard - Add new elementary score + * - DELETE /api/elementary-leaderboard/{id} - Delete score by ID + * - GET /api/elementary-leaderboard/top/{limit} - Top N scores + * - GET /api/elementary-leaderboard/game/{gameName} - Scores for specific game + * - GET /api/elementary-leaderboard/user/{user} - Scores for specific user + */ +@RestController +@RequestMapping("/api/elementary-leaderboard") +@CrossOrigin( + origins = "*", + allowedHeaders = "*", + methods = { + org.springframework.web.bind.annotation.RequestMethod.GET, + org.springframework.web.bind.annotation.RequestMethod.POST, + org.springframework.web.bind.annotation.RequestMethod.DELETE, + org.springframework.web.bind.annotation.RequestMethod.OPTIONS + }, + allowCredentials = "false" +) +@PreAuthorize("permitAll()") // Allow public access to all methods in this controller +public class ElementaryLeaderboardController { + + @Autowired + private ElementaryLeaderboardService elementaryLeaderboardService; + + /** + * READ - Get all elementary leaderboard entries ordered by score + * GET /api/elementary-leaderboard + */ + @GetMapping("") + public ResponseEntity> getAllEntries() { + try { + List entries = elementaryLeaderboardService.getAllEntriesByScore(); + // Always return a valid JSON array, even if empty + if (entries == null) { + entries = List.of(); + } + System.out.println("Elementary Leaderboard: Returning " + entries.size() + " entries"); + return ResponseEntity.ok(entries); + } catch (Exception e) { + System.err.println("Error fetching elementary leaderboard: " + e.getMessage()); + e.printStackTrace(); + return ResponseEntity.ok(List.of()); // Return empty array on error + } + } + + /** + * CREATE - Add new elementary score entry + * POST /api/elementary-leaderboard + * Request body: { "user": "playerName", "score": 100, "gameName": "GameName" } + */ + @PostMapping("") + public ResponseEntity addScore(@RequestBody InputScore scoreEntry) { + try { + // Validate input + if (scoreEntry.getScore() < 0) { + return ResponseEntity.badRequest().body("Score must be non-negative"); + } + + // Set default game name if not provided + if (scoreEntry.getGameName() == null || scoreEntry.getGameName().trim().isEmpty()) { + scoreEntry.setGameName("Global"); + } + + // Force id to null to ensure new entry is created (not updated) + scoreEntry.setId(null); + + // Save the score + InputScore savedEntry = elementaryLeaderboardService.addScore(scoreEntry); + + System.out.println("Elementary Leaderboard: Added new score - ID: " + savedEntry.getId() + + ", User: " + scoreEntry.getUser() + ", Score: " + scoreEntry.getScore() + + ", Game: " + scoreEntry.getGameName()); + + return ResponseEntity.status(HttpStatus.CREATED).body(savedEntry); + } catch (Exception e) { + System.err.println("Error adding elementary score: " + e.getMessage()); + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Failed to add score: " + e.getMessage()); + } + } + + /** + * DELETE - Delete score entry by ID + * DELETE /api/elementary-leaderboard/{id} + */ + @DeleteMapping("/{id}") + public ResponseEntity deleteScore(@PathVariable Long id) { + try { + boolean deleted = elementaryLeaderboardService.deleteScore(id); + + if (deleted) { + System.out.println("Elementary Leaderboard: Deleted score with ID: " + id); + return ResponseEntity.ok().body("Score deleted successfully"); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body("Score with ID " + id + " not found"); + } + } catch (Exception e) { + System.err.println("Error deleting elementary score: " + e.getMessage()); + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Failed to delete score: " + e.getMessage()); + } + } + + /** + * READ - Get top N scores + * GET /api/elementary-leaderboard/top/{limit} + */ + @GetMapping("/top/{limit}") + public ResponseEntity> getTopScores(@PathVariable int limit) { + List entries = elementaryLeaderboardService.getTopScores(limit); + return ResponseEntity.ok(entries != null ? entries : List.of()); + } + + /** + * READ - Get leaderboard entries for a specific game + * GET /api/elementary-leaderboard/game/{gameName} + */ + @GetMapping("/game/{gameName}") + public ResponseEntity> getEntriesByGame(@PathVariable String gameName) { + List entries = elementaryLeaderboardService.getEntriesByGame(gameName); + return ResponseEntity.ok(entries != null ? entries : List.of()); + } + + /** + * READ - Get leaderboard entries for a specific user + * GET /api/elementary-leaderboard/user/{user} + */ + @GetMapping("/user/{user}") + public ResponseEntity> getUserEntries(@PathVariable String user) { + List entries = elementaryLeaderboardService.getUserEntries(user); + return ResponseEntity.ok(entries != null ? entries : List.of()); + } + + /** + * READ - Get entries for a specific user and game + * GET /api/elementary-leaderboard/user/{user}/game/{gameName} + */ + @GetMapping("/user/{user}/game/{gameName}") + public ResponseEntity> getUserGameEntries( + @PathVariable String user, + @PathVariable String gameName) { + List entries = elementaryLeaderboardService.getUserGameEntries(user, gameName); + return ResponseEntity.ok(entries != null ? entries : List.of()); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardRepository.java new file mode 100644 index 00000000..1402a655 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardRepository.java @@ -0,0 +1,34 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; +import java.util.List; + +/** + * Repository for Elementary Leaderboard table (uses InputScore entity) + */ +@Repository +public interface ElementaryLeaderboardRepository extends JpaRepository { + + // Get all scores ordered by score descending + @Query("SELECT s FROM InputScore s ORDER BY s.score DESC") + List findAllByOrderByScoreDesc(); + + // Get top N scores + @Query("SELECT s FROM InputScore s ORDER BY s.score DESC") + List findTopScores(); + + // Get scores for a specific game + @Query("SELECT s FROM InputScore s WHERE s.gameName = :gameName ORDER BY s.score DESC") + List findByGameNameOrderByScoreDesc(@Param("gameName") String gameName); + + // Get scores for a specific user + @Query("SELECT s FROM InputScore s WHERE s.user = :user ORDER BY s.score DESC") + List findByUserOrderByScoreDesc(@Param("user") String user); + + // Get scores for a specific user and game + @Query("SELECT s FROM InputScore s WHERE s.user = :user AND s.gameName = :gameName ORDER BY s.score DESC") + List findByUserAndGameNameOrderByScoreDesc(@Param("user") String user, @Param("gameName") String gameName); +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardService.java b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardService.java new file mode 100644 index 00000000..83b2a833 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/ElementaryLeaderboardService.java @@ -0,0 +1,72 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Service layer for Elementary Leaderboard functionality + */ +@Service +public class ElementaryLeaderboardService { + + @Autowired + private ElementaryLeaderboardRepository elementaryLeaderboardRepository; + + /** + * Add a new score entry + */ + public InputScore addScore(InputScore scoreEntry) { + return elementaryLeaderboardRepository.save(scoreEntry); + } + + /** + * Delete a score entry by ID + */ + public boolean deleteScore(Long id) { + if (elementaryLeaderboardRepository.existsById(id)) { + elementaryLeaderboardRepository.deleteById(id); + return true; + } + return false; + } + + /** + * Get top N entries + */ + public List getTopScores(int limit) { + return elementaryLeaderboardRepository.findTopScores() + .stream() + .limit(limit) + .collect(Collectors.toList()); + } + + /** + * Get all entries ordered by score + */ + public List getAllEntriesByScore() { + return elementaryLeaderboardRepository.findAllByOrderByScoreDesc(); + } + + /** + * Get entries for a specific game + */ + public List getEntriesByGame(String gameName) { + return elementaryLeaderboardRepository.findByGameNameOrderByScoreDesc(gameName); + } + + /** + * Get entries for a specific user + */ + public List getUserEntries(String user) { + return elementaryLeaderboardRepository.findByUserOrderByScoreDesc(user); + } + + /** + * Get entries for a specific user and game + */ + public List getUserGameEntries(String user, String gameName) { + return elementaryLeaderboardRepository.findByUserAndGameNameOrderByScoreDesc(user, gameName); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/InputScore.java b/src/main/java/com/open/spring/mvc/leaderboard/InputScore.java new file mode 100644 index 00000000..8bc1a7b0 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/InputScore.java @@ -0,0 +1,38 @@ +package com.open.spring.mvc.leaderboard; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "elementary_leaderboard") +public class InputScore { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = true) + private String user; + + @Column(nullable = false) + private int score; + + @Column(nullable = true) + private String gameName; + + @Column(nullable = true) + private String variableName; + + // Constructor without ID (for creating new entries) + public InputScore(String user, int score, String gameName, String variableName) { + this.user = user; + this.score = score; + this.gameName = gameName; + this.variableName = variableName; + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java index 208c1ac8..0dd7a400 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java index b7d9434d..f1ddc593 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java @@ -15,6 +15,13 @@ public class LeaderboardService { @Autowired private LeaderboardRepository leaderboardRepository; + /** + * Add a new score entry + */ + public ScoreCounter addScore(ScoreCounter scoreEntry) { + return leaderboardRepository.save(scoreEntry); + } + /** * Get top N entries from pausemenu table */ diff --git a/src/main/java/com/open/spring/security/SecurityConfig.java b/src/main/java/com/open/spring/security/SecurityConfig.java index 1c9109b9..974d134d 100644 --- a/src/main/java/com/open/spring/security/SecurityConfig.java +++ b/src/main/java/com/open/spring/security/SecurityConfig.java @@ -87,6 +87,8 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce .requestMatchers("/api/pausemenu/**").permitAll() // Leaderboard should be public - displays scores without authentication .requestMatchers("/api/leaderboard/**").permitAll() + // Elementary Leaderboard should be public - displays scores without authentication + .requestMatchers("/api/elementary-leaderboard/**").permitAll() // Frontend calls gamer score endpoint; make it public .requestMatchers("/api/gamer/**").permitAll() // ==========================================