From 952c7dcc76c586a41b6de058732fb9a223ddeb5f Mon Sep 17 00:00:00 2001 From: ccornu <ccornu@takima.fr> Date: Fri, 7 Feb 2025 16:15:12 +0100 Subject: [PATCH] feat: new player endpoints (put + get) + update structure and readme --- README.md | 21 ++++++++++++----- build.gradle.kts | 15 +++++++++--- src/main/kotlin/Application.kt | 5 +++- .../ApiConfiguration.kt} | 9 +------- .../ApiDocumentationConfiguration.kt} | 2 +- .../DynamoDbConfiguration.kt | 8 +------ .../InjectionConfiguration.kt} | 2 +- .../configuration/RoutingConfiguration.kt | 11 +++++++++ .../SecurityConfiguration.kt} | 2 +- src/main/kotlin/player/PlayerRepository.kt | 19 ++++++++++++--- src/main/kotlin/player/PlayerRoute.kt | 23 ++++++++++++------- src/main/kotlin/player/PlayerService.kt | 4 ++++ src/main/kotlin/player/PlayerServiceImpl.kt | 8 +++++++ .../kotlin/player/PlayerIntegrationTest.kt | 3 +-- 14 files changed, 91 insertions(+), 41 deletions(-) rename src/main/kotlin/{Serialization.kt => configuration/ApiConfiguration.kt} (64%) rename src/main/kotlin/{HTTP.kt => configuration/ApiDocumentationConfiguration.kt} (85%) rename src/main/kotlin/{ => configuration}/DynamoDbConfiguration.kt (88%) rename src/main/kotlin/{Koin.kt => configuration/InjectionConfiguration.kt} (94%) create mode 100644 src/main/kotlin/configuration/RoutingConfiguration.kt rename src/main/kotlin/{Security.kt => configuration/SecurityConfiguration.kt} (69%) diff --git a/README.md b/README.md index c185f27..dcd8dbf 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ docker compose up -d ## Available endpoints +## Technical choices + +- I chose ... because ... + ## I want to ensure tests are running ## Left to do @@ -32,11 +36,16 @@ docker compose up -d - [x] faire la connexion avec Dynamo - 1er endpoint - [x] créer l'entité joueur - - [ ] créer le repo et le connecter à la BD - - [ ] tester unitairement le service + - [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 - - [ ] tests d'inté - - [ ] tests unitaires + - [ ] endpoint fonctionnel pour l'ajout d'un avec test + - [ ] tests d'intégration + - [ ] tests unitaires - [ ] tous les endpoints autres -- [ ] gestion de la sécurité \ No newline at end of file +- gestion de la sécurité + +## Going further + +- Database migration tool +- ... \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index ad35518..ade3b6f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } group = "betclic.test" -version = "0.0.1" +version = "0.0.1-SNAPSHOT" application { mainClass.set("io.ktor.server.netty.EngineMain") @@ -26,19 +26,28 @@ repositories { } dependencies { + // Ktor implementation("io.ktor:ktor-server-core") implementation("io.ktor:ktor-server-auth") implementation("io.ktor:ktor-server-openapi") + implementation("io.ktor:ktor-server-config-yaml") + implementation("io.ktor:ktor-server-netty") + + // Dependency injection implementation("io.insert-koin:koin-ktor:$koin_version") implementation("io.insert-koin:koin-logger-slf4j:$koin_version") + + // Content negociation implementation("io.ktor:ktor-server-content-negotiation") implementation("io.ktor:ktor-serialization-jackson") - implementation("io.ktor:ktor-server-netty") implementation("ch.qos.logback:logback-classic:$logback_version") - implementation("io.ktor:ktor-server-config-yaml") + + // Database implementation("software.amazon.awssdk:dynamodb-enhanced:$dynamo_version") implementation("software.amazon.awssdk:dynamodb:$dynamo_version") implementation("dev.andrewohara:dynamokt:$dynamo_kt_version") + + // Tests testImplementation("io.ktor:ktor-server-test-host") testImplementation("io.mockk:mockk:$mockk_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") diff --git a/src/main/kotlin/Application.kt b/src/main/kotlin/Application.kt index 074276f..6e183ef 100644 --- a/src/main/kotlin/Application.kt +++ b/src/main/kotlin/Application.kt @@ -1,6 +1,9 @@ package betclic.test -import betclic.test.player.configureRouting +import betclic.test.configuration.configureHTTP +import betclic.test.configuration.configureKoin +import betclic.test.configuration.configureRouting +import betclic.test.configuration.configureSerialization import io.ktor.server.application.* import kotlinx.coroutines.runBlocking diff --git a/src/main/kotlin/Serialization.kt b/src/main/kotlin/configuration/ApiConfiguration.kt similarity index 64% rename from src/main/kotlin/Serialization.kt rename to src/main/kotlin/configuration/ApiConfiguration.kt index c3ed7d8..27f2a9c 100644 --- a/src/main/kotlin/Serialization.kt +++ b/src/main/kotlin/configuration/ApiConfiguration.kt @@ -1,11 +1,9 @@ -package betclic.test +package betclic.test.configuration import com.fasterxml.jackson.databind.SerializationFeature import io.ktor.serialization.jackson.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.response.* -import io.ktor.server.routing.* fun Application.configureSerialization() { install(ContentNegotiation) { @@ -13,9 +11,4 @@ fun Application.configureSerialization() { enable(SerializationFeature.INDENT_OUTPUT) } } - routing { - get("/json/jackson") { - call.respond(mapOf("hello" to "world")) - } - } } diff --git a/src/main/kotlin/HTTP.kt b/src/main/kotlin/configuration/ApiDocumentationConfiguration.kt similarity index 85% rename from src/main/kotlin/HTTP.kt rename to src/main/kotlin/configuration/ApiDocumentationConfiguration.kt index 62f841d..ed88a96 100644 --- a/src/main/kotlin/HTTP.kt +++ b/src/main/kotlin/configuration/ApiDocumentationConfiguration.kt @@ -1,4 +1,4 @@ -package betclic.test +package betclic.test.configuration import io.ktor.server.application.* import io.ktor.server.plugins.openapi.* diff --git a/src/main/kotlin/DynamoDbConfiguration.kt b/src/main/kotlin/configuration/DynamoDbConfiguration.kt similarity index 88% rename from src/main/kotlin/DynamoDbConfiguration.kt rename to src/main/kotlin/configuration/DynamoDbConfiguration.kt index 8c3d3ad..1ef9481 100644 --- a/src/main/kotlin/DynamoDbConfiguration.kt +++ b/src/main/kotlin/configuration/DynamoDbConfiguration.kt @@ -1,4 +1,4 @@ -package betclic.test +package betclic.test.configuration import betclic.test.player.PlayerEntity import dev.andrewohara.dynamokt.DataClassTableSchema @@ -24,12 +24,6 @@ private fun createDynamoDbClient(): DynamoDbAsyncClient { return DynamoDbAsyncClient.builder().endpointOverride(URI("http://localhost:8000")).build() } -fun Application.createEnhancedDynamoDbClient(dynamoDbClient: DynamoDbAsyncClient): DynamoDbEnhancedAsyncClient { - return DynamoDbEnhancedAsyncClient.builder() - .dynamoDbClient(dynamoDbClient) - .build() -} - suspend fun createNecessaryTables( dynamoDbClient: DynamoDbAsyncClient, dynamoDbEnhancedClient: DynamoDbEnhancedAsyncClient diff --git a/src/main/kotlin/Koin.kt b/src/main/kotlin/configuration/InjectionConfiguration.kt similarity index 94% rename from src/main/kotlin/Koin.kt rename to src/main/kotlin/configuration/InjectionConfiguration.kt index e8afee5..4b62325 100644 --- a/src/main/kotlin/Koin.kt +++ b/src/main/kotlin/configuration/InjectionConfiguration.kt @@ -1,4 +1,4 @@ -package betclic.test +package betclic.test.configuration import betclic.test.player.PlayerRepository import betclic.test.player.PlayerService diff --git a/src/main/kotlin/configuration/RoutingConfiguration.kt b/src/main/kotlin/configuration/RoutingConfiguration.kt new file mode 100644 index 0000000..2e5b34b --- /dev/null +++ b/src/main/kotlin/configuration/RoutingConfiguration.kt @@ -0,0 +1,11 @@ +package betclic.test.configuration + +import betclic.test.player.playerRoutes +import io.ktor.server.application.* +import io.ktor.server.routing.* + +fun Application.configureRouting() { + routing { + playerRoutes() + } +} \ No newline at end of file diff --git a/src/main/kotlin/Security.kt b/src/main/kotlin/configuration/SecurityConfiguration.kt similarity index 69% rename from src/main/kotlin/Security.kt rename to src/main/kotlin/configuration/SecurityConfiguration.kt index 93a244c..1e3ab66 100644 --- a/src/main/kotlin/Security.kt +++ b/src/main/kotlin/configuration/SecurityConfiguration.kt @@ -1,4 +1,4 @@ -package betclic.test +package betclic.test.configuration import io.ktor.server.application.* diff --git a/src/main/kotlin/player/PlayerRepository.kt b/src/main/kotlin/player/PlayerRepository.kt index 68612ab..c85d2a0 100644 --- a/src/main/kotlin/player/PlayerRepository.kt +++ b/src/main/kotlin/player/PlayerRepository.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.future.await import org.slf4j.LoggerFactory import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedAsyncClient +import software.amazon.awssdk.enhanced.dynamodb.Key class PlayerRepository(dynamoDbEnhancedClient: DynamoDbEnhancedAsyncClient) { private val tableName = PlayerEntity::class.simpleName @@ -13,8 +14,20 @@ class PlayerRepository(dynamoDbEnhancedClient: DynamoDbEnhancedAsyncClient) { private val table = dynamoDbEnhancedClient.table(tableName, tableSchema) private val logger = LoggerFactory.getLogger(Application::class.java) - suspend fun createNewPlayer(player: Player): Unit = coroutineScope { - val saved = table.putItem(player.toPlayerEntity()).await() - logger.info("Successfully created new player $saved") + suspend fun createNewPlayer(player: Player) = coroutineScope { + val createdPlayer = table.putItem(player.toPlayerEntity()).await() + logger.info("Successfully created new player $createdPlayer") + } + + suspend fun updatePlayer(player: Player): Player { + val updatedPlayer = table.updateItem(player.toPlayerEntity()).await() + return updatedPlayer.toPlayer() + } + + suspend fun findPlayerByPseudo(pseudo: String): Player { + val foundPlayer = table.getItem( + Key.builder().partitionValue(pseudo).build() + ).await() + return foundPlayer.toPlayer() } } \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerRoute.kt b/src/main/kotlin/player/PlayerRoute.kt index 43d3ca7..ff94f41 100644 --- a/src/main/kotlin/player/PlayerRoute.kt +++ b/src/main/kotlin/player/PlayerRoute.kt @@ -7,14 +7,21 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import org.koin.ktor.ext.inject -fun Application.configureRouting() { - routing { - val playerService by inject<PlayerService>() +fun Routing.playerRoutes() { + val playerService by inject<PlayerService>() + post("/players") { + val request = call.receive<String>() + playerService.createNewPlayer(request) + call.respond(HttpStatusCode.Created) + } + + put("/players") { + val request = call.receive<Player>() + call.respond(playerService.updatePlayer(request)) + } - post("/player") { - val request = call.receive<String>() - playerService.createNewPlayer(request) - call.respond(HttpStatusCode.Created) - } + get("/players/{pseudo}") { + val pseudo = call.parameters["pseudo"] ?: return@get call.respond(HttpStatusCode.BadRequest) + call.respond<Player>(playerService.getPlayerByPseudo(pseudo)) } } \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerService.kt b/src/main/kotlin/player/PlayerService.kt index 222e09b..2f06a29 100644 --- a/src/main/kotlin/player/PlayerService.kt +++ b/src/main/kotlin/player/PlayerService.kt @@ -2,4 +2,8 @@ package betclic.test.player interface PlayerService { suspend fun createNewPlayer(pseudo: String) + + suspend fun updatePlayer(player: Player): Player + + suspend fun getPlayerByPseudo(pseudo: String): Player } \ No newline at end of file diff --git a/src/main/kotlin/player/PlayerServiceImpl.kt b/src/main/kotlin/player/PlayerServiceImpl.kt index 6cfee70..9af6cdc 100644 --- a/src/main/kotlin/player/PlayerServiceImpl.kt +++ b/src/main/kotlin/player/PlayerServiceImpl.kt @@ -5,4 +5,12 @@ class PlayerServiceImpl(private val playerRepository: PlayerRepository) : Player override suspend fun createNewPlayer(pseudo: String) { playerRepository.createNewPlayer(Player(pseudo = pseudo)) } + + override suspend fun updatePlayer(player: Player): Player { + return playerRepository.updatePlayer(player) + } + + override suspend fun getPlayerByPseudo(pseudo: String): Player { + return playerRepository.findPlayerByPseudo(pseudo) + } } \ No newline at end of file diff --git a/src/test/kotlin/player/PlayerIntegrationTest.kt b/src/test/kotlin/player/PlayerIntegrationTest.kt index dae72e7..2aeb263 100644 --- a/src/test/kotlin/player/PlayerIntegrationTest.kt +++ b/src/test/kotlin/player/PlayerIntegrationTest.kt @@ -1,4 +1,4 @@ -package betclic.test.player +package player import io.ktor.client.request.* import io.ktor.http.* @@ -9,7 +9,6 @@ import kotlin.test.assertEquals class PlayerIntegrationTest { @Test fun `When calling post player, should return created`() = testApplication { - assertEquals(HttpStatusCode.Created, client.post("/player") { header(HttpHeaders.ContentType, ContentType.Text.Plain) setBody("Test d'intégration") -- GitLab