diff --git a/README.md b/README.md index dcd8dbf92493b90aa504d99e7481a6aa9ecd9e91..a656b2c6de75c4d6891b023e298b88a3a7f58ea0 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,11 @@ docker compose up -d - [x] créer le repo et le connecter à la BD - [x] tester unitairement le service - [ ] tester l'intégration complète - - [ ] endpoint fonctionnel pour l'ajout d'un avec test + - [x] endpoint fonctionnel pour l'ajout d'un joueur avec test - [ ] tests d'intégration - [ ] tests unitaires -- [ ] tous les endpoints autres -- gestion de la sécurité +- [x] tous les endpoints demandés +- [ ] gestion de la sécurité ## Going further diff --git a/build.gradle.kts b/build.gradle.kts index ade3b6fc53e65e772afd864a32292bb1760f38f8..29241d06ad503adf9291ccea55da2283200770e9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ val logback_version: String by project val dynamo_version: String by project val dynamo_kt_version: String by project val mockk_version: String by project +val kotlin_reactive_version: String by project plugins { @@ -46,6 +47,7 @@ dependencies { implementation("software.amazon.awssdk:dynamodb-enhanced:$dynamo_version") implementation("software.amazon.awssdk:dynamodb:$dynamo_version") implementation("dev.andrewohara:dynamokt:$dynamo_kt_version") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$kotlin_reactive_version") // Tests testImplementation("io.ktor:ktor-server-test-host") diff --git a/gradle.properties b/gradle.properties index 4bf19e423ad49f08562490a302fd7a7286251344..89aec6c45909d73e9f16cddef3437b8807ab3e04 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,3 +6,4 @@ dynamo_kt_version=1.0.0 ktor_version=2.3.13 logback_version=1.4.14 mockk_version=1.10.0 +kotlin_reactive_version=1.9.0 diff --git a/src/main/kotlin/player/PlayerInfoDTO.kt b/src/main/kotlin/player/PlayerInfoDTO.kt new file mode 100644 index 0000000000000000000000000000000000000000..c74c97780011de86ae509d187427228fca0990f9 --- /dev/null +++ b/src/main/kotlin/player/PlayerInfoDTO.kt @@ -0,0 +1,7 @@ +package betclic.test.player + +class PlayerInfoDTO( + val pseudo: String, + val pointsNumber: Int, + val ranking: Int, +) \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerRepository.kt b/src/main/kotlin/player/PlayerRepository.kt index c85d2a0c4031433322cef9b84d8b00768495c96a..3a15fd9d5651b934a14d5ee86db5f5f491beec48 100644 --- a/src/main/kotlin/player/PlayerRepository.kt +++ b/src/main/kotlin/player/PlayerRepository.kt @@ -4,6 +4,7 @@ import dev.andrewohara.dynamokt.DataClassTableSchema import io.ktor.server.application.* import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.future.await +import kotlinx.coroutines.reactive.asFlow import org.slf4j.LoggerFactory import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient import software.amazon.awssdk.enhanced.dynamodb.Key @@ -24,10 +25,24 @@ class PlayerRepository(dynamoDbEnhancedClient: DynamoDbEnhancedAsyncClient) { return updatedPlayer.toPlayer() } - suspend fun findPlayerByPseudo(pseudo: String): Player { + suspend fun findPlayerByPseudo(pseudo: String): Player? { val foundPlayer = table.getItem( Key.builder().partitionValue(pseudo).build() ).await() - return foundPlayer.toPlayer() + return foundPlayer?.toPlayer() } -} \ No newline at end of file + + suspend fun findAll(): List<Player> { + return buildList { + table.scan().asFlow().collect { it.items().stream().forEach { item -> add(item.toPlayer()) } } + } + } + + + suspend fun deleteAllPlayers() { + table.deleteTable() + table.createTable().await() + + } +} + diff --git a/src/main/kotlin/player/PlayerRoute.kt b/src/main/kotlin/player/PlayerRoute.kt index ff94f41a1fdb2adee5306021f8a4df092a955c70..c55907f13bf01397c78d71667cd13ca1bf952fcb 100644 --- a/src/main/kotlin/player/PlayerRoute.kt +++ b/src/main/kotlin/player/PlayerRoute.kt @@ -9,19 +9,36 @@ import org.koin.ktor.ext.inject fun Routing.playerRoutes() { val playerService by inject<PlayerService>() - post("/players") { - val request = call.receive<String>() - playerService.createNewPlayer(request) - call.respond(HttpStatusCode.Created) - } + route("/players") { + post { + val request = call.receive<String>() + playerService.createNewPlayer(request) + call.respond(HttpStatusCode.Created) + } - put("/players") { - val request = call.receive<Player>() - call.respond(playerService.updatePlayer(request)) - } + put { + val request = call.receive<Player>() + call.respond(playerService.updatePlayer(request)) + } + + get("/{pseudo}") { + val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest) + call.respond<Player>(playerService.findPlayerByPseudo(pseudo)) + } - get("/players/{pseudo}") { - val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest) - call.respond<Player>(playerService.getPlayerByPseudo(pseudo)) + get("/{pseudo}/info") { + val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest) + call.respond<PlayerInfoDTO>(playerService.getPlayerInfoByPseudo(pseudo)) + } + + get("/ranking") { + call.respond(playerService.getPlayersRanked()) + } + + delete { + playerService.deleteAllPlayers() + call.respond(HttpStatusCode.NoContent) + } } + } \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerService.kt b/src/main/kotlin/player/PlayerService.kt index 2f06a29e04cb25c3d304f2312b8e0f4d712ea33e..2b12d176de181fbc0acd999ecfa12a200c8f79b2 100644 --- a/src/main/kotlin/player/PlayerService.kt +++ b/src/main/kotlin/player/PlayerService.kt @@ -5,5 +5,11 @@ interface PlayerService { suspend fun updatePlayer(player: Player): Player - suspend fun getPlayerByPseudo(pseudo: String): Player + suspend fun findPlayerByPseudo(pseudo: String): Player + + suspend fun getPlayerInfoByPseudo(pseudo: String): PlayerInfoDTO + + suspend fun getPlayersRanked(): List<PlayerInfoDTO> + + suspend fun deleteAllPlayers() } \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerServiceImpl.kt b/src/main/kotlin/player/PlayerServiceImpl.kt index 9af6cdc32fa6330fdd70343995c8b8269052b93d..a9fc7a42c878c77d919b3e5631e0800835c59130 100644 --- a/src/main/kotlin/player/PlayerServiceImpl.kt +++ b/src/main/kotlin/player/PlayerServiceImpl.kt @@ -1,5 +1,7 @@ package betclic.test.player +import io.ktor.server.plugins.* + class PlayerServiceImpl(private val playerRepository: PlayerRepository) : PlayerService { override suspend fun createNewPlayer(pseudo: String) { @@ -10,7 +12,31 @@ class PlayerServiceImpl(private val playerRepository: PlayerRepository) : Player return playerRepository.updatePlayer(player) } - override suspend fun getPlayerByPseudo(pseudo: String): Player { - return playerRepository.findPlayerByPseudo(pseudo) + override suspend fun findPlayerByPseudo(pseudo: String): Player { + val player = playerRepository.findPlayerByPseudo(pseudo) ?: throw NotFoundException("Player $pseudo not found") + return player + } + + override suspend fun getPlayerInfoByPseudo(pseudo: String): PlayerInfoDTO { + val allPlayersRanked = getPlayersRanked() + val player = allPlayersRanked.find { it.pseudo == pseudo } + ?: throw NotFoundException("Player $pseudo not found in ranking") + return player + } + + override suspend fun getPlayersRanked(): List<PlayerInfoDTO> { + val allPlayers = playerRepository.findAll() + return allPlayers.sortedByDescending { it.pointsNumber }.mapIndexed { index, player -> + PlayerInfoDTO( + pseudo = player.pseudo, + pointsNumber = player.pointsNumber, + ranking = index + 1 + ) + } + + } + + override suspend fun deleteAllPlayers() { + playerRepository.deleteAllPlayers() } } \ No newline at end of file