feat: NCS-53 changed IO to MicroService for easier scaling
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("org.scoverage") version "8.1"
|
||||
id("io.quarkus")
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
@@ -28,6 +29,10 @@ tasks.withType<ScalaCompile> {
|
||||
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||
}
|
||||
|
||||
val quarkusPlatformGroupId: String by project
|
||||
val quarkusPlatformArtifactId: String by project
|
||||
val quarkusPlatformVersion: String by project
|
||||
|
||||
dependencies {
|
||||
|
||||
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||
@@ -53,19 +58,50 @@ dependencies {
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${versions["JACKSON"]!!}")
|
||||
|
||||
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
implementation("io.quarkus:quarkus-rest")
|
||||
implementation("io.quarkus:quarkus-rest-jackson")
|
||||
implementation("io.quarkus:quarkus-arc")
|
||||
implementation("io.quarkus:quarkus-config-yaml")
|
||||
implementation("io.quarkus:quarkus-smallrye-health")
|
||||
implementation("io.quarkus:quarkus-smallrye-openapi")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||
testImplementation("io.quarkus:quarkus-junit5")
|
||||
testImplementation("io.rest-assured:rest-assured")
|
||||
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
}
|
||||
|
||||
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||
}
|
||||
configurations.scoverage {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
|
||||
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Jar>().configureEach {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
includeEngines("scalatest")
|
||||
includeEngines("scalatest", "junit-jupiter")
|
||||
testLogging {
|
||||
events("skipped", "failed")
|
||||
events("passed", "skipped", "failed")
|
||||
}
|
||||
}
|
||||
finalizedBy(tasks.reportScoverage)
|
||||
@@ -73,3 +109,6 @@ tasks.test {
|
||||
tasks.reportScoverage {
|
||||
dependsOn(tasks.test)
|
||||
}
|
||||
tasks.jar {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
quarkus:
|
||||
http:
|
||||
port: 8081
|
||||
application:
|
||||
name: nowchess-io
|
||||
smallrye-openapi:
|
||||
info-title: NowChess IO Service
|
||||
info-version: 1.0.0
|
||||
info-description: Chess notation import and export — FEN and PGN
|
||||
path: /openapi
|
||||
swagger-ui:
|
||||
always-include: true
|
||||
path: /swagger-ui
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.databind.{DeserializationContext, KeyDeserializer}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeyDeserializer extends KeyDeserializer:
|
||||
override def deserializeKey(key: String, ctx: DeserializationContext): AnyRef =
|
||||
Square.fromAlgebraic(key).orNull
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.nowchess.io.json
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.databind.{JsonSerializer, SerializerProvider}
|
||||
import de.nowchess.api.board.Square
|
||||
|
||||
class SquareKeySerializer extends JsonSerializer[Square]:
|
||||
override def serialize(value: Square, gen: JsonGenerator, provider: SerializerProvider): Unit =
|
||||
gen.writeFieldName(value.toString)
|
||||
@@ -0,0 +1,24 @@
|
||||
package de.nowchess.io.service.config
|
||||
|
||||
import com.fasterxml.jackson.core.Version
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.module.scala.DefaultScalaModule
|
||||
import de.nowchess.api.board.Square
|
||||
import de.nowchess.io.json.{SquareKeyDeserializer, SquareKeySerializer}
|
||||
import io.quarkus.jackson.ObjectMapperCustomizer
|
||||
import jakarta.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class JacksonConfig extends ObjectMapperCustomizer:
|
||||
def customize(mapper: ObjectMapper): Unit =
|
||||
mapper.registerModule(new DefaultScalaModule() {
|
||||
override def version(): Version =
|
||||
// scalafix:off DisableSyntax.null
|
||||
new Version(2, 21, 1, null, "com.fasterxml.jackson.module", "jackson-module-scala")
|
||||
// scalafix:on DisableSyntax.null
|
||||
})
|
||||
val squareModule = new SimpleModule()
|
||||
squareModule.addKeyDeserializer(classOf[Square], new SquareKeyDeserializer())
|
||||
squareModule.addKeySerializer(classOf[Square], new SquareKeySerializer())
|
||||
mapper.registerModule(squareModule)
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.nowchess.io.service.config
|
||||
|
||||
import de.nowchess.api.board.{CastlingRights, Color, File, Piece, PieceType, Rank, Square}
|
||||
import de.nowchess.api.game.{DrawReason, GameContext, GameResult}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest, IoErrorDto}
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
|
||||
@RegisterForReflection(
|
||||
targets = Array(
|
||||
classOf[ImportFenRequest],
|
||||
classOf[ImportPgnRequest],
|
||||
classOf[IoErrorDto],
|
||||
classOf[GameContext],
|
||||
classOf[GameResult],
|
||||
classOf[DrawReason],
|
||||
classOf[Color],
|
||||
classOf[Piece],
|
||||
classOf[PieceType],
|
||||
classOf[CastlingRights],
|
||||
classOf[Square],
|
||||
classOf[File],
|
||||
classOf[Rank],
|
||||
classOf[Move],
|
||||
classOf[MoveType],
|
||||
classOf[PromotionPiece],
|
||||
),
|
||||
)
|
||||
class NativeReflectionConfig
|
||||
@@ -0,0 +1,3 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class ImportFenRequest(fen: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class ImportPgnRequest(pgn: String)
|
||||
@@ -0,0 +1,3 @@
|
||||
package de.nowchess.io.service.dto
|
||||
|
||||
case class IoErrorDto(code: String, message: String)
|
||||
@@ -0,0 +1,77 @@
|
||||
package de.nowchess.io.service.resource
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.io.fen.{FenExporter, FenParser}
|
||||
import de.nowchess.io.pgn.{PgnExporter, PgnParser}
|
||||
import de.nowchess.io.service.dto.{ImportFenRequest, ImportPgnRequest, IoErrorDto}
|
||||
import io.smallrye.mutiny.Uni
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.ws.rs.*
|
||||
import jakarta.ws.rs.core.{MediaType, Response}
|
||||
import org.eclipse.microprofile.openapi.annotations.Operation
|
||||
import org.eclipse.microprofile.openapi.annotations.media.{Content, Schema}
|
||||
import org.eclipse.microprofile.openapi.annotations.responses.{APIResponse, APIResponses}
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag
|
||||
|
||||
@Path("/io")
|
||||
@ApplicationScoped
|
||||
@Tag(name = "IO", description = "Chess notation import and export")
|
||||
class IoResource:
|
||||
|
||||
@POST
|
||||
@Path("/import/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Operation(summary = "Import FEN", description = "Parse a FEN string into a GameContext")
|
||||
@APIResponses(
|
||||
Array(
|
||||
new APIResponse(responseCode = "200", description = "Parsed GameContext"),
|
||||
new APIResponse(responseCode = "400", description = "Invalid FEN"),
|
||||
),
|
||||
)
|
||||
def importFen(body: ImportFenRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
FenParser.parseFen(body.fen) match
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_FEN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/import/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.APPLICATION_JSON))
|
||||
@Operation(summary = "Import PGN", description = "Parse a PGN string into a GameContext")
|
||||
@APIResponses(
|
||||
Array(
|
||||
new APIResponse(responseCode = "200", description = "Parsed GameContext"),
|
||||
new APIResponse(responseCode = "400", description = "Invalid PGN"),
|
||||
),
|
||||
)
|
||||
def importPgn(body: ImportPgnRequest): Uni[Response] =
|
||||
Uni.createFrom().item {
|
||||
PgnParser.importGameContext(body.pgn) match
|
||||
case Left(err) =>
|
||||
Response.status(400).entity(IoErrorDto("INVALID_PGN", err)).build()
|
||||
case Right(ctx) =>
|
||||
Response.ok(ctx).build()
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/export/fen")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array(MediaType.TEXT_PLAIN))
|
||||
@Operation(summary = "Export FEN", description = "Serialize a GameContext to FEN notation")
|
||||
@APIResponse(responseCode = "200", description = "FEN string")
|
||||
def exportFen(ctx: GameContext): Uni[Response] =
|
||||
Uni.createFrom().item(Response.ok(FenExporter.exportGameContext(ctx)).build())
|
||||
|
||||
@POST
|
||||
@Path("/export/pgn")
|
||||
@Consumes(Array(MediaType.APPLICATION_JSON))
|
||||
@Produces(Array("application/x-chess-pgn"))
|
||||
@Operation(summary = "Export PGN", description = "Serialize a GameContext to PGN notation")
|
||||
@APIResponse(responseCode = "200", description = "PGN text")
|
||||
def exportPgn(ctx: GameContext): Uni[Response] =
|
||||
Uni.createFrom().item(Response.ok(PgnExporter.exportGameContext(ctx)).build())
|
||||
Reference in New Issue
Block a user