Skip to content
Open
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,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<List<InputScore>> getAllEntries() {
try {
List<InputScore> 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<List<InputScore>> getTopScores(@PathVariable int limit) {
List<InputScore> 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<List<InputScore>> getEntriesByGame(@PathVariable String gameName) {
List<InputScore> 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<List<InputScore>> getUserEntries(@PathVariable String user) {
List<InputScore> 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<List<InputScore>> getUserGameEntries(
@PathVariable String user,
@PathVariable String gameName) {
List<InputScore> entries = elementaryLeaderboardService.getUserGameEntries(user, gameName);
return ResponseEntity.ok(entries != null ? entries : List.of());
}
}
Original file line number Diff line number Diff line change
@@ -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<InputScore, Long> {

// Get all scores ordered by score descending
@Query("SELECT s FROM InputScore s ORDER BY s.score DESC")
List<InputScore> findAllByOrderByScoreDesc();

// Get top N scores
@Query("SELECT s FROM InputScore s ORDER BY s.score DESC")
List<InputScore> findTopScores();

// Get scores for a specific game
@Query("SELECT s FROM InputScore s WHERE s.gameName = :gameName ORDER BY s.score DESC")
List<InputScore> 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<InputScore> 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<InputScore> findByUserAndGameNameOrderByScoreDesc(@Param("user") String user, @Param("gameName") String gameName);
}
Original file line number Diff line number Diff line change
@@ -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<InputScore> getTopScores(int limit) {
return elementaryLeaderboardRepository.findTopScores()
.stream()
.limit(limit)
.collect(Collectors.toList());
}

/**
* Get all entries ordered by score
*/
public List<InputScore> getAllEntriesByScore() {
return elementaryLeaderboardRepository.findAllByOrderByScoreDesc();
}

/**
* Get entries for a specific game
*/
public List<InputScore> getEntriesByGame(String gameName) {
return elementaryLeaderboardRepository.findByGameNameOrderByScoreDesc(gameName);
}

/**
* Get entries for a specific user
*/
public List<InputScore> getUserEntries(String user) {
return elementaryLeaderboardRepository.findByUserOrderByScoreDesc(user);
}

/**
* Get entries for a specific user and game
*/
public List<InputScore> getUserGameEntries(String user, String gameName) {
return elementaryLeaderboardRepository.findByUserAndGameNameOrderByScoreDesc(user, gameName);
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/open/spring/mvc/leaderboard/InputScore.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/open/spring/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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()
// ==========================================
Expand Down