Opengotha export and basic test
This commit is contained in:
@@ -9,15 +9,16 @@ import javax.servlet.http.HttpServletResponse
|
|||||||
interface ApiHandler {
|
interface ApiHandler {
|
||||||
|
|
||||||
fun route(request: HttpServletRequest, response: HttpServletResponse) =
|
fun route(request: HttpServletRequest, response: HttpServletResponse) =
|
||||||
|
// for now, only get() needed the response object ; other methods shall be reengineered as well if needed
|
||||||
when (request.method) {
|
when (request.method) {
|
||||||
"GET" -> get(request)
|
"GET" -> get(request, response)
|
||||||
"POST" -> post(request)
|
"POST" -> post(request)
|
||||||
"PUT" -> put(request)
|
"PUT" -> put(request)
|
||||||
"DELETE" -> delete(request)
|
"DELETE" -> delete(request)
|
||||||
else -> notImplemented()
|
else -> notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(request: HttpServletRequest): Json {
|
fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
notImplemented()
|
notImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.toJson
|
|||||||
import org.jeudego.pairgoth.web.Event
|
import org.jeudego.pairgoth.web.Event
|
||||||
import org.jeudego.pairgoth.web.Event.*
|
import org.jeudego.pairgoth.web.Event.*
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
object PairingHandler: PairgothApiHandler {
|
object PairingHandler: PairgothApiHandler {
|
||||||
|
|
||||||
override fun get(request: HttpServletRequest): Json {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
val playing = (tournament.games.getOrNull(round)?.values ?: emptyList()).flatMap {
|
val playing = (tournament.games.getOrNull(round)?.values ?: emptyList()).flatMap {
|
||||||
|
@@ -8,10 +8,11 @@ import org.jeudego.pairgoth.model.fromJson
|
|||||||
import org.jeudego.pairgoth.web.Event
|
import org.jeudego.pairgoth.web.Event
|
||||||
import org.jeudego.pairgoth.web.Event.*
|
import org.jeudego.pairgoth.web.Event.*
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
object PlayerHandler: PairgothApiHandler {
|
object PlayerHandler: PairgothApiHandler {
|
||||||
|
|
||||||
override fun get(request: HttpServletRequest): Json {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = getTournament(request) ?: badRequest("invalid tournament")
|
val tournament = getTournament(request) ?: badRequest("invalid tournament")
|
||||||
return when (val pid = getSubSelector(request)?.toIntOrNull()) {
|
return when (val pid = getSubSelector(request)?.toIntOrNull()) {
|
||||||
null -> tournament.pairables.values.map { it.toJson() }.toJsonArray()
|
null -> tournament.pairables.values.map { it.toJson() }.toJsonArray()
|
||||||
|
@@ -4,15 +4,14 @@ import com.republicate.kson.Json
|
|||||||
import com.republicate.kson.toJsonArray
|
import com.republicate.kson.toJsonArray
|
||||||
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
import org.jeudego.pairgoth.model.Game
|
import org.jeudego.pairgoth.model.Game
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
|
||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.store.Store
|
|
||||||
import org.jeudego.pairgoth.web.Event
|
import org.jeudego.pairgoth.web.Event
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
object ResultsHandler: PairgothApiHandler {
|
object ResultsHandler: PairgothApiHandler {
|
||||||
|
|
||||||
override fun get(request: HttpServletRequest): Json {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
|
||||||
val games = tournament.games.getOrNull(round)?.values ?: emptyList()
|
val games = tournament.games.getOrNull(round)?.values ?: emptyList()
|
||||||
|
@@ -8,17 +8,30 @@ import org.jeudego.pairgoth.model.Tournament
|
|||||||
import org.jeudego.pairgoth.model.fromJson
|
import org.jeudego.pairgoth.model.fromJson
|
||||||
import org.jeudego.pairgoth.model.toJson
|
import org.jeudego.pairgoth.model.toJson
|
||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
|
import org.jeudego.pairgoth.web.ApiServlet
|
||||||
import org.jeudego.pairgoth.web.Event
|
import org.jeudego.pairgoth.web.Event
|
||||||
import org.jeudego.pairgoth.web.Event.*
|
import org.jeudego.pairgoth.web.Event.*
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
object TournamentHandler: PairgothApiHandler {
|
object TournamentHandler: PairgothApiHandler {
|
||||||
|
|
||||||
override fun get(request: HttpServletRequest): Json {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
|
val accept = request.getHeader("Accept")
|
||||||
return when (val id = getSelector(request)?.toIntOrNull()) {
|
return when (val id = getSelector(request)?.toIntOrNull()) {
|
||||||
null -> Json.Array(Store.getTournamentsIDs())
|
null -> Json.Array(Store.getTournamentsIDs())
|
||||||
else -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
|
else ->
|
||||||
|
when {
|
||||||
|
ApiServlet.isJson(accept) -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
|
||||||
|
ApiServlet.isXml(accept) -> {
|
||||||
|
val export = Store.getTournament(id)?.let { OpenGotha.export(it) } ?: badRequest("no tournament with id #${id}")
|
||||||
|
response.contentType = "application/xml; charset=UTF-8"
|
||||||
|
response.writer.write(export)
|
||||||
|
null // return null to indicate that we handled the response ourself
|
||||||
|
}
|
||||||
|
else -> badRequest("unhandled Accept header: $accept")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,9 @@ import org.jeudego.pairgoth.model.Player
|
|||||||
import org.jeudego.pairgoth.model.StandardByoyomi
|
import org.jeudego.pairgoth.model.StandardByoyomi
|
||||||
import org.jeudego.pairgoth.model.SuddenDeath
|
import org.jeudego.pairgoth.model.SuddenDeath
|
||||||
import org.jeudego.pairgoth.model.Swiss
|
import org.jeudego.pairgoth.model.Swiss
|
||||||
|
import org.jeudego.pairgoth.model.TimeSystem
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.model.displayRank
|
||||||
import org.jeudego.pairgoth.model.parseRank
|
import org.jeudego.pairgoth.model.parseRank
|
||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import org.jeudego.pairgoth.util.XmlFormat
|
import org.jeudego.pairgoth.util.XmlFormat
|
||||||
@@ -173,4 +175,110 @@ object OpenGotha {
|
|||||||
tournament.games.addAll(gamesPerRound)
|
tournament.games.addAll(gamesPerRound)
|
||||||
return tournament
|
return tournament
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO - bye player(s)
|
||||||
|
fun export(tournament: Tournament): String {
|
||||||
|
val xml = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<Tournament dataVersion="201" externalIPAddress="88.122.144.219" fullVersionNumber="3.51" runningMode="SAL" saveDT="20210111180800">
|
||||||
|
<Players>
|
||||||
|
${tournament.pairables.values.map { player ->
|
||||||
|
player as Player
|
||||||
|
}.joinToString("\n") { player ->
|
||||||
|
"""<Player agaExpirationDate="" agaId="" club="${
|
||||||
|
player.club
|
||||||
|
}" country="${
|
||||||
|
player.country
|
||||||
|
}" egfPin="" ffgLicence="" ffgLicenceStatus="" firstName="${
|
||||||
|
player.firstname
|
||||||
|
}" grade="${
|
||||||
|
player.displayRank()
|
||||||
|
}" name="${
|
||||||
|
player.name
|
||||||
|
}" participating="${
|
||||||
|
(1..20).map {
|
||||||
|
if (player.skip.contains(it)) 0 else 1
|
||||||
|
}.joinToString("")
|
||||||
|
}" rank="${
|
||||||
|
player.displayRank()
|
||||||
|
}" rating="${
|
||||||
|
player.rating
|
||||||
|
}" ratingOrigin="" registeringStatus="FIN" smmsCorrection="0"/>"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</Players>
|
||||||
|
<Games>
|
||||||
|
${tournament.games.flatMapIndexed { round, games ->
|
||||||
|
games.values.mapIndexed { table, game ->
|
||||||
|
Triple(round, table , game)
|
||||||
|
}
|
||||||
|
}.joinToString("\n") { (round, table, game) ->
|
||||||
|
"""<Game blackPlayer="${
|
||||||
|
(tournament.pairables[game.black]!! as Player).let { black ->
|
||||||
|
"${black.name}${black.firstname}".uppercase(Locale.ENGLISH) // Use Locale.ENGLISH to transform é to É
|
||||||
|
}
|
||||||
|
}" handicap="0" knownColor="true" result="${
|
||||||
|
when (game.result) {
|
||||||
|
Game.Result.UNKNOWN, Game.Result.CANCELLED -> "RESULT_UNKNOWN"
|
||||||
|
Game.Result.BLACK -> "RESULT_BLACKWINS"
|
||||||
|
Game.Result.WHITE -> "RESULT_WHITEWINS"
|
||||||
|
Game.Result.JIGO -> "RESULT_EQUAL"
|
||||||
|
Game.Result.BOTHWIN -> "RESULT_BOTHWIN"
|
||||||
|
Game.Result.BOTHLOOSE -> "RESULT_BOTHLOOSE"
|
||||||
|
else -> throw Error("unhandled game result")
|
||||||
|
}
|
||||||
|
}" roundNumber="${
|
||||||
|
round + 1
|
||||||
|
}" tableNumber="${
|
||||||
|
table + 1
|
||||||
|
}" whitePlayer="${
|
||||||
|
(tournament.pairables[game.white]!! as Player).let { white ->
|
||||||
|
"${white.name}${white.firstname}".uppercase(Locale.ENGLISH) // Use Locale.ENGLISH to transform é to É
|
||||||
|
}
|
||||||
|
}"/>"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</Games>
|
||||||
|
<ByePlayer>
|
||||||
|
</ByePlayer>
|
||||||
|
<TournamentParameterSet>
|
||||||
|
<GeneralParameterSet bInternet="${tournament.online}" basicTime="${tournament.timeSystem.mainTime}" beginDate="${tournament.startDate}" canByoYomiTime="${tournament.timeSystem.byoyomi}" complementaryTimeSystem="${when(tournament.timeSystem.type) {
|
||||||
|
TimeSystem.TimeSystemType.SUDDEN_DEATH -> "SUDDENDEATH"
|
||||||
|
TimeSystem.TimeSystemType.STANDARD -> "STDBYOYOMI"
|
||||||
|
TimeSystem.TimeSystemType.CANADIAN -> "CANBYOYOMI"
|
||||||
|
TimeSystem.TimeSystemType.FISCHER -> "FISCHER"
|
||||||
|
} }" director="" endDate="${tournament.endDate}" fischerTime="${tournament.timeSystem.increment}" genCountNotPlayedGamesAsHalfPoint="false" genMMBar="9D" genMMFloor="30K" genMMS2ValueAbsent="1" genMMS2ValueBye="2" genMMZero="30K" genNBW2ValueAbsent="0" genNBW2ValueBye="2" genRoundDownNBWMMS="true" komi="${tournament.komi}" location="${tournament.location}" name="${tournament.name}" nbMovesCanTime="${tournament.timeSystem.stones}" numberOfCategories="1" numberOfRounds="${tournament.rounds}" shortName="${tournament.shortName}" size="${tournament.gobanSize}" stdByoYomiTime="${tournament.timeSystem.byoyomi}"/>
|
||||||
|
<HandicapParameterSet hdBasedOnMMS="false" hdCeiling="0" hdCorrection="0" hdNoHdRankThreshold="30K"/>
|
||||||
|
<PlacementParameterSet>
|
||||||
|
<PlacementCriteria>
|
||||||
|
<PlacementCriterion name="NBW" number="1"/>
|
||||||
|
<PlacementCriterion name="SOSW" number="2"/>
|
||||||
|
<PlacementCriterion name="SOSOSW" number="3"/>
|
||||||
|
<PlacementCriterion name="NULL" number="4"/>
|
||||||
|
<PlacementCriterion name="NULL" number="5"/>
|
||||||
|
<PlacementCriterion name="NULL" number="6"/>
|
||||||
|
</PlacementCriteria>
|
||||||
|
</PlacementParameterSet>
|
||||||
|
<PairingParameterSet paiBaAvoidDuplGame="500000000000000" paiBaBalanceWB="1000000" paiBaDeterministic="true" paiBaRandom="0" paiMaAdditionalPlacementCritSystem1="Rating" paiMaAdditionalPlacementCritSystem2="Rating" paiMaAvoidMixingCategories="0" paiMaCompensateDUDD="true" paiMaDUDDLowerMode="MID" paiMaDUDDUpperMode="MID" paiMaDUDDWeight="100000000" paiMaLastRoundForSeedSystem1="2" paiMaMaximizeSeeding="5000000" paiMaMinimizeScoreDifference="100000000000" paiMaSeedSystem1="SPLITANDSLIP" paiMaSeedSystem2="SPLITANDSLIP" paiSeAvoidSameGeo="0" paiSeBarThresholdActive="true" paiSeDefSecCrit="20000000000000" paiSeMinimizeHandicap="0" paiSeNbWinsThresholdActive="true" paiSePreferMMSDiffRatherThanSameClub="0" paiSePreferMMSDiffRatherThanSameCountry="0" paiSeRankThreshold="30K" paiStandardNX1Factor="0.5"/>
|
||||||
|
<DPParameterSet displayClCol="true" displayCoCol="true" displayIndGamesInMatches="true" displayNPPlayers="false" displayNumCol="true" displayPlCol="true" gameFormat="short" playerSortType="name" showByePlayer="true" showNotFinallyRegisteredPlayers="true" showNotPairedPlayers="true" showNotParticipatingPlayers="false" showPlayerClub="true" showPlayerCountry="false" showPlayerGrade="true"/>
|
||||||
|
<PublishParameterSet exportToLocalFile="true" htmlAutoScroll="false" print="false"/>
|
||||||
|
</TournamentParameterSet>
|
||||||
|
<TeamTournamentParameterSet>
|
||||||
|
<TeamGeneralParameterSet teamSize="4"/>
|
||||||
|
<TeamPlacementParameterSet>
|
||||||
|
<PlacementCriteria>
|
||||||
|
<PlacementCriterion name="TEAMP" number="1"/>
|
||||||
|
<PlacementCriterion name="BDW" number="2"/>
|
||||||
|
<PlacementCriterion name="BDW3U" number="3"/>
|
||||||
|
<PlacementCriterion name="BDW2U" number="4"/>
|
||||||
|
<PlacementCriterion name="BDW1U" number="5"/>
|
||||||
|
<PlacementCriterion name="MNR" number="6"/>
|
||||||
|
</PlacementCriteria>
|
||||||
|
</TeamPlacementParameterSet>
|
||||||
|
</TeamTournamentParameterSet>
|
||||||
|
</Tournament>
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
return xml
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -213,7 +213,7 @@ class ApiServlet : HttpServlet() {
|
|||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"Missing 'Accept' header"
|
"Missing 'Accept' header"
|
||||||
)
|
)
|
||||||
if (!isJson(accept)) throw ApiException(
|
if (!isJson(accept) && (!isXml(accept) || !request.requestURI.matches(Regex("/api/tour/\\d+")))) throw ApiException(
|
||||||
HttpServletResponse.SC_BAD_REQUEST,
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
"Invalid 'Accept' header"
|
"Invalid 'Accept' header"
|
||||||
)
|
)
|
||||||
@@ -260,7 +260,7 @@ class ApiServlet : HttpServlet() {
|
|||||||
private const val EXPECTED_CHARSET = "utf8"
|
private const val EXPECTED_CHARSET = "utf8"
|
||||||
const val AUTH_HEADER = "Authorization"
|
const val AUTH_HEADER = "Authorization"
|
||||||
const val AUTH_PREFIX = "Bearer"
|
const val AUTH_PREFIX = "Bearer"
|
||||||
private fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
|
fun isJson(mimeType: String) = "text/json" == mimeType || "application/json" == mimeType || mimeType.endsWith("+json")
|
||||||
private fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
|
fun isXml(mimeType: String) = "text/xml" == mimeType || "application/xml" == mimeType || mimeType.endsWith("+xml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
package org.jeudego.pairgoth.test
|
package org.jeudego.pairgoth.test
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
class ImportTests: TestBase() {
|
class ImportExportTests: TestBase() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `001 test imports`() {
|
fun `001 test imports`() {
|
||||||
@@ -21,6 +20,8 @@ class ImportTests: TestBase() {
|
|||||||
val games = TestAPI.get("/api/tour/$id/res/1").asArray()
|
val games = TestAPI.get("/api/tour/$id/res/1").asArray()
|
||||||
logger.info("games for round $round: {}", games.toString())
|
logger.info("games for round $round: {}", games.toString())
|
||||||
}
|
}
|
||||||
|
val xml = TestAPI.getXml("/api/tour/$id")
|
||||||
|
logger.info(xml)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -34,7 +34,7 @@ object TestAPI {
|
|||||||
private val apiServlet = ApiServlet()
|
private val apiServlet = ApiServlet()
|
||||||
private val sseServlet = SSEServlet()
|
private val sseServlet = SSEServlet()
|
||||||
|
|
||||||
private fun <T> testRequest(reqMethod: String, uri: String, payload: T? = null): Json {
|
private fun <T> testRequest(reqMethod: String, uri: String, accept: String = "application/json", payload: T? = null): String {
|
||||||
|
|
||||||
WebappManager.properties["webapp.env"] = "test"
|
WebappManager.properties["webapp.env"] = "test"
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ object TestAPI {
|
|||||||
else -> throw Error("unhandled case")
|
else -> throw Error("unhandled case")
|
||||||
}
|
}
|
||||||
on { headerNames } doReturn Collections.enumeration(myHeaderNames)
|
on { headerNames } doReturn Collections.enumeration(myHeaderNames)
|
||||||
on { getHeader(eq("Accept")) } doReturn "application/json"
|
on { getHeader(eq("Accept")) } doReturn accept
|
||||||
}
|
}
|
||||||
|
|
||||||
// mock response
|
// mock response
|
||||||
@@ -83,13 +83,14 @@ object TestAPI {
|
|||||||
"DELETE" -> apiServlet.doDelete(req, resp)
|
"DELETE" -> apiServlet.doDelete(req, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Json.parse(buffer.toString()) ?: throw Error("no response payload")
|
return buffer.toString() ?: throw Error("no response payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun get(uri: String) = testRequest<Void>("GET", uri)
|
fun get(uri: String): Json = Json.parse(testRequest<Void>("GET", uri)) ?: throw Error("no payload")
|
||||||
fun <T> post(uri: String, payload: T) = testRequest("POST", uri, payload)
|
fun getXml(uri: String): String = testRequest<Void>("GET", uri, "application/xml")
|
||||||
fun <T> put(uri: String, payload: T) = testRequest("PUT", uri, payload)
|
fun <T> post(uri: String, payload: T) = Json.parse(testRequest("POST", uri, payload = payload)) ?: throw Error("no payload")
|
||||||
fun <T> delete(uri: String, payload: T) = testRequest("DELETE", uri, payload)
|
fun <T> put(uri: String, payload: T) = Json.parse(testRequest("PUT", uri, payload = payload)) ?: throw Error("no payload")
|
||||||
|
fun <T> delete(uri: String, payload: T) = Json.parse(testRequest("DELETE", uri, payload = payload)) ?: throw Error("no payload")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of resources
|
// Get a list of resources
|
||||||
|
Reference in New Issue
Block a user