Merge from webview2

This commit is contained in:
Claude Brisson
2023-12-24 00:10:03 +01:00
214 changed files with 11364 additions and 81708 deletions

View File

@@ -59,10 +59,20 @@
<configuration>
<scan>0</scan>
<httpConnector>
<port>8085</port>
<host>${pairgoth.api.host}</host>
<port>${pairgoth.api.port}</port>
</httpConnector>
<systemProperties>
<pairgoth.env>${pairgoth.env}</pairgoth.env>
<pairgoth.api.external.url>${pairgoth.api.external.url}</pairgoth.api.external.url>
<pairgoth.webapp.external.url>${pairgoth.webapp.external.url}</pairgoth.webapp.external.url>
<pairgoth.store>${pairgoth.store}</pairgoth.store>
<pairgoth.store.file.path>${pairgoth.store.file.path}</pairgoth.store.file.path>
<pairgoth.logger.level>${pairgoth.logger.level}</pairgoth.logger.level>
<pairgoth.logger.format>${pairgoth.logger.format}</pairgoth.logger.format>
</systemProperties>
<webApp>
<contextPath>/api/</contextPath>
<contextPath>${pairgoth.api.context}/</contextPath>
</webApp>
</configuration>
</plugin>
@@ -134,6 +144,12 @@
<artifactId>jakarta.mail</artifactId>
<version>1.6.7</version>
</dependency>
<!-- utils -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- auth -->
<dependency>
<groupId>org.pac4j</groupId>

View File

@@ -1,7 +1,7 @@
package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import org.jeudego.pairgoth.web.ApiException
import org.jeudego.pairgoth.server.ApiException
import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

View File

@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.api
import com.republicate.kson.Json
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.server.Event
import javax.servlet.http.HttpServletRequest
interface PairgothApiHandler: ApiHandler {

View File

@@ -6,7 +6,8 @@ import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.toID
import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.web.Event.*
import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.server.Event.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@@ -15,10 +16,18 @@ object PairingHandler: PairgothApiHandler {
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
if (round > tournament.lastRound() + 1) badRequest("invalid round: previous round has not been played")
val playing = tournament.games(round).values.flatMap {
listOf(it.black, it.white)
}.toSet()
return tournament.pairables.values.filter { !it.skip.contains(round) && !playing.contains(it.id) }.map { it.id }.toJsonArray()
val unpairables = tournament.pairables.values.filter { it.skip.contains(round) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
val pairables = tournament.pairables.values.filter { !it.skip.contains(round) && !playing.contains(it.id) }.sortedByDescending { it.rating }.map { it.id }.toJsonArray()
val games = tournament.games(round).values
return Json.Object(
"games" to games.map { it.toJson() }.toCollection(Json.MutableArray()),
"pairables" to pairables,
"unpairables" to unpairables
)
}
override fun post(request: HttpServletRequest): Json {
@@ -54,9 +63,19 @@ object PairingHandler: PairgothApiHandler {
// only allow last round (if players have not been paired in the last round, it *may* be possible to be more laxist...)
if (round != tournament.lastRound()) badRequest("cannot edit pairings in other rounds but the last")
val payload = getObjectPayload(request)
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
val gameId = payload.getInt("id") ?: badRequest("invalid game id")
val game = tournament.games(round)[gameId] ?: badRequest("invalid game id")
val playing = (tournament.games(round).values).filter { it.id != gameId }.flatMap {
listOf(it.black, it.white)
}.toSet()
game.black = payload.getID("b") ?: badRequest("missing black player id")
game.white = payload.getID("w") ?: badRequest("missing white player id")
val black = tournament.pairables[game.black] ?: badRequest("invalid black player id")
val white = tournament.pairables[game.black] ?: badRequest("invalid white player id")
if (black.skip.contains(round)) badRequest("black is not playing this round")
if (white.skip.contains(round)) badRequest("white is not playing this round")
if (playing.contains(black.id)) badRequest("black is already in another game")
if (playing.contains(white.id)) badRequest("white is already in another game")
if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap")
tournament.dispatchEvent(gameUpdated, Json.Object("round" to round, "game" to game.toJson()))
return Json.Object("success" to true)

View File

@@ -5,8 +5,8 @@ import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.Player
import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.web.Event.*
import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.server.Event.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

View File

@@ -5,7 +5,7 @@ import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.server.Event
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

View File

@@ -4,8 +4,8 @@ import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.TeamTournament
import org.jeudego.pairgoth.web.Event
import org.jeudego.pairgoth.web.Event.*
import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.server.Event.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

View File

@@ -10,9 +10,9 @@ import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.model.toJson
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.server.ApiServlet
import org.jeudego.pairgoth.server.Event
import org.jeudego.pairgoth.server.Event.*
import org.w3c.dom.Element
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
@@ -64,8 +64,7 @@ object TournamentHandler: PairgothApiHandler {
clear()
putAll(tournament.games(round))
}
Store.replaceTournament(updated)
tournament.dispatchEvent(tournamentUpdated, tournament.toJson())
updated.dispatchEvent(tournamentUpdated, updated.toJson())
return Json.Object("success" to true)
}

View File

@@ -151,6 +151,7 @@ object OpenGotha {
it.value.map { game ->
Game(
id = Store.nextGameId,
table = game.tableNumber,
black = canonicMap[game.blackPlayer] ?: throw Error("player not found: ${game.blackPlayer}"),
white = canonicMap[game.whitePlayer] ?: throw Error("player not found: ${game.whitePlayer}"),
handicap = game.handicap,

View File

@@ -6,6 +6,7 @@ import java.util.*
data class Game(
val id: ID,
var table: Int,
var white: ID,
var black: ID,
var handicap: Int = 0,
@@ -33,6 +34,7 @@ data class Game(
fun Game.toJson() = Json.Object(
"id" to id,
"t" to table,
"w" to white,
"b" to black,
"h" to handicap,
@@ -42,6 +44,7 @@ fun Game.toJson() = Json.Object(
fun Game.Companion.fromJson(json: Json.Object) = Game(
id = json.getID("id") ?: throw Error("missing game id"),
table = json.getInt("t") ?: throw Error("missing game table"),
white = json.getID("w") ?: throw Error("missing white player"),
black = json.getID("b") ?: throw Error("missing black player"),
handicap = json.getInt("h") ?: 0,

View File

@@ -3,6 +3,7 @@ package org.jeudego.pairgoth.model
import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.store.Store
import java.util.*
// Pairable
@@ -51,6 +52,13 @@ fun Pairable.Companion.parseRank(rankStr: String): Int {
// Player
enum class DatabaseId {
AGA,
EGF,
FFG;
val key get() = this.name.lowercase(Locale.ROOT)
}
class Player(
id: ID,
name: String,
@@ -62,7 +70,7 @@ class Player(
): Pairable(id, name, rating, rank) {
companion object
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
val externalIds = mutableMapOf<String, String>()
val externalIds = mutableMapOf<DatabaseId, String>()
override fun toJson(): Json.Object = Json.MutableObject(
"id" to id,
"name" to name,
@@ -71,8 +79,11 @@ class Player(
"rank" to rank,
"country" to country,
"club" to club
).also {
if (skip.isNotEmpty()) it["skip"] = Json.Array(skip)
).also { json ->
if (skip.isNotEmpty()) json["skip"] = Json.Array(skip)
externalIds.forEach { (dbid, id) ->
json[dbid.key] = id
}
}
override fun nameSeed(separator: String): String {
return name + separator + firstname
@@ -92,4 +103,9 @@ fun Player.Companion.fromJson(json: Json.Object, default: Player? = null) = Play
json.getArray("skip")?.let {
if (it.isNotEmpty()) player.skip.addAll(it.map { id -> (id as Number).toInt() })
}
DatabaseId.values().forEach { dbid ->
json.getString(dbid.key)?.let { id ->
player.externalIds[dbid] = id
}
}
}

View File

@@ -49,7 +49,7 @@ data class MainCritParams(
val additionalPlacementCritSystem1: Criterion = Criterion.RATING,
val additionalPlacementCritSystem2: Criterion = Criterion.NONE,
) {
enum class DrawUpDown {TOP, MIDDLE, BOTTOM}
enum class DrawUpDown { TOP, MIDDLE, BOTTOM }
enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP }
companion object {
const val MAX_CATEGORIES_WEIGHT = 20000000000000.0 // 2e13
@@ -185,7 +185,7 @@ class MacMahon(
handicap = HandicapParams(
weight = MainCritParams.MAX_SCORE_WEIGHT, // TODO - contradictory with the comment above (not used anyway ?!)
useMMS = true,
rankThreshold = 0,
rankThreshold = -20,
ceiling = 9
)
),

View File

@@ -3,7 +3,7 @@ package org.jeudego.pairgoth.oauth
// In progress
import com.republicate.kson.Json
import org.jeudego.pairgoth.web.WebappManager
import org.jeudego.pairgoth.server.WebappManager
//import com.republicate.modality.util.AESCryptograph
//import com.republicate.modality.util.Cryptograph
import org.apache.commons.codec.binary.Base64
@@ -21,7 +21,7 @@ abstract class OAuthHelper {
protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".secret")
protected val redirectURI: String?
protected get() = try {
val uri: String = WebappManager.Companion.getProperty("webapp.url") + "/oauth.html"
val uri: String = WebappManager.getProperty("webapp.external.url") + "/oauth.html"
URLEncoder.encode(uri, "UTF-8")
} catch (uee: UnsupportedEncodingException) {
logger.error("could not encode redirect URI", uee)

View File

@@ -1,6 +1,7 @@
package org.jeudego.pairgoth.pairing
import org.jeudego.pairgoth.model.*
import java.util.*
abstract class BasePairingHelper(
history: List<List<Game>>, // History of all games played for each round
@@ -131,4 +132,11 @@ abstract class BasePairingHelper(
open fun nameSort(p: Pairable, q: Pairable): Int {
return if (p.name > q.name) 1 else -1
}
val tables = history.mapTo(mutableListOf()) { games ->
games.map { it.table }.fold(BitSet()) { acc, table ->
acc.set(table)
acc
}
}
}

View File

@@ -28,7 +28,7 @@ sealed class BaseSolver(
companion object {
val rand = Random(/* seed from properties - TODO */)
val DEBUG_EXPORT_WEIGHT = true
val DEBUG_EXPORT_WEIGHT = false
var byePlayers: MutableList<Pairable> = mutableListOf()
}
@@ -123,7 +123,7 @@ sealed class BaseSolver(
var result = sorted.flatMap { games(white = it[0], black = it[1]) }
// add game for ByePlayer
if (chosenByePlayer != ByePlayer) result += Game(id = Store.nextGameId, white = ByePlayer.id, black = chosenByePlayer.id, result = Game.Result.fromSymbol('b'))
if (chosenByePlayer != ByePlayer) result += Game(id = Store.nextGameId, table = 0, white = ByePlayer.id, black = chosenByePlayer.id, result = Game.Result.fromSymbol('b'))
if (DEBUG_EXPORT_WEIGHT) {
//println("DUDD debug")
@@ -535,7 +535,9 @@ sealed class BaseSolver(
open fun games(black: Pairable, white: Pairable): List<Game> {
// CB TODO team of individuals pairing
return listOf(Game(id = Store.nextGameId, black = black.id, white = white.id, handicap = pairing.handicap.handicap(black, white), drawnUpDown = white.group-black.group))
val usedTables = tables.getOrNull(round - 1) ?: BitSet().also { tables.add(it) }
val table = if (black.id == 0 || white.id == 0) 0 else usedTables.nextClearBit(1)
usedTables.set(table)
return listOf(Game(id = Store.nextGameId, table = table, black = black.id, white = white.id, handicap = pairing.handicap.handicap(white, black), drawnUpDown = white.group-black.group))
}
}

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import com.republicate.kson.Json
import java.io.IOException

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler
@@ -23,7 +23,7 @@ import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class ApiServlet : HttpServlet() {
class ApiServlet: HttpServlet() {
public override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
doRequest(request, response)
@@ -62,7 +62,7 @@ class ApiServlet : HttpServlet() {
// validate request
if ("dev" == WebappManager.getProperty("webapp.env")) {
if ("dev" == WebappManager.getProperty("env")) {
response.addHeader("Access-Control-Allow-Origin", "*")
}
validateAccept(request);
@@ -90,7 +90,7 @@ class ApiServlet : HttpServlet() {
"team" -> TeamHandler
else -> ApiHandler.badRequest("unknown sub-entity: $subEntity")
}
"player" -> PlayerHandler
// "player" -> PlayerHandler
else -> ApiHandler.badRequest("unknown entity: $entity")
}
@@ -235,7 +235,7 @@ class ApiServlet : HttpServlet() {
cause: Throwable? = null
) {
try {
if (code == 500) {
if (code == 500 || response.isCommitted) {
logger.error(
"Request {} {} gave error {} {}",
request.method,
@@ -245,7 +245,14 @@ class ApiServlet : HttpServlet() {
cause
)
}
response.sendError(code, message)
response.status = code
if (response.isCommitted) return
val errorPayload = Json.Object(
"success" to false,
"error" to (message ?: "unknown error")
)
setContentType(response)
errorPayload.toString(response.writer)
} catch (ioe: IOException) {
logger.error("Could not send back error", ioe)
}

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import info.macias.sse.events.MessageEvent
import java.util.concurrent.atomic.AtomicLong

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import com.republicate.kson.Json
import org.jeudego.pairgoth.util.Colorizer.blue
@@ -15,9 +15,11 @@ fun Logger.logRequest(req: HttpServletRequest, logHeaders: Boolean = false) {
.append(req.localName)
val port = req.localPort
if (port != 80) builder.append(':').append(port)
/*
if (!req.contextPath.isEmpty()) {
builder.append(req.contextPath)
}
*/
builder.append(req.requestURI)
if (req.method == "GET") {
val qs = req.queryString

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import info.macias.sse.EventBroadcast
import info.macias.sse.events.MessageEvent

View File

@@ -1,4 +1,4 @@
package org.jeudego.pairgoth.web
package org.jeudego.pairgoth.server
import com.republicate.mailer.SmtpLoop
import org.apache.commons.lang3.tuple.Pair
@@ -51,8 +51,7 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
/* ServletContextListener interface */
override fun contextInitialized(sce: ServletContextEvent) {
context = sce.servletContext
logger.info("---------- Starting Pairgoth Server ----------")
context.setAttribute("manager", this)
logger.info("---------- Starting $WEBAPP_NAME ----------")
webappRoot = context.getRealPath("/")
try {
// load default properties
@@ -63,7 +62,8 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
}
logger.info("Using profile {}", properties.getProperty("webapp.env"))
val env = properties.getProperty("env")
logger.info("Using profile $env", )
// set system user agent string to empty string
System.setProperty("http.agent", "")
@@ -84,11 +84,15 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
}
override fun contextDestroyed(sce: ServletContextEvent) {
logger.info("---------- Stopping Web Application ----------")
logger.info("---------- Stopping $WEBAPP_NAME ----------")
stopService("smtp");
val context = sce.servletContext
for (service in webServices.keys) stopService(service, true)
// ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
logger.info("---------- Stopped $WEBAPP_NAME ----------")
}
/* ServletContextAttributeListener interface */
@@ -101,6 +105,7 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
override fun sessionDestroyed(se: HttpSessionEvent) {}
companion object {
const val WEBAPP_NAME = "Pairgoth API Server"
const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
lateinit var webappRoot: String
lateinit var context: ServletContext
@@ -111,10 +116,10 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
return properties.getProperty(prop)
}
fun getMandatoryProperty(prop: String): String {
return properties.getProperty(prop) ?: throw Error("missing property: ${prop}")
return getProperty(prop) ?: throw Error("missing property: ${prop}")
}
val webappURL by lazy { getProperty("webapp.url") }
val webappURL by lazy { getProperty("webapp.external.url") }
private val services = mutableMapOf<String, Pair<Runnable, Thread>>()

View File

@@ -10,7 +10,7 @@ import org.jeudego.pairgoth.model.fromJson
import org.jeudego.pairgoth.model.getID
import org.jeudego.pairgoth.model.toFullJson
import org.jeudego.pairgoth.model.toID
import org.jeudego.pairgoth.model.toJson
import java.lang.Integer.max
import java.nio.file.Path
import java.text.DateFormat
import java.text.SimpleDateFormat
@@ -64,11 +64,15 @@ class FileStore(pathStr: String): StoreImplementation {
}
val json = Json.parse(path.resolve(file).readText())?.asObject() ?: throw Error("could not read tournament")
val tournament = Tournament.fromJson(json)
var maxPlayerId = 0
var maxGameId = 0
val players = json["players"] as Json.Array? ?: Json.Array()
tournament.players.putAll(
players.associate {
(it as Json.Object).let { player ->
Pair(player.getID("id") ?: throw Error("invalid tournament file"), Player.fromJson(player))
Pair(player.getID("id") ?: throw Error("invalid tournament file"), Player.fromJson(player)).also {
maxPlayerId = max(maxPlayerId, it.first)
}
}
}
)
@@ -77,29 +81,41 @@ class FileStore(pathStr: String): StoreImplementation {
tournament.teams.putAll(
teams.associate {
(it as Json.Object).let { team ->
Pair(team.getID("id") ?: throw Error("invalid tournament file"), tournament.teamFromJson(team))
Pair(team.getID("id") ?: throw Error("invalid tournament file"), tournament.teamFromJson(team)).also {
maxPlayerId = max(maxPlayerId, it.first)
}
}
}
)
}
val games = json["games"] as Json.Array? ?: Json.Array()
(1..games.size).forEach { round ->
var nextDefaultTable = 1;
val roundGames = games[round - 1] as Json.Array
tournament.games(round).putAll(
games.associate {
roundGames.associate {
(it as Json.Object).let { game ->
Pair(game.getID("id") ?: throw Error("invalid tournament file"), Game.fromJson(game))
val fixedGame =
if (game.containsKey("t")) game
else Json.MutableObject(game).set("t", nextDefaultTable++)
Pair(game.getID("id") ?: throw Error("invalid tournament file"), Game.fromJson(fixedGame)).also {
maxGameId = max(maxGameId, it.first)
}
}
}
)
}
_nextPlayerId.set(maxPlayerId + 1)
_nextGameId.set(maxGameId + 1)
return tournament
}
override fun replaceTournament(tournament: Tournament<*>) {
val filename = tournament.filename()
val file = path.resolve(filename).toFile()
if (!file.exists()) throw Error("File $filename does not exist")
file.renameTo(path.resolve(filename + "-${timestamp}").toFile())
if (file.exists()) {
file.renameTo(path.resolve(filename + "-${timestamp}").toFile())
}
addTournament(tournament)
}

View File

@@ -2,14 +2,14 @@ package org.jeudego.pairgoth.store
import org.jeudego.pairgoth.model.ID
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.web.WebappManager
import org.jeudego.pairgoth.server.WebappManager
import java.util.concurrent.atomic.AtomicInteger
private fun createStoreImplementation(): StoreImplementation {
return when (val storeProperty = WebappManager.getProperty("store") ?: "memory") {
"memory" -> MemoryStore()
"file" -> {
val filePath = WebappManager.getMandatoryProperty("store.file.path") ?: "."
val filePath = WebappManager.getProperty("store.file.path") ?: "."
FileStore(filePath)
}
else -> throw Error("unknown store: $storeProperty")

View File

@@ -1,6 +1,19 @@
# webapp
webapp.env = dev
webapp.url = http://localhost:8080
# environment
env = dev
# webapp connector
webapp.protocol = http
webapp.interface = localhost
webapp.port = 8080
webapp.context = /
webapp.external.url = http://localhost:8080
# api connector
api.protocol = http
api.interface = localhost
api.port = 8085
api.context = /api
api.external.url = http://localhost:8085/api
# store
store = file

View File

@@ -8,7 +8,7 @@
<listener-class>com.republicate.slf4j.impl.ServletContextLoggerListener</listener-class>
</listener>
<listener>
<listener-class>org.jeudego.pairgoth.web.WebappManager</listener-class>
<listener-class>org.jeudego.pairgoth.server.WebappManager</listener-class>
</listener>
<!-- filters -->
@@ -29,11 +29,11 @@
<!-- servlets -->
<servlet>
<servlet-name>api</servlet-name>
<servlet-class>org.jeudego.pairgoth.web.ApiServlet</servlet-class>
<servlet-class>org.jeudego.pairgoth.server.ApiServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>sse</servlet-name>
<servlet-class>org.jeudego.pairgoth.web.SSEServlet</servlet-class>
<servlet-class>org.jeudego.pairgoth.server.SSEServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

View File

@@ -180,7 +180,7 @@ class BasicTests: TestBase() {
"""[{"id":$aTournamentGameID,"w":$anotherPlayerID,"b":$aPlayerID,"h":0,"r":"?","dd":0}]"""
)
assertTrue(possibleResults.contains(games.toString()), "pairing differs")
games = TestAPI.get("/api/tour/$aTournamentID/res/1").asArray()
games = TestAPI.get("/api/tour/$aTournamentID/res/1").asObject().getArray("games")!!
assertTrue(possibleResults.contains(games.toString()), "results differs")
val empty = TestAPI.get("/api/tour/$aTournamentID/pair/1").asArray()
assertEquals("[]", empty.toString(), "no more pairables for round 1")

View File

@@ -2,9 +2,9 @@ package org.jeudego.pairgoth.test
import com.republicate.kson.Json
import org.jeudego.pairgoth.api.ApiHandler
import org.jeudego.pairgoth.web.ApiServlet
import org.jeudego.pairgoth.web.SSEServlet
import org.jeudego.pairgoth.web.WebappManager
import org.jeudego.pairgoth.server.ApiServlet
import org.jeudego.pairgoth.server.SSEServlet
import org.jeudego.pairgoth.server.WebappManager
import org.mockito.kotlin.*
import java.io.*
import java.util.*