From d71d781bc15bc3e898e7ccb7c8e309baef300b26 Mon Sep 17 00:00:00 2001 From: SoniDhenuva Date: Tue, 18 Nov 2025 10:45:10 -0800 Subject: [PATCH 01/23] multiplayer --- .../open/spring/mvc/multiplayer/Player.java | 38 ++++++++++++ .../mvc/multiplayer/PlayerController.java | 60 +++++++++++++++++++ .../mvc/multiplayer/PlayerRepository.java | 12 ++++ 3 files changed, 110 insertions(+) create mode 100644 src/main/java/com/open/spring/mvc/multiplayer/Player.java create mode 100644 src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java create mode 100644 src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java diff --git a/src/main/java/com/open/spring/mvc/multiplayer/Player.java b/src/main/java/com/open/spring/mvc/multiplayer/Player.java new file mode 100644 index 00000000..16c220da --- /dev/null +++ b/src/main/java/com/open/spring/mvc/multiplayer/Player.java @@ -0,0 +1,38 @@ +package com.open.spring.mvc.multiplayer; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.time.LocalDateTime; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "players") +public class Player { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false) + private String status; // "online" or "offline" + + @Column(name = "last_active") + private LocalDateTime lastActive; + + @Column(name = "connected_at") + private LocalDateTime connectedAt; + + // Constructor for quick creation + public Player(String username, String status) { + this.username = username; + this.status = status; + this.lastActive = LocalDateTime.now(); + this.connectedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java new file mode 100644 index 00000000..fe329955 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -0,0 +1,60 @@ +package com.open.spring.mvc.multiplayer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/players") +@CrossOrigin +public class PlayerController { + @Autowired + private PlayerRepository playerRepository; + + @PostMapping("/connect") + public Player connect(@RequestBody Map request) { + String username = request.get("username"); + + Player player = playerRepository.findByUsername(username) + .orElse(new Player(username, "online")); + + player.setStatus("online"); + player.setLastActive(LocalDateTime.now()); + player.setConnectedAt(LocalDateTime.now()); + + return playerRepository.save(player); + } + + @GetMapping("/online") + public Map getOnlinePlayers() { + List onlinePlayers = playerRepository.findByStatus("online"); + return Map.of("players", onlinePlayers); + } + + @PutMapping("/status") + public Player updateStatus(@RequestBody Map request) { + String username = request.get("username"); + String status = request.get("status"); + + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + + player.setStatus(status); + player.setLastActive(LocalDateTime.now()); + + return playerRepository.save(player); + } + + @PostMapping("/disconnect") + public void disconnect(@RequestBody Map request) { + String username = request.get("username"); + + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + + player.setStatus("offline"); + playerRepository.save(player); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java new file mode 100644 index 00000000..30096c20 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java @@ -0,0 +1,12 @@ +package com.open.spring.mvc.multiplayer; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + +@Repository +public interface PlayerRepository extends JpaRepository { + Optional findByUsername(String username); + List findByStatus(String status); +} \ No newline at end of file From a45991c2793fa1d816d905fc9ff7581d7ecd8630 Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 19 Nov 2025 11:17:55 -0800 Subject: [PATCH 02/23] playerposition --- .../com/open/spring/mvc/multiplayer/Player.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/Player.java b/src/main/java/com/open/spring/mvc/multiplayer/Player.java index 16c220da..dccd7ccc 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/Player.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/Player.java @@ -35,4 +35,20 @@ public Player(String username, String status) { this.lastActive = LocalDateTime.now(); this.connectedAt = LocalDateTime.now(); } + // In Player.java + +private double x; +private double y; +private int level; + +// getters and setters +public double getX() { return x; } +public void setX(double x) { this.x = x; } + +public double getY() { return y; } +public void setY(double y) { this.y = y; } + +public int getLevel() { return level; } +public void setLevel(int level) { this.level = level; } + } \ No newline at end of file From 47b5a379f28ce0ee9084a3abb1d6c7db68673e93 Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 19 Nov 2025 11:21:13 -0800 Subject: [PATCH 03/23] locationendpoint --- .../spring/mvc/multiplayer/PlayerController.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java index fe329955..00d914fa 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -57,4 +57,20 @@ public void disconnect(@RequestBody Map request) { player.setStatus("offline"); playerRepository.save(player); } + @PutMapping("/location") + public Player updateLocation(@RequestBody Map request) { + String username = (String) request.get("username"); + double x = (double) request.get("x"); + double y = (double) request.get("y"); + + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + + player.setX(x); + player.setY(y); + player.setLastActive(LocalDateTime.now()); + + return playerRepository.save(player); + } + } \ No newline at end of file From 700c22b6ceeeb13163265bfa24d5d6480350f68c Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 19 Nov 2025 11:22:40 -0800 Subject: [PATCH 04/23] levelsave --- .../spring/mvc/multiplayer/PlayerController.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java index 00d914fa..d0c53e68 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -72,5 +72,17 @@ public Player updateLocation(@RequestBody Map request) { return playerRepository.save(player); } + @PutMapping("/level") + public Player updateLevel(@RequestBody Map request) { + String username = (String) request.get("username"); + int level = (int) request.get("level"); + + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + player.setLevel(level); + player.setLastActive(LocalDateTime.now()); + + return playerRepository.save(player); + } } \ No newline at end of file From 78ecee86afd10fb5e198b3e7141f29aa66bc81bf Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 19 Nov 2025 11:24:27 -0800 Subject: [PATCH 05/23] multilocation --- .../spring/mvc/multiplayer/PlayerController.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java index d0c53e68..39bdc4c7 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -85,4 +85,18 @@ public Player updateLevel(@RequestBody Map request) { return playerRepository.save(player); } + + // for multiplayer locations if needed: + @GetMapping("/locations") + public Map getPlayerLocations() { + List players = playerRepository.findByStatus("online"); + + return Map.of("players", players.stream().map(p -> Map.of( + "username", p.getUsername(), + "x", p.getX(), + "y", p.getY(), + "level", p.getLevel() + )).toList()); + } + } \ No newline at end of file From 43df5f36ed3fb3b2862f5c89ca3f8d6873eedd08 Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 19 Nov 2025 11:26:05 -0800 Subject: [PATCH 06/23] addingqueries --- .../java/com/open/spring/mvc/multiplayer/PlayerRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java index 30096c20..23cc4f21 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java @@ -9,4 +9,7 @@ public interface PlayerRepository extends JpaRepository { Optional findByUsername(String username); List findByStatus(String status); + // numerical queries + List findByLevel(int level); + List findByXBetweenAndYBetween(double x1, double x2, double y1, double y2); } \ No newline at end of file From ee91af7989caa577050d08724f105e07b6c90e6b Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Wed, 19 Nov 2025 11:27:31 -0800 Subject: [PATCH 07/23] crud and db base files made --- .../open/spring/mvc/leaderboard/Player.java | 64 ++++++ .../mvc/leaderboard/PlayerApiController.java | 188 ++++++++++++++++++ .../mvc/leaderboard/PlayerJpaRepository.java | 12 ++ 3 files changed, 264 insertions(+) create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/Player.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/Player.java b/src/main/java/com/open/spring/mvc/leaderboard/Player.java new file mode 100644 index 00000000..df9eb2a8 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/Player.java @@ -0,0 +1,64 @@ +package com.open.spring.mvc.leaderboard; + +import java.util.ArrayList; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +public class Player { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(unique = true, nullable = false) + private String username; + + @Column(nullable = false) + private String password; + + private String role = "PLAYER"; + private boolean enabled = true; + + @Column(nullable = false) + private int highScore = 0; + + public Player(String username, String password, String role, boolean enabled, int highScore) { + this.username = username; + this.password = password; + this.role = role; + this.enabled = enabled; + this.highScore = highScore; + } + + public static Player createPlayer(String username, String password, String role, boolean enabled, int highScore) { + Player player = new Player(); + player.setUsername(username); + player.setPassword(password); + player.setRole(role); + player.setEnabled(enabled); + player.setHighScore(highScore); + return player; + } + + public static Player[] init() { + ArrayList players = new ArrayList<>(); + + players.add(createPlayer("ProGamer123", "pass123", "PLAYER", true, 15000)); + players.add(createPlayer("SpeedRunner", "pass456", "PLAYER", true, 28500)); + players.add(createPlayer("ElitePlayer", "pass789", "PLAYER", true, 42000)); + players.add(createPlayer("NoobMaster", "pass321", "PLAYER", true, 8500)); + players.add(createPlayer("GamerGod", "pass654", "PLAYER", true, 55000)); + + return players.toArray(new Player[0]); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java b/src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java new file mode 100644 index 00000000..452ff269 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java @@ -0,0 +1,188 @@ +package com.open.spring.mvc.leaderboard; + +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Controller; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Controller +@RequestMapping("/player") +public class PlayerApiController { + @Autowired + private PlayerService playerService; + + @PostMapping("/register") + @ResponseBody + public String registerPlayer(@RequestBody PlayerRegistrationRequest request) { + if (playerService.registerPlayer(request.getUsername(), request.getPassword()) != null) { + return "Player registered successfully!"; + } + return "Registration failed!"; + } + + @PostMapping("/login") + @ResponseBody + public ResponseEntity loginPlayer(@RequestBody PlayerLoginRequest request) { + if (request.getPassword() == null || request.getPassword().isEmpty()) { + return ResponseEntity.badRequest().body("Password cannot be empty!"); + } + + Optional playerOptional = playerService.findByUsername(request.getUsername()); + if (playerOptional.isPresent()) { + Player player = playerOptional.get(); + if (playerService.checkPassword(request.getPassword(), player.getPassword())) { + return ResponseEntity.ok("Redirecting to game"); + } + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password!"); + } + + @PostMapping("/updateScore") + @ResponseBody + public ResponseEntity updateScore(@RequestBody ScoreUpdateRequest request) { + try { + playerService.updateHighScore(request.getUsername(), request.getScore()); + return ResponseEntity.ok("Score updated successfully!"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("An error occurred: " + e.getMessage()); + } + } + + @GetMapping("/getScore") + @ResponseBody + public ResponseEntity getScore(@RequestParam String username) { + try { + int highScore = playerService.getHighScore(username); + return ResponseEntity.ok(highScore); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(null); + } + } + + @GetMapping("/leaderboard") + @ResponseBody + public List getLeaderboard() { + return playerService.getTopPlayersByScore(); + } +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class LeaderboardEntry { + private String username; + private int highScore; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class PlayerRegistrationRequest { + private String username; + private String password; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class PlayerLoginRequest { + private String username; + private String password; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class ScoreUpdateRequest { + private String username; + private int score; +} + +@Service +class PlayerService implements UserDetailsService { + @Autowired + private PlayerJpaRepository playerRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Override + public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("Player not found")); + + List authorities = List.of(new SimpleGrantedAuthority("ROLE_" + player.getRole())); + + return org.springframework.security.core.userdetails.User + .withUsername(player.getUsername()) + .password(player.getPassword()) + .authorities(authorities) + .accountLocked(!player.isEnabled()) + .build(); + } + + public Player registerPlayer(String username, String password) { + Player player = new Player(); + player.setUsername(username); + player.setPassword(passwordEncoder.encode(password)); + player.setHighScore(0); + return playerRepository.save(player); + } + + public Optional findByUsername(String username) { + return playerRepository.findByUsername(username); + } + + public boolean checkPassword(String rawPassword, String encodedPassword) { + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + public void updateHighScore(String username, int score) { + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + + // Only update if the new score is higher than the current high score + if (score > player.getHighScore()) { + player.setHighScore(score); + playerRepository.save(player); + } + } + + public int getHighScore(String username) { + Player player = playerRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("Player not found")); + return player.getHighScore(); + } + + public List getTopPlayersByScore() { + return playerRepository.findAll().stream() + .sorted(Comparator.comparingInt(Player::getHighScore).reversed()) + .limit(10) + .map(player -> new LeaderboardEntry(player.getUsername(), player.getHighScore())) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java new file mode 100644 index 00000000..ce333c04 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java @@ -0,0 +1,12 @@ +package com.open.spring.mvc.leaderboard; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlayerJpaRepository extends JpaRepository { + Optional findByUsername(String username); + List findByUsernameIgnoreCase(String username); + Player findById(Integer playerId); +} \ No newline at end of file From 10eb293e122f1d07f1eaedc75bc2ce716340c06d Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Thu, 20 Nov 2025 10:10:17 -0800 Subject: [PATCH 08/23] fixed name conflict + post mapping changed --- .../leaderboard/{Player.java => Gamer.java} | 25 ++++++++++--------- ...ontroller.java => GamerApiController.java} | 24 +++++++++--------- .../mvc/leaderboard/GamerJpaRepository.java | 12 +++++++++ .../mvc/leaderboard/PlayerJpaRepository.java | 12 --------- 4 files changed, 37 insertions(+), 36 deletions(-) rename src/main/java/com/open/spring/mvc/leaderboard/{Player.java => Gamer.java} (56%) rename src/main/java/com/open/spring/mvc/leaderboard/{PlayerApiController.java => GamerApiController.java} (89%) create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/Player.java b/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java similarity index 56% rename from src/main/java/com/open/spring/mvc/leaderboard/Player.java rename to src/main/java/com/open/spring/mvc/leaderboard/Gamer.java index df9eb2a8..35066997 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/Player.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java @@ -15,7 +15,7 @@ @NoArgsConstructor @AllArgsConstructor @Entity -public class Player { +public class Gamer { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @@ -32,7 +32,7 @@ public class Player { @Column(nullable = false) private int highScore = 0; - public Player(String username, String password, String role, boolean enabled, int highScore) { + public Gamer(String username, String password, String role, boolean enabled, int highScore) { this.username = username; this.password = password; this.role = role; @@ -40,8 +40,8 @@ public Player(String username, String password, String role, boolean enabled, in this.highScore = highScore; } - public static Player createPlayer(String username, String password, String role, boolean enabled, int highScore) { - Player player = new Player(); + public static Gamer createPlayer(String username, String password, String role, boolean enabled, int highScore) { + Gamer player = new Gamer(); player.setUsername(username); player.setPassword(password); player.setRole(role); @@ -50,15 +50,16 @@ public static Player createPlayer(String username, String password, String role, return player; } - public static Player[] init() { - ArrayList players = new ArrayList<>(); + public static Gamer[] init() { + ArrayList players = new ArrayList<>(); - players.add(createPlayer("ProGamer123", "pass123", "PLAYER", true, 15000)); - players.add(createPlayer("SpeedRunner", "pass456", "PLAYER", true, 28500)); - players.add(createPlayer("ElitePlayer", "pass789", "PLAYER", true, 42000)); - players.add(createPlayer("NoobMaster", "pass321", "PLAYER", true, 8500)); - players.add(createPlayer("GamerGod", "pass654", "PLAYER", true, 55000)); + players.add(createPlayer("Avika", "pass123", "PLAYER", true, 15000)); + players.add(createPlayer("Soni", "pass456", "PLAYER", true, 28500)); + players.add(createPlayer("Nora", "pass789", "PLAYER", true, 42000)); + players.add(createPlayer("Gurshawn", "pass321", "PLAYER", true, 8500)); + players.add(createPlayer("Xavier", "pass654", "PLAYER", true, 55000)); + players.add(createPlayer("Spencer", "pass765", "PLAYER", true, 54000)); - return players.toArray(new Player[0]); + return players.toArray(new Gamer[0]); } } \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java similarity index 89% rename from src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java rename to src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java index 452ff269..de21e056 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/PlayerApiController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java @@ -27,8 +27,8 @@ import lombok.NoArgsConstructor; @Controller -@RequestMapping("/player") -public class PlayerApiController { +@RequestMapping("/gamer") +public class GamerApiController { @Autowired private PlayerService playerService; @@ -48,9 +48,9 @@ public ResponseEntity loginPlayer(@RequestBody PlayerLoginRequest reques return ResponseEntity.badRequest().body("Password cannot be empty!"); } - Optional playerOptional = playerService.findByUsername(request.getUsername()); + Optional playerOptional = playerService.findByUsername(request.getUsername()); if (playerOptional.isPresent()) { - Player player = playerOptional.get(); + Gamer player = playerOptional.get(); if (playerService.checkPassword(request.getPassword(), player.getPassword())) { return ResponseEntity.ok("Redirecting to game"); } @@ -124,7 +124,7 @@ class ScoreUpdateRequest { @Service class PlayerService implements UserDetailsService { @Autowired - private PlayerJpaRepository playerRepository; + private GamerJpaRepository playerRepository; @Autowired private PasswordEncoder passwordEncoder; @@ -132,7 +132,7 @@ class PlayerService implements UserDetailsService { @Override public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - Player player = playerRepository.findByUsername(username) + Gamer player = playerRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("Player not found")); List authorities = List.of(new SimpleGrantedAuthority("ROLE_" + player.getRole())); @@ -145,15 +145,15 @@ public org.springframework.security.core.userdetails.UserDetails loadUserByUsern .build(); } - public Player registerPlayer(String username, String password) { - Player player = new Player(); + public Gamer registerPlayer(String username, String password) { + Gamer player = new Gamer(); player.setUsername(username); player.setPassword(passwordEncoder.encode(password)); player.setHighScore(0); return playerRepository.save(player); } - public Optional findByUsername(String username) { + public Optional findByUsername(String username) { return playerRepository.findByUsername(username); } @@ -162,7 +162,7 @@ public boolean checkPassword(String rawPassword, String encodedPassword) { } public void updateHighScore(String username, int score) { - Player player = playerRepository.findByUsername(username) + Gamer player = playerRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("Player not found")); // Only update if the new score is higher than the current high score @@ -173,14 +173,14 @@ public void updateHighScore(String username, int score) { } public int getHighScore(String username) { - Player player = playerRepository.findByUsername(username) + Gamer player = playerRepository.findByUsername(username) .orElseThrow(() -> new RuntimeException("Player not found")); return player.getHighScore(); } public List getTopPlayersByScore() { return playerRepository.findAll().stream() - .sorted(Comparator.comparingInt(Player::getHighScore).reversed()) + .sorted(Comparator.comparingInt(Gamer::getHighScore).reversed()) .limit(10) .map(player -> new LeaderboardEntry(player.getUsername(), player.getHighScore())) .collect(Collectors.toList()); diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java new file mode 100644 index 00000000..e7f854f1 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java @@ -0,0 +1,12 @@ +package com.open.spring.mvc.leaderboard; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GamerJpaRepository extends JpaRepository { + Optional findByUsername(String username); + List findByUsernameIgnoreCase(String username); + Gamer findById(Integer playerId); +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java deleted file mode 100644 index ce333c04..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/PlayerJpaRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import java.util.List; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface PlayerJpaRepository extends JpaRepository { - Optional findByUsername(String username); - List findByUsernameIgnoreCase(String username); - Player findById(Integer playerId); -} \ No newline at end of file From f4696ddd20da362161ec86c50c1dd95d26b9d6f5 Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Wed, 3 Dec 2025 11:14:44 -0800 Subject: [PATCH 09/23] schema added to leaderboard table! --- .../open/spring/mvc/leaderboard/Gamer.java | 50 ++--- .../mvc/leaderboard/GamerApiController.java | 188 +++++++----------- .../spring/mvc/leaderboard/GamerInit.java | 39 ++++ .../mvc/leaderboard/GamerJpaRepository.java | 6 +- 4 files changed, 134 insertions(+), 149 deletions(-) create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java b/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java index 35066997..4becf1e3 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java @@ -1,7 +1,5 @@ package com.open.spring.mvc.leaderboard; -import java.util.ArrayList; - import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -26,40 +24,22 @@ public class Gamer { @Column(nullable = false) private String password; - private String role = "PLAYER"; - private boolean enabled = true; + private String role; + private boolean enabled; @Column(nullable = false) - private int highScore = 0; - - public Gamer(String username, String password, String role, boolean enabled, int highScore) { - this.username = username; - this.password = password; - this.role = role; - this.enabled = enabled; - this.highScore = highScore; - } - - public static Gamer createPlayer(String username, String password, String role, boolean enabled, int highScore) { - Gamer player = new Gamer(); - player.setUsername(username); - player.setPassword(password); - player.setRole(role); - player.setEnabled(enabled); - player.setHighScore(highScore); - return player; - } - - public static Gamer[] init() { - ArrayList players = new ArrayList<>(); - - players.add(createPlayer("Avika", "pass123", "PLAYER", true, 15000)); - players.add(createPlayer("Soni", "pass456", "PLAYER", true, 28500)); - players.add(createPlayer("Nora", "pass789", "PLAYER", true, 42000)); - players.add(createPlayer("Gurshawn", "pass321", "PLAYER", true, 8500)); - players.add(createPlayer("Xavier", "pass654", "PLAYER", true, 55000)); - players.add(createPlayer("Spencer", "pass765", "PLAYER", true, 54000)); - - return players.toArray(new Gamer[0]); + private int highScore; + + // Starting players for initialization + public static String[] init() { + final String[] playersArray = { + "Avika", + "Soni", + "Nora", + "Gurshawn", + "Xavier", + "Spencer" + }; + return playersArray; } } \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java index de21e056..7cd2c9e1 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java @@ -8,87 +8,115 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Controller; -import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -@Controller -@RequestMapping("/gamer") +@RestController // Changed from @Controller to @RestController +@RequestMapping("/api/gamer") // Changed to /api/gamer to follow REST conventions public class GamerApiController { + + @Autowired + private GamerJpaRepository repository; + @Autowired - private PlayerService playerService; + private PasswordEncoder passwordEncoder; + + /* GET List of All Players + * @GetMapping annotation is used for mapping HTTP GET requests onto specific handler methods. + */ + @GetMapping("/") + public ResponseEntity> getPlayers() { + return new ResponseEntity<>(repository.findAll(), HttpStatus.OK); + } + /* POST Register New Player + */ @PostMapping("/register") - @ResponseBody - public String registerPlayer(@RequestBody PlayerRegistrationRequest request) { - if (playerService.registerPlayer(request.getUsername(), request.getPassword()) != null) { - return "Player registered successfully!"; + public ResponseEntity registerPlayer(@RequestBody PlayerRegistrationRequest request) { + // Check if username already exists + Optional existingPlayer = repository.findByUsername(request.getUsername()); + if (existingPlayer.isPresent()) { + return new ResponseEntity<>("Username already exists!", HttpStatus.BAD_REQUEST); } - return "Registration failed!"; + + // Create new player + Gamer player = new Gamer(); + player.setUsername(request.getUsername()); + player.setPassword(passwordEncoder.encode(request.getPassword())); + player.setRole("PLAYER"); + player.setEnabled(true); + player.setHighScore(0); + + repository.save(player); + return new ResponseEntity<>("Player registered successfully!", HttpStatus.OK); } + /* POST Login Player + */ @PostMapping("/login") - @ResponseBody public ResponseEntity loginPlayer(@RequestBody PlayerLoginRequest request) { if (request.getPassword() == null || request.getPassword().isEmpty()) { - return ResponseEntity.badRequest().body("Password cannot be empty!"); + return new ResponseEntity<>("Password cannot be empty!", HttpStatus.BAD_REQUEST); } - Optional playerOptional = playerService.findByUsername(request.getUsername()); + Optional playerOptional = repository.findByUsername(request.getUsername()); if (playerOptional.isPresent()) { Gamer player = playerOptional.get(); - if (playerService.checkPassword(request.getPassword(), player.getPassword())) { - return ResponseEntity.ok("Redirecting to game"); + if (passwordEncoder.matches(request.getPassword(), player.getPassword())) { + return new ResponseEntity<>("Login successful!", HttpStatus.OK); } } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password!"); + return new ResponseEntity<>("Invalid username or password!", HttpStatus.UNAUTHORIZED); } - @PostMapping("/updateScore") - @ResponseBody - public ResponseEntity updateScore(@RequestBody ScoreUpdateRequest request) { - try { - playerService.updateHighScore(request.getUsername(), request.getScore()); - return ResponseEntity.ok("Score updated successfully!"); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("An error occurred: " + e.getMessage()); + /* POST Update Player Score + * @PathVariable annotation can be used if you want to pass username in URL + */ + @PostMapping("/score") + public ResponseEntity updateScore(@RequestBody ScoreUpdateRequest request) { + Optional optional = repository.findByUsername(request.getUsername()); + if (optional.isPresent()) { + Gamer player = optional.get(); + // Only update if the new score is higher than the current high score + if (request.getScore() > player.getHighScore()) { + player.setHighScore(request.getScore()); + repository.save(player); + } + return new ResponseEntity<>(player, HttpStatus.OK); } + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - @GetMapping("/getScore") - @ResponseBody - public ResponseEntity getScore(@RequestParam String username) { - try { - int highScore = playerService.getHighScore(username); - return ResponseEntity.ok(highScore); - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.NOT_FOUND) - .body(null); + /* GET Player Score by Username + */ + @GetMapping("/score/{username}") + public ResponseEntity getScore(@PathVariable String username) { + Optional optional = repository.findByUsername(username); + if (optional.isPresent()) { + return new ResponseEntity<>(optional.get().getHighScore(), HttpStatus.OK); } + return new ResponseEntity<>(HttpStatus.NOT_FOUND); } + /* GET Leaderboard - Top 10 Players + */ @GetMapping("/leaderboard") - @ResponseBody - public List getLeaderboard() { - return playerService.getTopPlayersByScore(); + public ResponseEntity> getLeaderboard() { + List leaderboard = repository.findAll().stream() + .sorted(Comparator.comparingInt(Gamer::getHighScore).reversed()) + .limit(10) + .map(player -> new LeaderboardEntry(player.getUsername(), player.getHighScore())) + .collect(Collectors.toList()); + + return new ResponseEntity<>(leaderboard, HttpStatus.OK); } } +// Request/Response DTOs @Data @NoArgsConstructor @AllArgsConstructor @@ -119,70 +147,4 @@ class PlayerLoginRequest { class ScoreUpdateRequest { private String username; private int score; -} - -@Service -class PlayerService implements UserDetailsService { - @Autowired - private GamerJpaRepository playerRepository; - - @Autowired - private PasswordEncoder passwordEncoder; - - @Override - public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) - throws UsernameNotFoundException { - Gamer player = playerRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("Player not found")); - - List authorities = List.of(new SimpleGrantedAuthority("ROLE_" + player.getRole())); - - return org.springframework.security.core.userdetails.User - .withUsername(player.getUsername()) - .password(player.getPassword()) - .authorities(authorities) - .accountLocked(!player.isEnabled()) - .build(); - } - - public Gamer registerPlayer(String username, String password) { - Gamer player = new Gamer(); - player.setUsername(username); - player.setPassword(passwordEncoder.encode(password)); - player.setHighScore(0); - return playerRepository.save(player); - } - - public Optional findByUsername(String username) { - return playerRepository.findByUsername(username); - } - - public boolean checkPassword(String rawPassword, String encodedPassword) { - return passwordEncoder.matches(rawPassword, encodedPassword); - } - - public void updateHighScore(String username, int score) { - Gamer player = playerRepository.findByUsername(username) - .orElseThrow(() -> new RuntimeException("Player not found")); - - // Only update if the new score is higher than the current high score - if (score > player.getHighScore()) { - player.setHighScore(score); - playerRepository.save(player); - } - } - - public int getHighScore(String username) { - Gamer player = playerRepository.findByUsername(username) - .orElseThrow(() -> new RuntimeException("Player not found")); - return player.getHighScore(); - } - - public List getTopPlayersByScore() { - return playerRepository.findAll().stream() - .sorted(Comparator.comparingInt(Gamer::getHighScore).reversed()) - .limit(10) - .map(player -> new LeaderboardEntry(player.getUsername(), player.getHighScore())) - .collect(Collectors.toList()); - } } \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java new file mode 100644 index 00000000..bbf5b884 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java @@ -0,0 +1,39 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Random; + +@Configuration +public class GamerInit { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Bean + CommandLineRunner initGamers(GamerJpaRepository repository) { + return args -> { + // Only initialize if database is empty + if (repository.count() == 0) { + String[] usernames = Gamer.init(); + Random random = new Random(); + + for (String username : usernames) { + Gamer player = new Gamer(); + player.setUsername(username); + player.setPassword(passwordEncoder.encode("password123")); // Default password + player.setRole("PLAYER"); + player.setEnabled(true); + player.setHighScore(random.nextInt(60000)); // Random score between 0-60000 + repository.save(player); + } + + System.out.println("Gamer database initialized with " + usernames.length + " players"); + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java index e7f854f1..6d0036bd 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java @@ -5,8 +5,12 @@ import org.springframework.data.jpa.repository.JpaRepository; +// JPA is an object-relational mapping (ORM) to persistent data, originally relational databases (SQL) public interface GamerJpaRepository extends JpaRepository { + /* JPA has many built in methods: https://www.tutorialspoint.com/spring_boot_jpa/spring_boot_jpa_repository_methods.htm + The below custom methods are prototyped for this application + */ Optional findByUsername(String username); List findByUsernameIgnoreCase(String username); - Gamer findById(Integer playerId); + List findAllByOrderByHighScoreDesc(); // Orders by high score descending } \ No newline at end of file From cc335b60074a09e9747a8d8949d8cff012833d90 Mon Sep 17 00:00:00 2001 From: Gurbop Date: Wed, 3 Dec 2025 11:32:31 -0800 Subject: [PATCH 10/23] (Unfinished)PlayerInnit --- .../spring/mvc/multiplayer/PlayerInit.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java new file mode 100644 index 00000000..5652335c --- /dev/null +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java @@ -0,0 +1,45 @@ +package com.open.spring.mvc.multiplayer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Random; + +@Configuration +public class PlayerInit { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Bean + CommandLineRunner initGamers(PlayerRepository repository) { + return args -> { + + if (repository.count() == 0) { + + String[] usernames = Player.init(); + Random random = new Random(); + + for (String username : usernames) { + + Player player = new Player(); + player.setUsername(username); + player.setPassword(passwordEncoder.encode("password123")); + player.setRole("PLAYER"); + player.setEnabled(true); + + String location = random.nextInt(1001) + "," + random.nextInt(1001); + player.setLocation(location); + + repository.save(player); + } + + System.out.println("Gamer database initialized with " + + usernames.length + " players (level + XY coords)"); + } + }; + } +} From 97fba92c74911d2542c1b9e8df479863223e9ddd Mon Sep 17 00:00:00 2001 From: Nora Ahadian Date: Thu, 4 Dec 2025 10:53:14 -0800 Subject: [PATCH 11/23] fix java version 2 run itt --- pom.xml | 4 +-- .../spring/mvc/multiplayer/PlayerInit.java | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 67aa6dee..06633316 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ spring Intro project for Spring Boot - 21 + 17 3.1.3.RELEASE 3.1.3.RELEASE @@ -280,7 +280,7 @@ maven-compiler-plugin 3.11.0 - 21 + 17 org.projectlombok diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java index 5652335c..9edd28be 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java @@ -1,38 +1,42 @@ package com.open.spring.mvc.multiplayer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.crypto.password.PasswordEncoder; +import java.time.LocalDateTime; import java.util.Random; @Configuration public class PlayerInit { - @Autowired - private PasswordEncoder passwordEncoder; - @Bean CommandLineRunner initGamers(PlayerRepository repository) { return args -> { if (repository.count() == 0) { - String[] usernames = Player.init(); + // create a set of default usernames + int count = 50; + String[] usernames = new String[count]; + for (int i = 0; i < count; i++) { + usernames[i] = "player" + (i + 1); + } + Random random = new Random(); for (String username : usernames) { - Player player = new Player(); player.setUsername(username); - player.setPassword(passwordEncoder.encode("password123")); - player.setRole("PLAYER"); - player.setEnabled(true); - - String location = random.nextInt(1001) + "," + random.nextInt(1001); - player.setLocation(location); + // set a default status and random position/level + player.setStatus("online"); + player.setLevel(random.nextInt(10) + 1); // levels 1-10 + double x = random.nextDouble() * 1000.0; + double y = random.nextDouble() * 1000.0; + player.setX(x); + player.setY(y); + player.setLastActive(LocalDateTime.now()); + player.setConnectedAt(LocalDateTime.now()); repository.save(player); } From 6006e970a3ba705631f6ab5160238bb2ac75e915 Mon Sep 17 00:00:00 2001 From: SoniDhenuva Date: Fri, 5 Dec 2025 10:22:37 -0800 Subject: [PATCH 12/23] tesing for multiplayer --- .../open/spring/mvc/multiplayer/Player.java | 37 ++++----- .../mvc/multiplayer/PlayerController.java | 83 +++++++++++++++---- .../mvc/multiplayer/PlayerRepository.java | 3 +- 3 files changed, 85 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/Player.java b/src/main/java/com/open/spring/mvc/multiplayer/Player.java index dccd7ccc..aaa68647 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/Player.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/Player.java @@ -12,12 +12,22 @@ @AllArgsConstructor @Table(name = "players") public class Player { + @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(nullable = false, unique = true) - private String username; + private String uid; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String email; + + @Column(nullable = true) + private String pfp; @Column(nullable = false) private String status; // "online" or "offline" @@ -28,27 +38,16 @@ public class Player { @Column(name = "connected_at") private LocalDateTime connectedAt; + private double x; + private double y; + private int level; + // Constructor for quick creation - public Player(String username, String status) { - this.username = username; + public Player(String uid, String name, String status) { + this.uid = uid; + this.name = name; this.status = status; this.lastActive = LocalDateTime.now(); this.connectedAt = LocalDateTime.now(); } - // In Player.java - -private double x; -private double y; -private int level; - -// getters and setters -public double getX() { return x; } -public void setX(double x) { this.x = x; } - -public double getY() { return y; } -public void setY(double y) { this.y = y; } - -public int getLevel() { return level; } -public void setLevel(int level) { this.level = level; } - } \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java index 39bdc4c7..a53b9786 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -1,6 +1,9 @@ package com.open.spring.mvc.multiplayer; +import com.open.spring.mvc.person.Person; +import com.open.spring.mvc.person.PersonJpaRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.List; @@ -10,19 +13,63 @@ @RequestMapping("/api/players") @CrossOrigin public class PlayerController { + @Autowired private PlayerRepository playerRepository; + + @Autowired + private PersonJpaRepository personRepository; + + // Get current logged-in user and add to player table + @GetMapping("/current") + public Player getCurrentUser() { + String currentUsername = SecurityContextHolder.getContext() + .getAuthentication().getName(); + Person person = personRepository.findByUid(currentUsername); + + if (person == null) { + throw new RuntimeException("User not found"); + } + + // Check if player already exists + Player player = playerRepository.findByUid(person.getUid()) + .orElse(new Player()); + + // Update or create player with person data + player.setUid(person.getUid()); + player.setName(person.getName()); + player.setEmail(person.getEmail()); + player.setPfp(person.getPfp()); + player.setStatus("online"); + player.setLastActive(LocalDateTime.now()); + if (player.getConnectedAt() == null) { + player.setConnectedAt(LocalDateTime.now()); + } + + return playerRepository.save(player); + } @PostMapping("/connect") public Player connect(@RequestBody Map request) { - String username = request.get("username"); + String uid = request.get("uid"); + + Person person = personRepository.findByUid(uid); + if (person == null) { + throw new RuntimeException("User not found in PersonJpaRepository"); + } - Player player = playerRepository.findByUsername(username) - .orElse(new Player(username, "online")); + Player player = playerRepository.findByUid(uid) + .orElse(new Player()); + player.setUid(uid); + player.setName(person.getName()); + player.setEmail(person.getEmail()); + player.setPfp(person.getPfp()); player.setStatus("online"); player.setLastActive(LocalDateTime.now()); - player.setConnectedAt(LocalDateTime.now()); + if (player.getConnectedAt() == null) { + player.setConnectedAt(LocalDateTime.now()); + } return playerRepository.save(player); } @@ -35,10 +82,10 @@ public Map getOnlinePlayers() { @PutMapping("/status") public Player updateStatus(@RequestBody Map request) { - String username = request.get("username"); + String uid = request.get("uid"); String status = request.get("status"); - Player player = playerRepository.findByUsername(username) + Player player = playerRepository.findByUid(uid) .orElseThrow(() -> new RuntimeException("Player not found")); player.setStatus(status); @@ -49,21 +96,22 @@ public Player updateStatus(@RequestBody Map request) { @PostMapping("/disconnect") public void disconnect(@RequestBody Map request) { - String username = request.get("username"); + String uid = request.get("uid"); - Player player = playerRepository.findByUsername(username) + Player player = playerRepository.findByUid(uid) .orElseThrow(() -> new RuntimeException("Player not found")); player.setStatus("offline"); playerRepository.save(player); } + @PutMapping("/location") public Player updateLocation(@RequestBody Map request) { - String username = (String) request.get("username"); + String uid = (String) request.get("uid"); double x = (double) request.get("x"); double y = (double) request.get("y"); - Player player = playerRepository.findByUsername(username) + Player player = playerRepository.findByUid(uid) .orElseThrow(() -> new RuntimeException("Player not found")); player.setX(x); @@ -72,31 +120,32 @@ public Player updateLocation(@RequestBody Map request) { return playerRepository.save(player); } + @PutMapping("/level") - public Player updateLevel(@RequestBody Map request) { - String username = (String) request.get("username"); + public Player updateLevel(@RequestBody Map request) { + String uid = (String) request.get("uid"); int level = (int) request.get("level"); - Player player = playerRepository.findByUsername(username) + Player player = playerRepository.findByUid(uid) .orElseThrow(() -> new RuntimeException("Player not found")); player.setLevel(level); player.setLastActive(LocalDateTime.now()); return playerRepository.save(player); - } + } - // for multiplayer locations if needed: @GetMapping("/locations") public Map getPlayerLocations() { List players = playerRepository.findByStatus("online"); return Map.of("players", players.stream().map(p -> Map.of( - "username", p.getUsername(), + "uid", p.getUid(), + "name", p.getName(), "x", p.getX(), "y", p.getY(), "level", p.getLevel() )).toList()); } +} -} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java index 23cc4f21..a316cb95 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerRepository.java @@ -7,9 +7,8 @@ @Repository public interface PlayerRepository extends JpaRepository { - Optional findByUsername(String username); + Optional findByUid(String uid); List findByStatus(String status); - // numerical queries List findByLevel(int level); List findByXBetweenAndYBetween(double x1, double x2, double y1, double y2); } \ No newline at end of file From 108bb54af7383029ec96cdabbe77c5d449c8f06d Mon Sep 17 00:00:00 2001 From: SoniDhenuva Date: Mon, 8 Dec 2025 09:55:10 -0800 Subject: [PATCH 13/23] player data stored in backend tbales --- .../open/spring/mvc/multiplayer/Player.java | 19 ++++----------- .../mvc/multiplayer/PlayerController.java | 12 +++------- .../spring/mvc/multiplayer/PlayerInit.java | 24 ++++++++----------- 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/open/spring/mvc/multiplayer/Player.java b/src/main/java/com/open/spring/mvc/multiplayer/Player.java index aaa68647..30811aca 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/Player.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/Player.java @@ -18,16 +18,7 @@ public class Player { private Long id; @Column(nullable = false, unique = true) - private String uid; - - @Column(nullable = false) - private String name; - - @Column(nullable = false) - private String email; - - @Column(nullable = true) - private String pfp; + private String uid; // username @Column(nullable = false) private String status; // "online" or "offline" @@ -42,12 +33,12 @@ public class Player { private double y; private int level; - // Constructor for quick creation - public Player(String uid, String name, String status) { + // Constructor + public Player(String uid, String status) { this.uid = uid; - this.name = name; this.status = status; this.lastActive = LocalDateTime.now(); this.connectedAt = LocalDateTime.now(); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java index a53b9786..efdd8b5d 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerController.java @@ -35,11 +35,8 @@ public Player getCurrentUser() { Player player = playerRepository.findByUid(person.getUid()) .orElse(new Player()); - // Update or create player with person data + // Update or create player with just username player.setUid(person.getUid()); - player.setName(person.getName()); - player.setEmail(person.getEmail()); - player.setPfp(person.getPfp()); player.setStatus("online"); player.setLastActive(LocalDateTime.now()); if (player.getConnectedAt() == null) { @@ -55,16 +52,13 @@ public Player connect(@RequestBody Map request) { Person person = personRepository.findByUid(uid); if (person == null) { - throw new RuntimeException("User not found in PersonJpaRepository"); + throw new RuntimeException("User not found"); } Player player = playerRepository.findByUid(uid) .orElse(new Player()); player.setUid(uid); - player.setName(person.getName()); - player.setEmail(person.getEmail()); - player.setPfp(person.getPfp()); player.setStatus("online"); player.setLastActive(LocalDateTime.now()); if (player.getConnectedAt() == null) { @@ -141,7 +135,6 @@ public Map getPlayerLocations() { return Map.of("players", players.stream().map(p -> Map.of( "uid", p.getUid(), - "name", p.getName(), "x", p.getX(), "y", p.getY(), "level", p.getLevel() @@ -149,3 +142,4 @@ public Map getPlayerLocations() { } } + diff --git a/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java index 9edd28be..9af6fc57 100644 --- a/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java +++ b/src/main/java/com/open/spring/mvc/multiplayer/PlayerInit.java @@ -16,25 +16,21 @@ CommandLineRunner initGamers(PlayerRepository repository) { if (repository.count() == 0) { - // create a set of default usernames int count = 50; - String[] usernames = new String[count]; + String[] uids = new String[count]; for (int i = 0; i < count; i++) { - usernames[i] = "player" + (i + 1); + uids[i] = "player" + (i + 1); } Random random = new Random(); - for (String username : usernames) { + for (String uid : uids) { Player player = new Player(); - player.setUsername(username); - // set a default status and random position/level - player.setStatus("online"); - player.setLevel(random.nextInt(10) + 1); // levels 1-10 - double x = random.nextDouble() * 1000.0; - double y = random.nextDouble() * 1000.0; - player.setX(x); - player.setY(y); + player.setUid(uid); + player.setStatus("offline"); + player.setLevel(random.nextInt(10) + 1); + player.setX(random.nextDouble() * 1000.0); + player.setY(random.nextDouble() * 1000.0); player.setLastActive(LocalDateTime.now()); player.setConnectedAt(LocalDateTime.now()); @@ -42,8 +38,8 @@ CommandLineRunner initGamers(PlayerRepository repository) { } System.out.println("Gamer database initialized with " - + usernames.length + " players (level + XY coords)"); + + uids.length + " players"); } }; } -} +} \ No newline at end of file From a0630428e5bb9445ffefa6626fce4825c8ed95ac Mon Sep 17 00:00:00 2001 From: Nora Ahadian Date: Mon, 8 Dec 2025 10:24:06 -0800 Subject: [PATCH 14/23] creating database table in backend for score counting --- .../mvc/PauseMenu/PauseMenuApiController.java | 103 ++++++++++ .../mvc/PauseMenu/PauseMenuController.java | 44 +++++ .../spring/mvc/PauseMenu/ScoreCounter.java | 27 +++ .../mvc/PauseMenu/ScorePauseMenuRepo.java | 17 ++ .../resources/static/js/pauseMenu/score.js | 183 ++++++++++++++++++ 5 files changed, 374 insertions(+) create mode 100644 src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java create mode 100644 src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuController.java create mode 100644 src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java create mode 100644 src/main/java/com/open/spring/mvc/PauseMenu/ScorePauseMenuRepo.java create mode 100644 src/main/resources/static/js/pauseMenu/score.js diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java new file mode 100644 index 00000000..823b9e3b --- /dev/null +++ b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java @@ -0,0 +1,103 @@ +package com.open.spring.mvc.PauseMenu; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import lombok.Data; +import java.util.List; +import java.util.Map; +import java.util.HashMap; + +/** + * API Controller for Pause Menu Score Management + */ +@RestController +@RequestMapping("/api/pausemenu/score") +public class PauseMenuApiController { + + @Autowired + private ScorePauseMenuRepo scoreRepository; + + /** + * DTO for receiving score data from the frontend + */ + @Data + public static class ScorePauseMenuRequest { + private String user; + private int score; + } + + /** + * Save a new score + * POST /api/pausemenu/score/save + */ + @PostMapping("/save") + public ResponseEntity> saveScore(@RequestBody ScorePauseMenuRequest request) { + try { + ScoreCounter newScore = new ScoreCounter(); + newScore.setUser(request.getUser()); + newScore.setScore(request.getScore()); + + ScoreCounter saved = scoreRepository.save(newScore); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("id", saved.getId()); + response.put("message", "Score saved successfully"); + + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "Error saving score: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } + + /** + * Get all scores + * GET /api/pausemenu/score/all + */ + @GetMapping("/all") + public ResponseEntity> getAllScores() { + List scores = scoreRepository.findAll(); + return ResponseEntity.ok(scores); + } + + /** + * Get scores for a specific user + * GET /api/pausemenu/score/user/{user} + */ + @GetMapping("/user/{user}") + public ResponseEntity> getScoresByUser(@PathVariable String user) { + List scores = scoreRepository.findByUser(user); + return ResponseEntity.ok(scores); + } + + /** + * Get a specific score by ID + * GET /api/pausemenu/score/{id} + */ + @GetMapping("/{id}") + public ResponseEntity getScoreById(@PathVariable Long id) { + return scoreRepository.findById(id) + .map(score -> ResponseEntity.ok((Object) score)) + .orElse(ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", "Score not found"))); + } + + /** + * Delete a score + * DELETE /api/pausemenu/score/{id} + */ + @DeleteMapping("/{id}") + public ResponseEntity> deleteScore(@PathVariable Long id) { + if (scoreRepository.existsById(id)) { + scoreRepository.deleteById(id); + return ResponseEntity.ok(Map.of("message", "Score deleted")); + } + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", "Score not found")); + } +} diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuController.java b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuController.java new file mode 100644 index 00000000..0e6b2840 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuController.java @@ -0,0 +1,44 @@ +package com.open.spring.mvc.PauseMenu; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; + +/** + * MVC Controller for displaying pause menu scores + */ +@Controller +@RequestMapping("/pausemenu") +public class PauseMenuController { + + @Autowired + private ScorePauseMenuRepo scoreRepository; + + /** + * Display all scores + * GET /pausemenu/scores + */ + @GetMapping("/scores") + public String showAllScores(Model model) { + List scores = scoreRepository.findAll(); + model.addAttribute("scores", scores); + model.addAttribute("pageTitle", "All Scores"); + return "pausemenu/scores-table"; + } + + /** + * Display scores for a specific user + * GET /pausemenu/scores?user=Username + */ + @GetMapping("/scores/user") + public String showUserScores(String user, Model model) { + List scores = scoreRepository.findByUser(user); + model.addAttribute("scores", scores); + model.addAttribute("pageTitle", "Scores for " + user); + model.addAttribute("userName", user); + return "pausemenu/scores-table"; + } +} diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java new file mode 100644 index 00000000..f91ddf4a --- /dev/null +++ b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java @@ -0,0 +1,27 @@ +package com.open.spring.mvc.PauseMenu; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * ScoreCounter Entity - Stores game scores + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "score_counter") +public class ScoreCounter { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = true) + private String user; + + @Column(nullable = false) + private int score; +} diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/ScorePauseMenuRepo.java b/src/main/java/com/open/spring/mvc/PauseMenu/ScorePauseMenuRepo.java new file mode 100644 index 00000000..e23f1f75 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/PauseMenu/ScorePauseMenuRepo.java @@ -0,0 +1,17 @@ +package com.open.spring.mvc.PauseMenu; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +/** + * Repository for ScoreCounter entity + */ +@Repository +public interface ScorePauseMenuRepo extends JpaRepository { + + /** + * Find all scores for a specific user + */ + List findByUser(String user); +} diff --git a/src/main/resources/static/js/pauseMenu/score.js b/src/main/resources/static/js/pauseMenu/score.js new file mode 100644 index 00000000..ae40e3e7 --- /dev/null +++ b/src/main/resources/static/js/pauseMenu/score.js @@ -0,0 +1,183 @@ +/** + * Score Management Module + * Handles saving, retrieving, and managing game scores + * Communicates with /api/score backend endpoints + */ + +class ScoreManager { + constructor(backendUrl = "http://localhost:8585") { + this.backendUrl = backendUrl; + this.apiEndpoint = `${this.backendUrl}/api/score`; + } + + /** + * Save a score to the database + * @param {string} personName - The player's name + * @param {number} score - The score to save + * @returns {Promise} - Response from the server + */ + async saveScore(personName, score) { + try { + const response = await fetch(`${this.apiEndpoint}/save`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + personName: personName, + score: parseInt(score) + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log("Score saved successfully:", data); + return data; + } catch (error) { + console.error("Error saving score:", error); + throw error; + } + } + + /** + * Get all scores for a specific player + * @param {string} personName - The player's name + * @returns {Promise} - Array of scores for that player + */ + async getPlayerScores(personName) { + try { + const response = await fetch(`${this.apiEndpoint}/person/${personName}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log(`Scores for ${personName}:`, data); + return data; + } catch (error) { + console.error("Error fetching player scores:", error); + throw error; + } + } + + /** + * Get all scores in the database + * @returns {Promise} - Array of all scores + */ + async getAllScores() { + try { + const response = await fetch(`${this.apiEndpoint}/all`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log("All scores:", data); + return data; + } catch (error) { + console.error("Error fetching all scores:", error); + throw error; + } + } + + /** + * Get a score by ID + * @param {number} scoreId - The score ID + * @returns {Promise} - Score object + */ + async getScoreById(scoreId) { + try { + const response = await fetch(`${this.apiEndpoint}/${scoreId}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log("Score found:", data); + return data; + } catch (error) { + console.error("Error fetching score:", error); + throw error; + } + } + + /** + * Delete a score by ID + * @param {number} scoreId - The score ID to delete + * @returns {Promise} - Response message + */ + async deleteScore(scoreId) { + try { + const response = await fetch(`${this.apiEndpoint}/${scoreId}`, { + method: "DELETE" + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const message = await response.text(); + console.log("Score deleted:", message); + return message; + } catch (error) { + console.error("Error deleting score:", error); + throw error; + } + } + + /** + * Display scores on the page (helper function) + * @param {Array} scores - Array of score objects + * @param {string} containerId - ID of the HTML element to display scores in + */ + displayScores(scores, containerId = "scores-container") { + const container = document.getElementById(containerId); + if (!container) { + console.error(`Container with ID '${containerId}' not found`); + return; + } + + if (scores.length === 0) { + container.innerHTML = "

No scores found.

"; + return; + } + + let html = ""; + scores.forEach(score => { + html += ``; + }); + html += "
IDPlayerScore
${score.id}${score.personName}${score.score}
"; + + container.innerHTML = html; + } + + /** + * Get the highest score from an array of scores + * @param {Array} scores - Array of score objects + * @returns {Object|null} - Highest score object or null if empty + */ + getHighestScore(scores) { + if (!scores || scores.length === 0) return null; + return scores.reduce((max, score) => (score.score > max.score) ? score : max); + } + + /** + * Get the average score from an array of scores + * @param {Array} scores - Array of score objects + * @returns {number} - Average score + */ + getAverageScore(scores) { + if (!scores || scores.length === 0) return 0; + const sum = scores.reduce((acc, score) => acc + score.score, 0); + return (sum / scores.length).toFixed(2); + } +} + +// Create a global instance for easy access +const scoreManager = new ScoreManager(); From c0951cf1ea4a52c525c7443af2bd8d0faf20e936 Mon Sep 17 00:00:00 2001 From: Nora Ahadian Date: Mon, 15 Dec 2025 19:57:05 -0800 Subject: [PATCH 15/23] make authenticate not required fix connection to backend (saves in score_counter table) --- .../mvc/PauseMenu/GamerScoreController.java | 56 +++++++++++++++++++ .../mvc/PauseMenu/PauseMenuApiController.java | 46 ++++++++++++++- .../open/spring/mvc/grades/DataLoader.java | 30 +++++----- .../open/spring/security/SecurityConfig.java | 4 ++ 4 files changed, 122 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java new file mode 100644 index 00000000..d1a186c8 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java @@ -0,0 +1,56 @@ +package com.open.spring.mvc.PauseMenu; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import java.util.HashMap; +import java.util.Map; + +/** + * Lightweight controller to handle gamer score submissions from the frontend. + * Exposed at /api/gamer/score (public, no auth required). + */ +@RestController +@RequestMapping("/api/pausemenu") +public class GamerScoreController { + + @Autowired + private ScorePauseMenuRepo scoreRepository; + + public static class GamerScoreRequest { + public String user; + public Integer score; + } + + @PostMapping("/score") + public ResponseEntity> saveGamerScore(@RequestBody GamerScoreRequest payload) { + try { + int score = payload != null && payload.score != null ? payload.score : 0; + String user = payload != null ? payload.user : null; + if (user == null || user.trim().isEmpty()) { + user = "guest"; + } + + ScoreCounter newScore = new ScoreCounter(); + newScore.setUser(user); + newScore.setScore(score); + + ScoreCounter saved = scoreRepository.save(newScore); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("id", saved.getId()); + response.put("message", "Score saved successfully"); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "Error saving score: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } +} diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java index 823b9e3b..33f35d2c 100644 --- a/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java +++ b/src/main/java/com/open/spring/mvc/PauseMenu/PauseMenuApiController.java @@ -36,7 +36,12 @@ public static class ScorePauseMenuRequest { public ResponseEntity> saveScore(@RequestBody ScorePauseMenuRequest request) { try { ScoreCounter newScore = new ScoreCounter(); - newScore.setUser(request.getUser()); + // default to "guest" when user is missing or blank + String user = request.getUser(); + if (user == null || user.trim().isEmpty()) { + user = "guest"; + } + newScore.setUser(user); newScore.setScore(request.getScore()); ScoreCounter saved = scoreRepository.save(newScore); @@ -100,4 +105,43 @@ public ResponseEntity> deleteScore(@PathVariable Long id) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(Map.of("error", "Score not found")); } + + /** + * Alternate endpoint used by frontend + * POST /api/gamer/score + * Accepts JSON: { "score": number, "user": string? } + * If user is missing, defaults to "guest" + */ + @PostMapping(path = "/api/gamer/score", consumes = "application/json") + public ResponseEntity> saveGamerScore(@RequestBody Map payload) { + try { + int score = 0; + Object scoreObj = payload.get("score"); + if (scoreObj instanceof Number) { + score = ((Number) scoreObj).intValue(); + } + + String user = (payload.get("user") instanceof String) ? (String) payload.get("user") : null; + if (user == null || user.trim().isEmpty()) { + user = "guest"; + } + + ScoreCounter newScore = new ScoreCounter(); + newScore.setUser(user); + newScore.setScore(score); + + ScoreCounter saved = scoreRepository.save(newScore); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("id", saved.getId()); + response.put("message", "Score saved successfully"); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } catch (Exception e) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", "Error saving score: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } } diff --git a/src/main/java/com/open/spring/mvc/grades/DataLoader.java b/src/main/java/com/open/spring/mvc/grades/DataLoader.java index a24b546b..044ef9b1 100644 --- a/src/main/java/com/open/spring/mvc/grades/DataLoader.java +++ b/src/main/java/com/open/spring/mvc/grades/DataLoader.java @@ -19,20 +19,24 @@ public class DataLoader implements CommandLineRunner { @Override public void run(String... args) throws Exception { - // Sample Grades - gradeRepository.save(new Grade("STU001", "Mathematics", 85.5, "A", "submission link")); - gradeRepository.save(new Grade("STU001", "Science", 92.0, "A+", "submission link")); - gradeRepository.save(new Grade("STU002", "Mathematics", 78.0, "B+", "submission link")); - gradeRepository.save(new Grade("STU002", "English", 88.5, "A-", "submission link")); - gradeRepository.save(new Grade("STU003", "Science", 95.0, "A+", "submission link")); + try { + // Sample Grades + gradeRepository.save(new Grade("STU001", "Mathematics", 85.5, "A", "submission link")); + gradeRepository.save(new Grade("STU001", "Science", 92.0, "A+", "submission link")); + gradeRepository.save(new Grade("STU002", "Mathematics", 78.0, "B+", "submission link")); + gradeRepository.save(new Grade("STU002", "English", 88.5, "A-", "submission link")); + gradeRepository.save(new Grade("STU003", "Science", 95.0, "A+", "submission link")); - // Sample Progress - progressRepository.save(new Progress("STU001", "Mathematics", 75.0, "In Progress")); - progressRepository.save(new Progress("STU001", "Science", 100.0, "Completed")); - progressRepository.save(new Progress("STU002", "Mathematics", 60.0, "In Progress")); - progressRepository.save(new Progress("STU002", "English", 90.0, "Completed")); - progressRepository.save(new Progress("STU003", "Science", 45.0, "In Progress")); + // Sample Progress + progressRepository.save(new Progress("STU001", "Mathematics", 75.0, "In Progress")); + progressRepository.save(new Progress("STU001", "Science", 100.0, "Completed")); + progressRepository.save(new Progress("STU002", "Mathematics", 60.0, "In Progress")); + progressRepository.save(new Progress("STU002", "English", 90.0, "Completed")); + progressRepository.save(new Progress("STU003", "Science", 45.0, "In Progress")); - System.out.println("Sample data loaded successfully!"); + System.out.println("Sample data loaded successfully!"); + } catch (Exception e) { + System.err.println("Grades DataLoader skipped due to database schema mismatch: " + e.getMessage()); + } } } diff --git a/src/main/java/com/open/spring/security/SecurityConfig.java b/src/main/java/com/open/spring/security/SecurityConfig.java index 87d3897d..9dd7760d 100644 --- a/src/main/java/com/open/spring/security/SecurityConfig.java +++ b/src/main/java/com/open/spring/security/SecurityConfig.java @@ -83,6 +83,10 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce // ========== PUBLIC API ENDPOINTS ========== // Intentionally public - used for polling and public features .requestMatchers("/api/jokes/**").permitAll() + // Pause Menu APIs should be public + .requestMatchers("/api/pausemenu/**").permitAll() + // Frontend calls gamer score endpoint; make it public + .requestMatchers("/api/gamer/**").permitAll() // ========================================== .requestMatchers("/api/exports/**").permitAll() .requestMatchers("/api/imports/**").permitAll() From c8bc78f303c3d2ddf78f37f754807116fd9054c4 Mon Sep 17 00:00:00 2001 From: Nora Ahadian Date: Mon, 15 Dec 2025 22:31:00 -0800 Subject: [PATCH 16/23] add collum for gameName --- .../com/open/spring/mvc/PauseMenu/GamerScoreController.java | 6 ++++++ .../java/com/open/spring/mvc/PauseMenu/ScoreCounter.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java index d1a186c8..0820c058 100644 --- a/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java +++ b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java @@ -24,6 +24,7 @@ public class GamerScoreController { public static class GamerScoreRequest { public String user; public Integer score; + public String gameName; } @PostMapping("/score") @@ -34,10 +35,15 @@ public ResponseEntity> saveGamerScore(@RequestBody GamerScor if (user == null || user.trim().isEmpty()) { user = "guest"; } + String gameName = payload != null ? payload.gameName : null; + if (gameName == null || gameName.trim().isEmpty()) { + gameName = "unknown"; + } ScoreCounter newScore = new ScoreCounter(); newScore.setUser(user); newScore.setScore(score); + newScore.setGameName(gameName); ScoreCounter saved = scoreRepository.save(newScore); diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java index f91ddf4a..56f568a0 100644 --- a/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java +++ b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java @@ -24,4 +24,7 @@ public class ScoreCounter { @Column(nullable = false) private int score; + + @Column(nullable = true) + private String gameName; } From bf0141f60921e32b07b85b07b170433e5ffa3450 Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Mon, 15 Dec 2025 23:53:37 -0800 Subject: [PATCH 17/23] new leaderboard table --- .../open/spring/mvc/leaderboard/Gamer.java | 45 ----- .../mvc/leaderboard/GamerApiController.java | 150 --------------- .../spring/mvc/leaderboard/GamerInit.java | 39 ---- .../mvc/leaderboard/GamerJpaRepository.java | 16 -- .../leaderboard/LeaderboardController.java | 176 ++++++++++++++++++ .../mvc/leaderboard/LeaderboardEntry.java | 104 +++++++++++ .../leaderboard/LeaderboardRepository.java | 43 +++++ .../mvc/leaderboard/LeaderboardService.java | 158 ++++++++++++++++ 8 files changed, 481 insertions(+), 250 deletions(-) delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/Gamer.java delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java b/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java deleted file mode 100644 index 4becf1e3..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/Gamer.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -@Entity -public class Gamer { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - private Long id; - - @Column(unique = true, nullable = false) - private String username; - - @Column(nullable = false) - private String password; - - private String role; - private boolean enabled; - - @Column(nullable = false) - private int highScore; - - // Starting players for initialization - public static String[] init() { - final String[] playersArray = { - "Avika", - "Soni", - "Nora", - "Gurshawn", - "Xavier", - "Spencer" - }; - return playersArray; - } -} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java deleted file mode 100644 index 7cd2c9e1..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/GamerApiController.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.*; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@RestController // Changed from @Controller to @RestController -@RequestMapping("/api/gamer") // Changed to /api/gamer to follow REST conventions -public class GamerApiController { - - @Autowired - private GamerJpaRepository repository; - - @Autowired - private PasswordEncoder passwordEncoder; - - /* GET List of All Players - * @GetMapping annotation is used for mapping HTTP GET requests onto specific handler methods. - */ - @GetMapping("/") - public ResponseEntity> getPlayers() { - return new ResponseEntity<>(repository.findAll(), HttpStatus.OK); - } - - /* POST Register New Player - */ - @PostMapping("/register") - public ResponseEntity registerPlayer(@RequestBody PlayerRegistrationRequest request) { - // Check if username already exists - Optional existingPlayer = repository.findByUsername(request.getUsername()); - if (existingPlayer.isPresent()) { - return new ResponseEntity<>("Username already exists!", HttpStatus.BAD_REQUEST); - } - - // Create new player - Gamer player = new Gamer(); - player.setUsername(request.getUsername()); - player.setPassword(passwordEncoder.encode(request.getPassword())); - player.setRole("PLAYER"); - player.setEnabled(true); - player.setHighScore(0); - - repository.save(player); - return new ResponseEntity<>("Player registered successfully!", HttpStatus.OK); - } - - /* POST Login Player - */ - @PostMapping("/login") - public ResponseEntity loginPlayer(@RequestBody PlayerLoginRequest request) { - if (request.getPassword() == null || request.getPassword().isEmpty()) { - return new ResponseEntity<>("Password cannot be empty!", HttpStatus.BAD_REQUEST); - } - - Optional playerOptional = repository.findByUsername(request.getUsername()); - if (playerOptional.isPresent()) { - Gamer player = playerOptional.get(); - if (passwordEncoder.matches(request.getPassword(), player.getPassword())) { - return new ResponseEntity<>("Login successful!", HttpStatus.OK); - } - } - return new ResponseEntity<>("Invalid username or password!", HttpStatus.UNAUTHORIZED); - } - - /* POST Update Player Score - * @PathVariable annotation can be used if you want to pass username in URL - */ - @PostMapping("/score") - public ResponseEntity updateScore(@RequestBody ScoreUpdateRequest request) { - Optional optional = repository.findByUsername(request.getUsername()); - if (optional.isPresent()) { - Gamer player = optional.get(); - // Only update if the new score is higher than the current high score - if (request.getScore() > player.getHighScore()) { - player.setHighScore(request.getScore()); - repository.save(player); - } - return new ResponseEntity<>(player, HttpStatus.OK); - } - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - - /* GET Player Score by Username - */ - @GetMapping("/score/{username}") - public ResponseEntity getScore(@PathVariable String username) { - Optional optional = repository.findByUsername(username); - if (optional.isPresent()) { - return new ResponseEntity<>(optional.get().getHighScore(), HttpStatus.OK); - } - return new ResponseEntity<>(HttpStatus.NOT_FOUND); - } - - /* GET Leaderboard - Top 10 Players - */ - @GetMapping("/leaderboard") - public ResponseEntity> getLeaderboard() { - List leaderboard = repository.findAll().stream() - .sorted(Comparator.comparingInt(Gamer::getHighScore).reversed()) - .limit(10) - .map(player -> new LeaderboardEntry(player.getUsername(), player.getHighScore())) - .collect(Collectors.toList()); - - return new ResponseEntity<>(leaderboard, HttpStatus.OK); - } -} - -// Request/Response DTOs -@Data -@NoArgsConstructor -@AllArgsConstructor -class LeaderboardEntry { - private String username; - private int highScore; -} - -@Data -@NoArgsConstructor -@AllArgsConstructor -class PlayerRegistrationRequest { - private String username; - private String password; -} - -@Data -@NoArgsConstructor -@AllArgsConstructor -class PlayerLoginRequest { - private String username; - private String password; -} - -@Data -@NoArgsConstructor -@AllArgsConstructor -class ScoreUpdateRequest { - private String username; - private int score; -} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java deleted file mode 100644 index bbf5b884..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/GamerInit.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.crypto.password.PasswordEncoder; - -import java.util.Random; - -@Configuration -public class GamerInit { - - @Autowired - private PasswordEncoder passwordEncoder; - - @Bean - CommandLineRunner initGamers(GamerJpaRepository repository) { - return args -> { - // Only initialize if database is empty - if (repository.count() == 0) { - String[] usernames = Gamer.init(); - Random random = new Random(); - - for (String username : usernames) { - Gamer player = new Gamer(); - player.setUsername(username); - player.setPassword(passwordEncoder.encode("password123")); // Default password - player.setRole("PLAYER"); - player.setEnabled(true); - player.setHighScore(random.nextInt(60000)); // Random score between 0-60000 - repository.save(player); - } - - System.out.println("Gamer database initialized with " + usernames.length + " players"); - } - }; - } -} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java deleted file mode 100644 index 6d0036bd..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/GamerJpaRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import java.util.List; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -// JPA is an object-relational mapping (ORM) to persistent data, originally relational databases (SQL) -public interface GamerJpaRepository extends JpaRepository { - /* JPA has many built in methods: https://www.tutorialspoint.com/spring_boot_jpa/spring_boot_jpa_repository_methods.htm - The below custom methods are prototyped for this application - */ - Optional findByUsername(String username); - List findByUsernameIgnoreCase(String username); - List findAllByOrderByHighScoreDesc(); // Orders by high score descending -} \ 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 new file mode 100644 index 00000000..10d342f3 --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java @@ -0,0 +1,176 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +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.PostMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import lombok.Data; +import java.util.List; + +/** + * API Controller for Leaderboard Management + */ +@RestController +@RequestMapping("/api/leaderboard") +@CrossOrigin(origins = "*") +public class LeaderboardController { + + @Autowired + private LeaderboardService leaderboardService; + + /** + * CREATE - Add a new leaderboard entry + * POST /api/leaderboard + */ + @PostMapping + public ResponseEntity createEntry(@RequestBody LeaderboardEntryRequest request) { + LeaderboardEntry entry = leaderboardService.addEntry( + request.getUser(), + request.getGameName(), + request.getScore() + ); + return new ResponseEntity<>(entry, HttpStatus.CREATED); + } + + /** + * READ - Get all leaderboard entries ordered by score + * GET /api/leaderboard + */ + @GetMapping + public ResponseEntity> getAllEntries() { + List entries = leaderboardService.getAllEntriesByScore(); + return ResponseEntity.ok(entries); + } + + /** + * READ - Get a single leaderboard entry by user and game + * GET /api/leaderboard/{user}/{gameName} + */ + @GetMapping("/{user}/{gameName}") + public ResponseEntity getEntryByUserAndGame( + @PathVariable String user, + @PathVariable String gameName) { + LeaderboardEntry entry = leaderboardService.getEntryByUserAndGame(user, gameName); + return ResponseEntity.ok(entry); + } + + /** + * READ - Get top N scores + * GET /api/leaderboard/top/{limit} + */ + @GetMapping("/top/{limit}") + public ResponseEntity> getTopScores(@PathVariable int limit) { + List entries = leaderboardService.getTopScores(limit); + return ResponseEntity.ok(entries); + } + + /** + * READ - Get leaderboard entries for a specific game + * GET /api/leaderboard/game/{gameName} + */ + @GetMapping("/game/{gameName}") + public ResponseEntity> getEntriesByGame(@PathVariable String gameName) { + List entries = leaderboardService.getEntriesByGame(gameName); + return ResponseEntity.ok(entries); + } + + /** + * READ - Get leaderboard entries for a specific user + * GET /api/leaderboard/user/{user} + */ + @GetMapping("/user/{user}") + public ResponseEntity> getUserEntries(@PathVariable String user) { + List entries = leaderboardService.getUserEntries(user); + return ResponseEntity.ok(entries); + } + + /** + * READ - Get entries for a specific user and game + * GET /api/leaderboard/user/{user}/game/{gameName} + */ + @GetMapping("/user/{user}/game/{gameName}") + public ResponseEntity> getUserGameEntries( + @PathVariable String user, + @PathVariable String gameName) { + List entries = leaderboardService.getUserGameEntries(user, gameName); + return ResponseEntity.ok(entries); + } + + /** + * UPDATE - Update an existing leaderboard entry + * PUT /api/leaderboard/{user}/{gameName} + */ + @PutMapping("/{user}/{gameName}") + public ResponseEntity updateEntry( + @PathVariable String user, + @PathVariable String gameName, + @RequestBody LeaderboardEntryRequest request) { + LeaderboardEntry updated = leaderboardService.updateEntry( + user, + gameName, + request.getScore() + ); + return ResponseEntity.ok(updated); + } + + /** + * DELETE - Delete a leaderboard entry by user and game + * DELETE /api/leaderboard/{user}/{gameName} + */ + @DeleteMapping("/{user}/{gameName}") + public ResponseEntity deleteEntry( + @PathVariable String user, + @PathVariable String gameName) { + leaderboardService.deleteEntry(user, gameName); + return ResponseEntity.noContent().build(); + } + + /** + * DELETE - Delete all leaderboard entries + * DELETE /api/leaderboard + */ + @DeleteMapping + public ResponseEntity deleteAllEntries() { + leaderboardService.deleteAllEntries(); + return ResponseEntity.noContent().build(); + } + + /** + * REFRESH - Refresh entire leaderboard from score tables + * POST /api/leaderboard/refresh + */ + @PostMapping("/refresh") + public ResponseEntity refreshLeaderboard(@RequestParam(defaultValue = "100") int topN) { + leaderboardService.refreshLeaderboard(topN); + return ResponseEntity.ok("Leaderboard refreshed with top " + topN + " scores"); + } + + /** + * REFRESH - Refresh leaderboard for a specific game + * POST /api/leaderboard/refresh/game/{gameName} + */ + @PostMapping("/refresh/game/{gameName}") + public ResponseEntity refreshLeaderboardForGame( + @PathVariable String gameName, + @RequestParam(defaultValue = "100") int topN) { + leaderboardService.refreshLeaderboardForGame(gameName, topN); + return ResponseEntity.ok("Leaderboard refreshed for game '" + gameName + "' with top " + topN + " scores"); + } + + // DTO for request body + @Data + public static class LeaderboardEntryRequest { + private String user; + private String gameName; + private Integer score; + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java new file mode 100644 index 00000000..d97c53bf --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java @@ -0,0 +1,104 @@ +package com.open.spring.mvc.leaderboard; + +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Id; +import jakarta.persistence.Column; +import jakarta.persistence.IdClass; +import java.io.Serializable; +import java.util.Objects; + +@Entity +@Table(name = "leaderboard") +@IdClass(LeaderboardEntry.LeaderboardId.class) +public class LeaderboardEntry { + + @Id + @Column(name = "user", nullable = false) + private String user; + + @Id + @Column(name = "game_name", nullable = false) + private String gameName; + + @Column(name = "score", nullable = false) + private Integer score; + + // Constructors + public LeaderboardEntry() {} + + public LeaderboardEntry(String user, String gameName, Integer score) { + this.user = user; + this.gameName = gameName; + this.score = score; + } + + // Getters and Setters + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getGameName() { + return gameName; + } + + public void setGameName(String gameName) { + this.gameName = gameName; + } + + public Integer getScore() { + return score; + } + + public void setScore(Integer score) { + this.score = score; + } + + // Composite Primary Key Class + public static class LeaderboardId implements Serializable { + private String user; + private String gameName; + + public LeaderboardId() {} + + public LeaderboardId(String user, String gameName) { + this.user = user; + this.gameName = gameName; + } + + // Getters and Setters + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getGameName() { + return gameName; + } + + public void setGameName(String gameName) { + this.gameName = gameName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LeaderboardId that = (LeaderboardId) o; + return Objects.equals(user, that.user) && + Objects.equals(gameName, that.gameName); + } + + @Override + public int hashCode() { + return Objects.hash(user, gameName); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java new file mode 100644 index 00000000..6c88786d --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java @@ -0,0 +1,43 @@ +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 +public interface LeaderboardRepository extends JpaRepository { + + // Find all entries ordered by score descending + List findAllByOrderByScoreDesc(); + + // Find top N scores + @Query("SELECT l FROM LeaderboardEntry l ORDER BY l.score DESC") + List findTopScores(); + + // Find leaderboard entries for a specific user + List findByUserOrderByScoreDesc(String user); + + // Find leaderboard entries for a specific game + List findByGameNameOrderByScoreDesc(String gameName); + + // Find entries for a specific user and game + List findByUserAndGameNameOrderByScoreDesc(String user, String gameName); + + // Custom query to populate leaderboard from existing ScoreCounter table + // This pulls user, game name, and score from your pause menu scores + @Query(value = "SELECT sc.user as user_name, sc.game_name as game_name, sc.score " + + "FROM score_counter sc " + + "ORDER BY sc.score DESC " + + "LIMIT :limit", nativeQuery = true) + List getTopScoresFromScoreTables(@Param("limit") int limit); + + // Get top scores for a specific game + @Query(value = "SELECT sc.user as user_name, sc.game_name as game_name, sc.score " + + "FROM score_counter sc " + + "WHERE sc.game_name = :gameName " + + "ORDER BY sc.score DESC " + + "LIMIT :limit", nativeQuery = true) + List getTopScoresForGame(@Param("gameName") String gameName, @Param("limit") int limit); +} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java new file mode 100644 index 00000000..c37896ad --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java @@ -0,0 +1,158 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class LeaderboardService { + + @Autowired + private LeaderboardRepository leaderboardRepository; + + /** + * Get top N entries from the leaderboard + */ + public List getTopScores(int limit) { + return leaderboardRepository.findTopScores() + .stream() + .limit(limit) + .collect(Collectors.toList()); + } + + /** + * Get all leaderboard entries ordered by score + */ + public List getAllEntriesByScore() { + return leaderboardRepository.findAllByOrderByScoreDesc(); + } + + /** + * Get leaderboard entries for a specific game + */ + public List getEntriesByGame(String gameName) { + return leaderboardRepository.findByGameNameOrderByScoreDesc(gameName); + } + + /** + * Refresh leaderboard from the scores table + * This pulls from your ScoreCounter table (pause menu scores) + */ + @Transactional + public void refreshLeaderboard(int topN) { + // Clear existing leaderboard + leaderboardRepository.deleteAll(); + + // Get top scores from ScoreCounter table + List topScores = leaderboardRepository.getTopScoresFromScoreTables(topN); + + // Convert to LeaderboardEntry objects and save + List entries = topScores.stream() + .map(row -> new LeaderboardEntry( + (String) row[0], // user (from ScoreCounter) + (String) row[1], // game_name (from ScoreCounter) + (Integer) row[2] // score (from ScoreCounter) + )) + .collect(Collectors.toList()); + + leaderboardRepository.saveAll(entries); + } + + /** + * Refresh leaderboard for a specific game + */ + @Transactional + public void refreshLeaderboardForGame(String gameName, int topN) { + // Delete existing entries for this game + List existingEntries = leaderboardRepository.findByGameNameOrderByScoreDesc(gameName); + leaderboardRepository.deleteAll(existingEntries); + + // Get top scores for this game from ScoreCounter + List topScores = leaderboardRepository.getTopScoresForGame(gameName, topN); + + // Convert to LeaderboardEntry objects and save + List entries = topScores.stream() + .map(row -> new LeaderboardEntry( + (String) row[0], // user (from ScoreCounter) + (String) row[1], // game_name (from ScoreCounter) + (Integer) row[2] // score (from ScoreCounter) + )) + .collect(Collectors.toList()); + + leaderboardRepository.saveAll(entries); + } + + /** + * Add a single entry to the leaderboard + */ + public LeaderboardEntry addEntry(String user, String gameName, Integer score) { + LeaderboardEntry entry = new LeaderboardEntry(user, gameName, score); + return leaderboardRepository.save(entry); + } + + /** + * Get leaderboard entries for a specific user + */ + public List getUserEntries(String user) { + return leaderboardRepository.findByUserOrderByScoreDesc(user); + } + + /** + * Get entries for a specific user and game + */ + public List getUserGameEntries(String user, String gameName) { + return leaderboardRepository.findByUserAndGameNameOrderByScoreDesc(user, gameName); + } + + /** + * Get all leaderboard entries + */ + public List getAllEntries() { + return leaderboardRepository.findAll(); + } + + /** + * Get a single leaderboard entry by user and game + */ + public LeaderboardEntry getEntryByUserAndGame(String user, String gameName) { + LeaderboardEntry.LeaderboardId id = new LeaderboardEntry.LeaderboardId(user, gameName); + return leaderboardRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Leaderboard entry not found for user: " + user + ", game: " + gameName)); + } + + /** + * Update an existing leaderboard entry + */ + @Transactional + public LeaderboardEntry updateEntry(String user, String gameName, Integer score) { + LeaderboardEntry entry = getEntryByUserAndGame(user, gameName); + + if (score != null) { + entry.setScore(score); + } + + return leaderboardRepository.save(entry); + } + + /** + * Delete a leaderboard entry by user and game + */ + @Transactional + public void deleteEntry(String user, String gameName) { + LeaderboardEntry.LeaderboardId id = new LeaderboardEntry.LeaderboardId(user, gameName); + if (!leaderboardRepository.existsById(id)) { + throw new RuntimeException("Leaderboard entry not found for user: " + user + ", game: " + gameName); + } + leaderboardRepository.deleteById(id); + } + + /** + * Delete all leaderboard entries + */ + @Transactional + public void deleteAllEntries() { + leaderboardRepository.deleteAll(); + } +} \ No newline at end of file From bc5522b387181859356d8f3dcde708677d214a9b Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Tue, 16 Dec 2025 00:29:08 -0800 Subject: [PATCH 18/23] added CORS stuff to controller --- .../com/open/spring/mvc/leaderboard/LeaderboardController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 10d342f3..51b11cfe 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java @@ -21,7 +21,7 @@ */ @RestController @RequestMapping("/api/leaderboard") -@CrossOrigin(origins = "*") +@CrossOrigin(origins = "*", allowedHeaders = "*") public class LeaderboardController { @Autowired From d1f7ac3075e57136c44cbf62c33eabd73ce60829 Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Tue, 16 Dec 2025 07:18:11 -0800 Subject: [PATCH 19/23] getting rid of cors errors --- .../spring/mvc/leaderboard/CorsConfig.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java b/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java new file mode 100644 index 00000000..949de6ec --- /dev/null +++ b/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java @@ -0,0 +1,29 @@ +package com.open.spring.mvc.leaderboard; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins( + "http://localhost:4500", + "http://127.0.0.1:4500", + "http://localhost:4200", + "http://127.0.0.1:4200" + ) + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } + }; + } +} From 7497054a2bfe8c84c052778e2a213d8ed966ec1a Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Tue, 16 Dec 2025 07:21:56 -0800 Subject: [PATCH 20/23] trying to fix mismatch error --- .../leaderboard/LeaderboardController.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) 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 51b11cfe..20a00871 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java @@ -4,7 +4,7 @@ import org.springframework.http.HttpStatus; 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.RequestMapping; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -20,7 +20,6 @@ * API Controller for Leaderboard Management */ @RestController -@RequestMapping("/api/leaderboard") @CrossOrigin(origins = "*", allowedHeaders = "*") public class LeaderboardController { @@ -31,7 +30,7 @@ public class LeaderboardController { * CREATE - Add a new leaderboard entry * POST /api/leaderboard */ - @PostMapping + @PostMapping("/api/leaderboard") public ResponseEntity createEntry(@RequestBody LeaderboardEntryRequest request) { LeaderboardEntry entry = leaderboardService.addEntry( request.getUser(), @@ -43,9 +42,10 @@ public ResponseEntity createEntry(@RequestBody LeaderboardEntr /** * READ - Get all leaderboard entries ordered by score - * GET /api/leaderboard + * GET /api/leaderboard (primary endpoint) + * GET /api/pausemenu/score/leaderboard (alias for compatibility) */ - @GetMapping + @GetMapping({"/api/leaderboard", "/api/pausemenu/score/leaderboard"}) public ResponseEntity> getAllEntries() { List entries = leaderboardService.getAllEntriesByScore(); return ResponseEntity.ok(entries); @@ -55,7 +55,7 @@ public ResponseEntity> getAllEntries() { * READ - Get a single leaderboard entry by user and game * GET /api/leaderboard/{user}/{gameName} */ - @GetMapping("/{user}/{gameName}") + @GetMapping("/api/leaderboard/{user}/{gameName}") public ResponseEntity getEntryByUserAndGame( @PathVariable String user, @PathVariable String gameName) { @@ -67,7 +67,7 @@ public ResponseEntity getEntryByUserAndGame( * READ - Get top N scores * GET /api/leaderboard/top/{limit} */ - @GetMapping("/top/{limit}") + @GetMapping("/api/leaderboard/top/{limit}") public ResponseEntity> getTopScores(@PathVariable int limit) { List entries = leaderboardService.getTopScores(limit); return ResponseEntity.ok(entries); @@ -77,7 +77,7 @@ public ResponseEntity> getTopScores(@PathVariable int lim * READ - Get leaderboard entries for a specific game * GET /api/leaderboard/game/{gameName} */ - @GetMapping("/game/{gameName}") + @GetMapping("/api/leaderboard/game/{gameName}") public ResponseEntity> getEntriesByGame(@PathVariable String gameName) { List entries = leaderboardService.getEntriesByGame(gameName); return ResponseEntity.ok(entries); @@ -87,7 +87,7 @@ public ResponseEntity> getEntriesByGame(@PathVariable Str * READ - Get leaderboard entries for a specific user * GET /api/leaderboard/user/{user} */ - @GetMapping("/user/{user}") + @GetMapping("/api/leaderboard/user/{user}") public ResponseEntity> getUserEntries(@PathVariable String user) { List entries = leaderboardService.getUserEntries(user); return ResponseEntity.ok(entries); @@ -97,7 +97,7 @@ public ResponseEntity> getUserEntries(@PathVariable Strin * READ - Get entries for a specific user and game * GET /api/leaderboard/user/{user}/game/{gameName} */ - @GetMapping("/user/{user}/game/{gameName}") + @GetMapping("/api/leaderboard/user/{user}/game/{gameName}") public ResponseEntity> getUserGameEntries( @PathVariable String user, @PathVariable String gameName) { @@ -109,7 +109,7 @@ public ResponseEntity> getUserGameEntries( * UPDATE - Update an existing leaderboard entry * PUT /api/leaderboard/{user}/{gameName} */ - @PutMapping("/{user}/{gameName}") + @PutMapping("/api/leaderboard/{user}/{gameName}") public ResponseEntity updateEntry( @PathVariable String user, @PathVariable String gameName, @@ -126,7 +126,7 @@ public ResponseEntity updateEntry( * DELETE - Delete a leaderboard entry by user and game * DELETE /api/leaderboard/{user}/{gameName} */ - @DeleteMapping("/{user}/{gameName}") + @DeleteMapping("/api/leaderboard/{user}/{gameName}") public ResponseEntity deleteEntry( @PathVariable String user, @PathVariable String gameName) { @@ -138,7 +138,7 @@ public ResponseEntity deleteEntry( * DELETE - Delete all leaderboard entries * DELETE /api/leaderboard */ - @DeleteMapping + @DeleteMapping("/api/leaderboard") public ResponseEntity deleteAllEntries() { leaderboardService.deleteAllEntries(); return ResponseEntity.noContent().build(); @@ -148,7 +148,7 @@ public ResponseEntity deleteAllEntries() { * REFRESH - Refresh entire leaderboard from score tables * POST /api/leaderboard/refresh */ - @PostMapping("/refresh") + @PostMapping("/api/leaderboard/refresh") public ResponseEntity refreshLeaderboard(@RequestParam(defaultValue = "100") int topN) { leaderboardService.refreshLeaderboard(topN); return ResponseEntity.ok("Leaderboard refreshed with top " + topN + " scores"); @@ -158,7 +158,7 @@ public ResponseEntity refreshLeaderboard(@RequestParam(defaultValue = "1 * REFRESH - Refresh leaderboard for a specific game * POST /api/leaderboard/refresh/game/{gameName} */ - @PostMapping("/refresh/game/{gameName}") + @PostMapping("/api/leaderboard/refresh/game/{gameName}") public ResponseEntity refreshLeaderboardForGame( @PathVariable String gameName, @RequestParam(defaultValue = "100") int topN) { From bf9ed8f345a2c34bd72c87c3c7211c0a156c3fe5 Mon Sep 17 00:00:00 2001 From: Nora Ahadian Date: Tue, 16 Dec 2025 23:12:08 -0800 Subject: [PATCH 21/23] create a collom to track the variable that is being counted --- .../com/open/spring/mvc/PauseMenu/GamerScoreController.java | 6 ++++++ .../java/com/open/spring/mvc/PauseMenu/ScoreCounter.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java index 0820c058..9df579a4 100644 --- a/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java +++ b/src/main/java/com/open/spring/mvc/PauseMenu/GamerScoreController.java @@ -25,6 +25,7 @@ public static class GamerScoreRequest { public String user; public Integer score; public String gameName; + public String variableName; } @PostMapping("/score") @@ -39,11 +40,16 @@ public ResponseEntity> saveGamerScore(@RequestBody GamerScor if (gameName == null || gameName.trim().isEmpty()) { gameName = "unknown"; } + String variableName = payload != null ? payload.variableName : null; + if (variableName == null || variableName.trim().isEmpty()) { + variableName = "unknown"; + } ScoreCounter newScore = new ScoreCounter(); newScore.setUser(user); newScore.setScore(score); newScore.setGameName(gameName); + newScore.setVariableName(variableName); ScoreCounter saved = scoreRepository.save(newScore); diff --git a/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java index 56f568a0..5ba5c166 100644 --- a/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java +++ b/src/main/java/com/open/spring/mvc/PauseMenu/ScoreCounter.java @@ -27,4 +27,7 @@ public class ScoreCounter { @Column(nullable = true) private String gameName; + + @Column(nullable = true) + private String variableName; } From 61f1091503f735cafeccf3b7377cce8261bc9f3a Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Wed, 17 Dec 2025 00:56:44 -0800 Subject: [PATCH 22/23] made leaderboard api accessible --- .../spring/mvc/leaderboard/CorsConfig.java | 29 --- .../leaderboard/LeaderboardController.java | 170 +++++------------- .../mvc/leaderboard/LeaderboardEntry.java | 104 ----------- .../leaderboard/LeaderboardRepository.java | 48 +++-- .../mvc/leaderboard/LeaderboardService.java | 129 ++----------- .../open/spring/security/SecurityConfig.java | 2 + 6 files changed, 83 insertions(+), 399 deletions(-) delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java delete mode 100644 src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java diff --git a/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java b/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java deleted file mode 100644 index 949de6ec..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/CorsConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class CorsConfig { - - @Bean - public WebMvcConfigurer corsConfigurer() { - return new WebMvcConfigurer() { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") - .allowedOrigins( - "http://localhost:4500", - "http://127.0.0.1:4500", - "http://localhost:4200", - "http://127.0.0.1:4200" - ) - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true); - } - }; - } -} 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 20a00871..208c1ac8 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardController.java @@ -1,66 +1,61 @@ package com.open.spring.mvc.leaderboard; +import com.open.spring.mvc.PauseMenu.ScoreCounter; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; 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.PostMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import lombok.Data; import java.util.List; /** - * API Controller for Leaderboard Management + * API Controller for Leaderboard (reads from score_counter table) + * CORS configured for public access without authentication + * + * Endpoints: + * - GET /api/leaderboard - Main leaderboard endpoint (all scores) + * - GET /api/pausemenu/score/leaderboard - Alias for compatibility with PauseMenu + * - GET /api/leaderboard/top/{limit} - Top N scores + * - GET /api/leaderboard/game/{gameName} - Scores for specific game + * - GET /api/leaderboard/user/{user} - Scores for specific user */ @RestController -@CrossOrigin(origins = "*", allowedHeaders = "*") +@CrossOrigin( + origins = "*", + allowedHeaders = "*", + methods = { + org.springframework.web.bind.annotation.RequestMethod.GET, + org.springframework.web.bind.annotation.RequestMethod.OPTIONS + }, + allowCredentials = "false" +) public class LeaderboardController { @Autowired private LeaderboardService leaderboardService; - /** - * CREATE - Add a new leaderboard entry - * POST /api/leaderboard - */ - @PostMapping("/api/leaderboard") - public ResponseEntity createEntry(@RequestBody LeaderboardEntryRequest request) { - LeaderboardEntry entry = leaderboardService.addEntry( - request.getUser(), - request.getGameName(), - request.getScore() - ); - return new ResponseEntity<>(entry, HttpStatus.CREATED); - } - /** * READ - Get all leaderboard entries ordered by score - * GET /api/leaderboard (primary endpoint) - * GET /api/pausemenu/score/leaderboard (alias for compatibility) + * This pulls directly from the score_counter table + * GET /api/leaderboard (primary endpoint - used by frontend) + * GET /api/pausemenu/score/leaderboard (alias to match PauseMenu path structure) */ @GetMapping({"/api/leaderboard", "/api/pausemenu/score/leaderboard"}) - public ResponseEntity> getAllEntries() { - List entries = leaderboardService.getAllEntriesByScore(); - return ResponseEntity.ok(entries); - } - - /** - * READ - Get a single leaderboard entry by user and game - * GET /api/leaderboard/{user}/{gameName} - */ - @GetMapping("/api/leaderboard/{user}/{gameName}") - public ResponseEntity getEntryByUserAndGame( - @PathVariable String user, - @PathVariable String gameName) { - LeaderboardEntry entry = leaderboardService.getEntryByUserAndGame(user, gameName); - return ResponseEntity.ok(entry); + public ResponseEntity> getAllEntries() { + try { + List entries = leaderboardService.getAllEntriesByScore(); + // Always return a valid JSON array, even if empty + if (entries == null) { + entries = List.of(); + } + System.out.println("Leaderboard: Returning " + entries.size() + " entries"); + return ResponseEntity.ok(entries); + } catch (Exception e) { + System.err.println("Error fetching leaderboard: " + e.getMessage()); + e.printStackTrace(); + return ResponseEntity.ok(List.of()); // Return empty array on error + } } /** @@ -68,9 +63,9 @@ public ResponseEntity getEntryByUserAndGame( * GET /api/leaderboard/top/{limit} */ @GetMapping("/api/leaderboard/top/{limit}") - public ResponseEntity> getTopScores(@PathVariable int limit) { - List entries = leaderboardService.getTopScores(limit); - return ResponseEntity.ok(entries); + public ResponseEntity> getTopScores(@PathVariable int limit) { + List entries = leaderboardService.getTopScores(limit); + return ResponseEntity.ok(entries != null ? entries : List.of()); } /** @@ -78,9 +73,9 @@ public ResponseEntity> getTopScores(@PathVariable int lim * GET /api/leaderboard/game/{gameName} */ @GetMapping("/api/leaderboard/game/{gameName}") - public ResponseEntity> getEntriesByGame(@PathVariable String gameName) { - List entries = leaderboardService.getEntriesByGame(gameName); - return ResponseEntity.ok(entries); + public ResponseEntity> getEntriesByGame(@PathVariable String gameName) { + List entries = leaderboardService.getEntriesByGame(gameName); + return ResponseEntity.ok(entries != null ? entries : List.of()); } /** @@ -88,9 +83,9 @@ public ResponseEntity> getEntriesByGame(@PathVariable Str * GET /api/leaderboard/user/{user} */ @GetMapping("/api/leaderboard/user/{user}") - public ResponseEntity> getUserEntries(@PathVariable String user) { - List entries = leaderboardService.getUserEntries(user); - return ResponseEntity.ok(entries); + public ResponseEntity> getUserEntries(@PathVariable String user) { + List entries = leaderboardService.getUserEntries(user); + return ResponseEntity.ok(entries != null ? entries : List.of()); } /** @@ -98,79 +93,10 @@ public ResponseEntity> getUserEntries(@PathVariable Strin * GET /api/leaderboard/user/{user}/game/{gameName} */ @GetMapping("/api/leaderboard/user/{user}/game/{gameName}") - public ResponseEntity> getUserGameEntries( + public ResponseEntity> getUserGameEntries( @PathVariable String user, @PathVariable String gameName) { - List entries = leaderboardService.getUserGameEntries(user, gameName); - return ResponseEntity.ok(entries); - } - - /** - * UPDATE - Update an existing leaderboard entry - * PUT /api/leaderboard/{user}/{gameName} - */ - @PutMapping("/api/leaderboard/{user}/{gameName}") - public ResponseEntity updateEntry( - @PathVariable String user, - @PathVariable String gameName, - @RequestBody LeaderboardEntryRequest request) { - LeaderboardEntry updated = leaderboardService.updateEntry( - user, - gameName, - request.getScore() - ); - return ResponseEntity.ok(updated); - } - - /** - * DELETE - Delete a leaderboard entry by user and game - * DELETE /api/leaderboard/{user}/{gameName} - */ - @DeleteMapping("/api/leaderboard/{user}/{gameName}") - public ResponseEntity deleteEntry( - @PathVariable String user, - @PathVariable String gameName) { - leaderboardService.deleteEntry(user, gameName); - return ResponseEntity.noContent().build(); - } - - /** - * DELETE - Delete all leaderboard entries - * DELETE /api/leaderboard - */ - @DeleteMapping("/api/leaderboard") - public ResponseEntity deleteAllEntries() { - leaderboardService.deleteAllEntries(); - return ResponseEntity.noContent().build(); - } - - /** - * REFRESH - Refresh entire leaderboard from score tables - * POST /api/leaderboard/refresh - */ - @PostMapping("/api/leaderboard/refresh") - public ResponseEntity refreshLeaderboard(@RequestParam(defaultValue = "100") int topN) { - leaderboardService.refreshLeaderboard(topN); - return ResponseEntity.ok("Leaderboard refreshed with top " + topN + " scores"); - } - - /** - * REFRESH - Refresh leaderboard for a specific game - * POST /api/leaderboard/refresh/game/{gameName} - */ - @PostMapping("/api/leaderboard/refresh/game/{gameName}") - public ResponseEntity refreshLeaderboardForGame( - @PathVariable String gameName, - @RequestParam(defaultValue = "100") int topN) { - leaderboardService.refreshLeaderboardForGame(gameName, topN); - return ResponseEntity.ok("Leaderboard refreshed for game '" + gameName + "' with top " + topN + " scores"); - } - - // DTO for request body - @Data - public static class LeaderboardEntryRequest { - private String user; - private String gameName; - private Integer score; + List entries = leaderboardService.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/LeaderboardEntry.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java deleted file mode 100644 index d97c53bf..00000000 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardEntry.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.open.spring.mvc.leaderboard; - -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import jakarta.persistence.Id; -import jakarta.persistence.Column; -import jakarta.persistence.IdClass; -import java.io.Serializable; -import java.util.Objects; - -@Entity -@Table(name = "leaderboard") -@IdClass(LeaderboardEntry.LeaderboardId.class) -public class LeaderboardEntry { - - @Id - @Column(name = "user", nullable = false) - private String user; - - @Id - @Column(name = "game_name", nullable = false) - private String gameName; - - @Column(name = "score", nullable = false) - private Integer score; - - // Constructors - public LeaderboardEntry() {} - - public LeaderboardEntry(String user, String gameName, Integer score) { - this.user = user; - this.gameName = gameName; - this.score = score; - } - - // Getters and Setters - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getGameName() { - return gameName; - } - - public void setGameName(String gameName) { - this.gameName = gameName; - } - - public Integer getScore() { - return score; - } - - public void setScore(Integer score) { - this.score = score; - } - - // Composite Primary Key Class - public static class LeaderboardId implements Serializable { - private String user; - private String gameName; - - public LeaderboardId() {} - - public LeaderboardId(String user, String gameName) { - this.user = user; - this.gameName = gameName; - } - - // Getters and Setters - public String getUser() { - return user; - } - - public void setUser(String user) { - this.user = user; - } - - public String getGameName() { - return gameName; - } - - public void setGameName(String gameName) { - this.gameName = gameName; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LeaderboardId that = (LeaderboardId) o; - return Objects.equals(user, that.user) && - Objects.equals(gameName, that.gameName); - } - - @Override - public int hashCode() { - return Objects.hash(user, gameName); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java index 6c88786d..29d1a830 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardRepository.java @@ -1,43 +1,35 @@ package com.open.spring.mvc.leaderboard; +import com.open.spring.mvc.PauseMenu.ScoreCounter; 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; +// NOTE: This repository queries the existing score_counter table from PauseMenu +// No separate leaderboard table needed! + @Repository -public interface LeaderboardRepository extends JpaRepository { - - // Find all entries ordered by score descending - List findAllByOrderByScoreDesc(); - - // Find top N scores - @Query("SELECT l FROM LeaderboardEntry l ORDER BY l.score DESC") - List findTopScores(); +public interface LeaderboardRepository extends JpaRepository { - // Find leaderboard entries for a specific user - List findByUserOrderByScoreDesc(String user); + // Get all scores ordered by score descending + @Query("SELECT s FROM ScoreCounter s ORDER BY s.score DESC") + List findAllByOrderByScoreDesc(); - // Find leaderboard entries for a specific game - List findByGameNameOrderByScoreDesc(String gameName); + // Get top N scores + @Query("SELECT s FROM ScoreCounter s ORDER BY s.score DESC") + List findTopScores(); - // Find entries for a specific user and game - List findByUserAndGameNameOrderByScoreDesc(String user, String gameName); + // Get scores for a specific game + @Query("SELECT s FROM ScoreCounter s WHERE s.gameName = :gameName ORDER BY s.score DESC") + List findByGameNameOrderByScoreDesc(@Param("gameName") String gameName); - // Custom query to populate leaderboard from existing ScoreCounter table - // This pulls user, game name, and score from your pause menu scores - @Query(value = "SELECT sc.user as user_name, sc.game_name as game_name, sc.score " + - "FROM score_counter sc " + - "ORDER BY sc.score DESC " + - "LIMIT :limit", nativeQuery = true) - List getTopScoresFromScoreTables(@Param("limit") int limit); + // Get scores for a specific user + @Query("SELECT s FROM ScoreCounter s WHERE s.user = :user ORDER BY s.score DESC") + List findByUserOrderByScoreDesc(@Param("user") String user); - // Get top scores for a specific game - @Query(value = "SELECT sc.user as user_name, sc.game_name as game_name, sc.score " + - "FROM score_counter sc " + - "WHERE sc.game_name = :gameName " + - "ORDER BY sc.score DESC " + - "LIMIT :limit", nativeQuery = true) - List getTopScoresForGame(@Param("gameName") String gameName, @Param("limit") int limit); + // Get scores for a specific user and game + @Query("SELECT s FROM ScoreCounter 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/LeaderboardService.java b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java index c37896ad..b7d9434d 100644 --- a/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java +++ b/src/main/java/com/open/spring/mvc/leaderboard/LeaderboardService.java @@ -1,11 +1,14 @@ package com.open.spring.mvc.leaderboard; +import com.open.spring.mvc.PauseMenu.ScoreCounter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; +// This service reads directly from the score_counter table (PauseMenu) +// No separate leaderboard table needed! + @Service public class LeaderboardService { @@ -13,9 +16,9 @@ public class LeaderboardService { private LeaderboardRepository leaderboardRepository; /** - * Get top N entries from the leaderboard + * Get top N entries from pausemenu table */ - public List getTopScores(int limit) { + public List getTopScores(int limit) { return leaderboardRepository.findTopScores() .stream() .limit(limit) @@ -23,136 +26,30 @@ public List getTopScores(int limit) { } /** - * Get all leaderboard entries ordered by score + * Get all entries ordered by score (for the leaderboard widget) */ - public List getAllEntriesByScore() { + public List getAllEntriesByScore() { return leaderboardRepository.findAllByOrderByScoreDesc(); } /** - * Get leaderboard entries for a specific game + * Get entries for a specific game */ - public List getEntriesByGame(String gameName) { + public List getEntriesByGame(String gameName) { return leaderboardRepository.findByGameNameOrderByScoreDesc(gameName); } /** - * Refresh leaderboard from the scores table - * This pulls from your ScoreCounter table (pause menu scores) - */ - @Transactional - public void refreshLeaderboard(int topN) { - // Clear existing leaderboard - leaderboardRepository.deleteAll(); - - // Get top scores from ScoreCounter table - List topScores = leaderboardRepository.getTopScoresFromScoreTables(topN); - - // Convert to LeaderboardEntry objects and save - List entries = topScores.stream() - .map(row -> new LeaderboardEntry( - (String) row[0], // user (from ScoreCounter) - (String) row[1], // game_name (from ScoreCounter) - (Integer) row[2] // score (from ScoreCounter) - )) - .collect(Collectors.toList()); - - leaderboardRepository.saveAll(entries); - } - - /** - * Refresh leaderboard for a specific game - */ - @Transactional - public void refreshLeaderboardForGame(String gameName, int topN) { - // Delete existing entries for this game - List existingEntries = leaderboardRepository.findByGameNameOrderByScoreDesc(gameName); - leaderboardRepository.deleteAll(existingEntries); - - // Get top scores for this game from ScoreCounter - List topScores = leaderboardRepository.getTopScoresForGame(gameName, topN); - - // Convert to LeaderboardEntry objects and save - List entries = topScores.stream() - .map(row -> new LeaderboardEntry( - (String) row[0], // user (from ScoreCounter) - (String) row[1], // game_name (from ScoreCounter) - (Integer) row[2] // score (from ScoreCounter) - )) - .collect(Collectors.toList()); - - leaderboardRepository.saveAll(entries); - } - - /** - * Add a single entry to the leaderboard + * Get entries for a specific user */ - public LeaderboardEntry addEntry(String user, String gameName, Integer score) { - LeaderboardEntry entry = new LeaderboardEntry(user, gameName, score); - return leaderboardRepository.save(entry); - } - - /** - * Get leaderboard entries for a specific user - */ - public List getUserEntries(String user) { + public List getUserEntries(String user) { return leaderboardRepository.findByUserOrderByScoreDesc(user); } /** * Get entries for a specific user and game */ - public List getUserGameEntries(String user, String gameName) { + public List getUserGameEntries(String user, String gameName) { return leaderboardRepository.findByUserAndGameNameOrderByScoreDesc(user, gameName); } - - /** - * Get all leaderboard entries - */ - public List getAllEntries() { - return leaderboardRepository.findAll(); - } - - /** - * Get a single leaderboard entry by user and game - */ - public LeaderboardEntry getEntryByUserAndGame(String user, String gameName) { - LeaderboardEntry.LeaderboardId id = new LeaderboardEntry.LeaderboardId(user, gameName); - return leaderboardRepository.findById(id) - .orElseThrow(() -> new RuntimeException("Leaderboard entry not found for user: " + user + ", game: " + gameName)); - } - - /** - * Update an existing leaderboard entry - */ - @Transactional - public LeaderboardEntry updateEntry(String user, String gameName, Integer score) { - LeaderboardEntry entry = getEntryByUserAndGame(user, gameName); - - if (score != null) { - entry.setScore(score); - } - - return leaderboardRepository.save(entry); - } - - /** - * Delete a leaderboard entry by user and game - */ - @Transactional - public void deleteEntry(String user, String gameName) { - LeaderboardEntry.LeaderboardId id = new LeaderboardEntry.LeaderboardId(user, gameName); - if (!leaderboardRepository.existsById(id)) { - throw new RuntimeException("Leaderboard entry not found for user: " + user + ", game: " + gameName); - } - leaderboardRepository.deleteById(id); - } - - /** - * Delete all leaderboard entries - */ - @Transactional - public void deleteAllEntries() { - leaderboardRepository.deleteAll(); - } } \ No newline at end of file diff --git a/src/main/java/com/open/spring/security/SecurityConfig.java b/src/main/java/com/open/spring/security/SecurityConfig.java index 9dd7760d..b8091d0a 100644 --- a/src/main/java/com/open/spring/security/SecurityConfig.java +++ b/src/main/java/com/open/spring/security/SecurityConfig.java @@ -85,6 +85,8 @@ public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http) throws Exce .requestMatchers("/api/jokes/**").permitAll() // Pause Menu APIs should be public .requestMatchers("/api/pausemenu/**").permitAll() + // Leaderboard should be public - displays scores without authentication + .requestMatchers("/api/leaderboard/**").permitAll() // Frontend calls gamer score endpoint; make it public .requestMatchers("/api/gamer/**").permitAll() // ========================================== From 68bfb50bd51b8c2bdc004d6f02d4d498592bdce2 Mon Sep 17 00:00:00 2001 From: avikaprasad22 Date: Fri, 19 Dec 2025 10:11:04 -0800 Subject: [PATCH 23/23] put data loader back to normal --- src/main/java/com/open/spring/mvc/grades/DataLoader.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/open/spring/mvc/grades/DataLoader.java b/src/main/java/com/open/spring/mvc/grades/DataLoader.java index 044ef9b1..0a729d26 100644 --- a/src/main/java/com/open/spring/mvc/grades/DataLoader.java +++ b/src/main/java/com/open/spring/mvc/grades/DataLoader.java @@ -20,13 +20,12 @@ public class DataLoader implements CommandLineRunner { @Override public void run(String... args) throws Exception { try { - // Sample Grades + // Sample Grades gradeRepository.save(new Grade("STU001", "Mathematics", 85.5, "A", "submission link")); gradeRepository.save(new Grade("STU001", "Science", 92.0, "A+", "submission link")); gradeRepository.save(new Grade("STU002", "Mathematics", 78.0, "B+", "submission link")); gradeRepository.save(new Grade("STU002", "English", 88.5, "A-", "submission link")); gradeRepository.save(new Grade("STU003", "Science", 95.0, "A+", "submission link")); - // Sample Progress progressRepository.save(new Progress("STU001", "Mathematics", 75.0, "In Progress")); progressRepository.save(new Progress("STU001", "Science", 100.0, "Completed")); @@ -39,4 +38,4 @@ public void run(String... args) throws Exception { System.err.println("Grades DataLoader skipped due to database schema mismatch: " + e.getMessage()); } } -} +} \ No newline at end of file