From 417739e6b04837525994148106f6fade4ecdcecc Mon Sep 17 00:00:00 2001
From: mehdiboudjoudi <m.boudjoudi@neusta.de>
Date: Wed, 5 Mar 2025 10:27:24 +0100
Subject: [PATCH] TD-34: Endpoint for getting all players and tests for this
 endpoint

---
 api/api.yml                                   | 69 ++++++++++++++-
 .../server/admin/AdminApiController.java      | 49 ++++++++++-
 .../server/player/PlayerRepository.java       |  3 +
 .../utils/OrderToDirectionMapperService.java  | 18 ++++
 .../server/utils/PlayerMapperService.java     | 28 ++++++
 .../towerdefence/server/IntegrationTest.java  | 27 +++++-
 .../server/server/GetAllPlayersTest.java      | 85 +++++++++++++++++++
 .../server/server/PlayerRegistrationTest.java |  2 -
 8 files changed, 274 insertions(+), 7 deletions(-)
 create mode 100644 src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java
 create mode 100644 src/main/java/de/towerdefence/server/utils/PlayerMapperService.java
 create mode 100644 src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java

diff --git a/api/api.yml b/api/api.yml
index 83ffca4..3cac665 100644
--- a/api/api.yml
+++ b/api/api.yml
@@ -7,7 +7,7 @@ servers:
   - url: /api/v1
   - url: http://localhost:8080/api/v1
 security:
-  - JWTAuth: []
+  - JWTAuth: [ ]
 
 components:
   securitySchemes:
@@ -83,9 +83,47 @@ components:
       type: object
       properties:
         username:
-           type: string
+          type: string
       required:
         - username
+    #############################################
+    #  AdministratablePlayer                                   #
+    #############################################
+    AdministratablePlayer:
+      description: a Player
+      type: object
+      properties:
+        username:
+          type: string
+      required:
+        - username
+    #############################################
+    #  PlayerFilter                             #
+    #############################################
+    PlayerFilter:
+      description: Configuration data for query for the getting all players endpoint
+      type: object
+      properties:
+        page:
+          type: integer
+          description: "Page number (zero-based index)."
+        pageSize:
+          type: integer
+          description: "Number of players per page."
+        sortBy:
+          type: string
+          description: "Field to sort by (default is username)."
+        order:
+          type: string
+          enum: [ ascending, descending ]
+          description: "Sorting order (asc or desc)."
+        username:
+          type: string
+          description: "Filter players by username (case-insensitive, partial match)."
+      required:
+        - page
+        - pageSize
+        - order
   responses:
     201PlayerCreated:
       description: "201 - Player Created"
@@ -198,5 +236,32 @@ paths:
           $ref: "#/components/responses/401Unauthorized"
         500:
           $ref: "#/components/responses/500InternalError"
+        503:
+          $ref: "#/components/responses/503ServiceUnavailable"
+  /admin/players:
+    get:
+      operationId: "GetAllPlayers"
+      tags:
+        - admin
+      summary: "Retrieve a paginated list of players"
+      description: "Returns a paginat#ed list of players, optionally filtered by username."
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/PlayerFilter"
+      responses:
+        200:
+          description: "A List of all Player"
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: "#/components/schemas/AdministratablePlayer"
+        401:
+          $ref: "#/components/responses/401Unauthorized"
+        500:
+          $ref: "#/components/responses/500InternalError"
         503:
           $ref: "#/components/responses/503ServiceUnavailable"
\ No newline at end of file
diff --git a/src/main/java/de/towerdefence/server/admin/AdminApiController.java b/src/main/java/de/towerdefence/server/admin/AdminApiController.java
index a04f9b6..9356c2a 100644
--- a/src/main/java/de/towerdefence/server/admin/AdminApiController.java
+++ b/src/main/java/de/towerdefence/server/admin/AdminApiController.java
@@ -1,16 +1,26 @@
 package de.towerdefence.server.admin;
 
-
 import com.fasterxml.jackson.databind.ObjectMapper;
 import de.towerdefence.server.auth.UserSession;
 import de.towerdefence.server.oas.AdminApi;
 import de.towerdefence.server.oas.models.AdminAuthInfo;
+import de.towerdefence.server.oas.models.AdministratablePlayer;
+import de.towerdefence.server.oas.models.PlayerFilter;
+import de.towerdefence.server.player.Player;
+import de.towerdefence.server.player.PlayerRepository;
+import de.towerdefence.server.utils.OrderToDirectionMapperService;
+import de.towerdefence.server.utils.PlayerMapperService;
 import jakarta.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 
+import java.util.List;
 import java.util.Optional;
 
 @Controller
@@ -20,6 +30,16 @@ public class AdminApiController implements AdminApi {
     @Autowired
     UserSession userSession;
 
+    @Autowired
+    PlayerRepository playerRepository;
+
+    @Autowired
+    PlayerMapperService playerMapperService;
+
+    @Autowired
+    OrderToDirectionMapperService orderToDirectionMapperService;
+
+
     @Override
     public Optional<ObjectMapper> getObjectMapper() {
         return Optional.empty();
@@ -36,4 +56,31 @@ public class AdminApiController implements AdminApi {
         authInfo.setUsername(this.userSession.getUsername());
         return ResponseEntity.ok(authInfo);
     }
+
+    @Override
+    public ResponseEntity<List<AdministratablePlayer>> getAllPlayers(PlayerFilter body) {
+
+        PlayerFilter.OrderEnum order = body.getOrder();
+        Integer page = body.getPage();
+        Integer pageSize = body.getPageSize();
+        String sortBy = body.getSortBy();
+        String username = body.getUsername();
+
+        Sort.Direction direction = orderToDirectionMapperService.orderToDirection(order);
+
+        Pageable pageable = PageRequest.of(page, pageSize, Sort.by(direction, sortBy));
+
+        Page<Player> playerPage;
+
+        if (username != null && !username.isEmpty()) {
+            playerPage = playerRepository.findByUsernameContainingIgnoreCase(username, pageable);
+        } else {
+            playerPage = playerRepository.findAll(pageable);
+        }
+
+        List<AdministratablePlayer> playersMapped =
+                playerMapperService.mapPlayersToAdministratablePlayers(playerPage.getContent());
+
+        return ResponseEntity.ok(playersMapped);
+    }
 }
diff --git a/src/main/java/de/towerdefence/server/player/PlayerRepository.java b/src/main/java/de/towerdefence/server/player/PlayerRepository.java
index 5ca4c19..612608c 100644
--- a/src/main/java/de/towerdefence/server/player/PlayerRepository.java
+++ b/src/main/java/de/towerdefence/server/player/PlayerRepository.java
@@ -1,8 +1,11 @@
 package de.towerdefence.server.player;
 
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 public  interface PlayerRepository extends JpaRepository<Player, Long> {
     Player findByUsername(String username);
     boolean existsByUsername(String username);
+    Page<Player> findByUsernameContainingIgnoreCase(String username, Pageable pageable);
 }
diff --git a/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java b/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java
new file mode 100644
index 0000000..acf0e05
--- /dev/null
+++ b/src/main/java/de/towerdefence/server/utils/OrderToDirectionMapperService.java
@@ -0,0 +1,18 @@
+package de.towerdefence.server.utils;
+
+import de.towerdefence.server.oas.models.PlayerFilter;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Component;
+
+@Component
+public class OrderToDirectionMapperService {
+
+    public Sort.Direction orderToDirection(PlayerFilter.OrderEnum order) {
+
+        if (order == PlayerFilter.OrderEnum.ASCENDING) {
+            return Sort.Direction.ASC;
+        } else {
+            return Sort.Direction.DESC;
+        }
+    }
+}
diff --git a/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java b/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java
new file mode 100644
index 0000000..0de83da
--- /dev/null
+++ b/src/main/java/de/towerdefence/server/utils/PlayerMapperService.java
@@ -0,0 +1,28 @@
+package de.towerdefence.server.utils;
+
+import de.towerdefence.server.oas.models.AdministratablePlayer;
+import de.towerdefence.server.player.Player;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class PlayerMapperService {
+
+    public List<AdministratablePlayer> mapPlayersToAdministratablePlayers(List<Player> players) {
+        List<AdministratablePlayer> administratablePlayers = new ArrayList<>();
+        for (Player player : players) {
+            AdministratablePlayer apiPlayer = new AdministratablePlayer();
+            apiPlayer.setUsername(player.getUsername());
+            administratablePlayers.add(apiPlayer);
+        }
+        return administratablePlayers;
+    }
+
+    public AdministratablePlayer mapPlayerToAdministratablePlayer(Player player) {
+        AdministratablePlayer administratablePlayer = new AdministratablePlayer();
+        administratablePlayer.setUsername(player.getUsername());
+        return administratablePlayer;
+    }
+}
diff --git a/src/test/java/de/towerdefence/server/IntegrationTest.java b/src/test/java/de/towerdefence/server/IntegrationTest.java
index dbf41fa..62d2861 100644
--- a/src/test/java/de/towerdefence/server/IntegrationTest.java
+++ b/src/test/java/de/towerdefence/server/IntegrationTest.java
@@ -1,7 +1,9 @@
 package de.towerdefence.server;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import de.towerdefence.server.player.Player;
 import de.towerdefence.server.player.PlayerRepository;
+import de.towerdefence.server.player.PlayerService;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,6 +12,10 @@ import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.test.web.servlet.MockMvc;
 
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
 @SpringBootTest
 @AutoConfigureMockMvc(addFilters = false)
 @ActiveProfiles("test")
@@ -23,10 +29,27 @@ public abstract class IntegrationTest {
     protected ObjectMapper objectMapper;
     @Autowired
     protected PlayerRepository playerRepository;
-    
+    @Autowired
+    protected PlayerService playerService;
+
     @BeforeEach
     void setUp() {
         playerRepository.deleteAll();
+
+        Map<String, String> players = new HashMap<>();
+        players.put("Alex", "1234");
+        players.put("Zorro", "1234");
+
+        players.forEach((username, password) -> {
+            Player player = new Player();
+            player.setUsername(username);
+            try {
+                playerService.setPassword(player, password);
+                playerRepository.saveAndFlush(player);
+            } catch (NoSuchAlgorithmException e) {
+                System.err.println("Error setting password for player: " + username);
+            }
+        });
     }
 
     @AfterEach
@@ -34,5 +57,5 @@ public abstract class IntegrationTest {
         playerRepository.deleteAll();
     }
 
-   }
+}
 
diff --git a/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java b/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java
new file mode 100644
index 0000000..556c338
--- /dev/null
+++ b/src/test/java/de/towerdefence/server/server/GetAllPlayersTest.java
@@ -0,0 +1,85 @@
+package de.towerdefence.server.server;
+
+import de.towerdefence.server.IntegrationTest;
+import de.towerdefence.server.oas.models.PlayerFilter;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+
+class GetAllPlayersTest extends IntegrationTest {
+
+    private MockHttpServletRequestBuilder createGetAllPlayersRequest(String requestBody) {
+        return MockMvcRequestBuilders.get(baseUri + "/admin/players")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(requestBody);
+    }
+
+    @Test
+    void playersExist() throws Exception {
+        PlayerFilter playerFilter = new PlayerFilter();
+        playerFilter.setPage(0);
+        playerFilter.setPageSize(10);
+        playerFilter.setOrder(PlayerFilter.OrderEnum.DESCENDING);
+        playerFilter.setUsername("");
+        playerFilter.setSortBy("username");
+        String requestBody = this.objectMapper.writeValueAsString(playerFilter);
+
+        this.mvc.perform(createGetAllPlayersRequest(requestBody))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$").isArray())
+                .andExpect(jsonPath("$[0]").exists());
+    }
+
+    @Test
+    void playersSortedByAsc() throws Exception {
+
+        PlayerFilter playerFilter = new PlayerFilter();
+        playerFilter.setPage(0);
+        playerFilter.setPageSize(10);
+        playerFilter.setOrder(PlayerFilter.OrderEnum.ASCENDING);
+        playerFilter.setUsername("");
+        playerFilter.setSortBy("username");
+        String requestBody = this.objectMapper.writeValueAsString(playerFilter);
+
+        this.mvc.perform(createGetAllPlayersRequest(requestBody))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$[0].username").value("Alex"))
+                .andExpect(jsonPath("$[1].username").value("Zorro"));
+    }
+
+    @Test
+    void playersSortedByDesc() throws Exception {
+        PlayerFilter playerFilter = new PlayerFilter();
+        playerFilter.setPage(0);
+        playerFilter.setPageSize(10);
+        playerFilter.setOrder(PlayerFilter.OrderEnum.DESCENDING);
+        playerFilter.setUsername("");
+        playerFilter.setSortBy("username");
+        String requestBody = this.objectMapper.writeValueAsString(playerFilter);
+
+        this.mvc.perform(createGetAllPlayersRequest(requestBody))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$[1].username").value("Alex"))
+                .andExpect(jsonPath("$[0].username").value("Zorro"));
+    }
+
+    @Test
+    void playersFiltered() throws Exception {
+        PlayerFilter playerFilter = new PlayerFilter();
+        playerFilter.setPage(0);
+        playerFilter.setPageSize(10);
+        playerFilter.setOrder(PlayerFilter.OrderEnum.ASCENDING);
+        playerFilter.setUsername("Alex");
+        playerFilter.setSortBy("username");
+        String requestBody = this.objectMapper.writeValueAsString(playerFilter);
+
+        this.mvc.perform(createGetAllPlayersRequest(requestBody))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$[0].username").value("Alex"))
+                .andExpect(jsonPath("$").isNotEmpty());
+    }
+}
diff --git a/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java b/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java
index 4a76286..df8a3ce 100644
--- a/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java
+++ b/src/test/java/de/towerdefence/server/server/PlayerRegistrationTest.java
@@ -30,6 +30,4 @@ public class PlayerRegistrationTest extends IntegrationTest {
                 .content(this.objectMapper.writeValueAsString(playerRegistrationData))
                 .contentType(MediaType.APPLICATION_JSON);
     }
-
-
 }