diff --git a/README.md b/README.md index c185f273e090e217f7b4c4a6190528ba080c82bd..dcd8dbf92493b90aa504d99e7481a6aa9ecd9e91 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 ad355185daff0d5fa04af60a68110aa1fd063cc1..ade3b6fc53e65e772afd864a32292bb1760f38f8 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 074276f1574861c360f46f3b33860cf928584dc0..6e183effb698210cc51d727ab4bb6451811fe3e6 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 c3ed7d8d4a0883d3f65bdddc42018272d640964e..27f2a9c87fedd2c1e639985e042dd0e4d602042d 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 62f841d708bba4945f1e19b7b5cab4a38112e4d6..ed88a96295cf8183dc966eccda3cbfe17fd74636 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 8c3d3ad3e6bf45d4b4cc85f11bdea5a3691cb5b3..1ef94811b659922806b3a066bd706cd99ecc6b47 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 e8afee56714ea9b172003630cabc19e6c4c21dfe..4b623254b5cc0c60c770546bce811e5ffcb3a83f 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 0000000000000000000000000000000000000000..2e5b34b26566232bc6cabace86b29b5e5e79b0a0 --- /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 93a244c107d7f4dbe1b900c2e26b468638ed0f45..1e3ab668f17eb298da828379c48b7dd909c190de 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 68612abc1f05c13856e44dec0592e392df1675f3..c85d2a0c4031433322cef9b84d8b00768495c96a 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 43d3ca7de521af6cfbb1b14fda5641d58034a970..ff94f41a1fdb2adee5306021f8a4df092a955c70 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 222e09bc7bdbb3038cbcc0112a682a17163ad676..2f06a29e04cb25c3d304f2312b8e0f4d712ea33e 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 6cfee70fd1ad27f9b17d231673f00d85beeee3e4..9af6cdc32fa6330fdd70343995c8b8269052b93d 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 dae72e74c1a77871b05490528c4b7c798d0d5a1a..2aeb2638e07033c08b3837f34a95f4180a9c8960 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")