Events model and SSE servlet ready

This commit is contained in:
Claude Brisson
2023-05-19 18:54:39 +02:00
parent 45873d6014
commit 605b39123e
8 changed files with 81 additions and 26 deletions

View File

@@ -4,9 +4,9 @@ import com.republicate.kson.Json
import com.republicate.kson.toJsonArray
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.model.Pairing
import org.jeudego.pairgoth.model.Tournament
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
object PairingHandler: PairgothApiHandler {
@@ -42,6 +42,27 @@ object PairingHandler: PairgothApiHandler {
} ?: badRequest("invalid pairable id: #$id")
}
val games = tournament.pair(round, pairables)
return games.map { it.toJson() }.toJsonArray()
val ret = games.map { it.toJson() }.toJsonArray()
Event.dispatch(gamesAdded, Json.Object("tournament" to tournament.id, "round" to round, "data" to ret))
return ret
}
override fun delete(request: HttpServletRequest): Json {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
// 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.games.size) badRequest("cannot delete games in other rounds but the last")
val payload = getArrayPayload(request)
val allPlayers = payload.size == 1 && payload[0] == "all"
if (allPlayers) {
tournament.games.removeLast()
} else {
payload.forEach {
val id = (it as Number).toInt()
tournament.games[round].remove(id)
}
}
Event.dispatch(gamesDeleted, Json.Object("tournament" to tournament.id, "round" to round, "data" to payload))
return Json.Object("success" to true)
}
}

View File

@@ -5,6 +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 javax.servlet.http.HttpServletRequest
object PlayerHandler: PairgothApiHandler {
@@ -23,7 +25,7 @@ object PlayerHandler: PairgothApiHandler {
// player parsing (CB TODO - team handling, based on tournament type)
val player = Player.fromJson(payload)
tournament.pairables[player.id] = player
// CB TODO - handle event broadcasting
Event.dispatch(playerAdded, Json.Object("tournament" to tournament.id, "data" to player.toJson()))
return Json.Object("success" to true, "id" to player.id)
}
@@ -34,10 +36,16 @@ object PlayerHandler: PairgothApiHandler {
val payload = getObjectPayload(request)
val updated = Player.fromJson(payload, player as Player)
tournament.pairables[updated.id] = updated
Event.dispatch(playerUpdated, Json.Object("tournament" to tournament.id, "data" to player.toJson()))
return Json.Object("success" to true)
}
override fun delete(request: HttpServletRequest): Json {
return super.delete(request)
val tournament = getTournament(request) ?: badRequest("invalid tournament")
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
val player = tournament.pairables[id] ?: badRequest("invalid player id")
tournament.pairables.remove(id)
Event.dispatch(playerDeleted, Json.Object("tournament" to tournament.id, "data" to id))
return Json.Object("success" to true)
}
}

View File

@@ -7,6 +7,7 @@ import org.jeudego.pairgoth.model.Game
import org.jeudego.pairgoth.model.Tournament
import org.jeudego.pairgoth.model.toJson
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.web.Event
import javax.servlet.http.HttpServletRequest
object ResultsHandler: PairgothApiHandler {
@@ -18,30 +19,13 @@ object ResultsHandler: PairgothApiHandler {
return games.map { it.toJson() }.toJsonArray()
}
override fun post(request: HttpServletRequest): Json {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val payload = getObjectPayload(request)
val game = tournament.games[round][payload.getInt("id")] ?: badRequest("invalid game id")
game.result = Game.Result.valueOf(payload.getString("result") ?: badRequest("missing result"))
return Json.Object("success" to true)
}
override fun put(request: HttpServletRequest): Json {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val payload = getObjectPayload(request)
val game = tournament.games[round][payload.getInt("id")] ?: badRequest("invalid game id")
game.result = Game.Result.valueOf(payload.getString("result") ?: badRequest("missing result"))
return Json.Object("success" to true)
}
override fun delete(request: HttpServletRequest): Json {
val tournament = getTournament(request)
val round = getSubSelector(request)?.toIntOrNull() ?: badRequest("invalid round number")
val payload = getObjectPayload(request)
val game = tournament.games[round][payload.getInt("id")] ?: badRequest("invalid game id")
tournament.games[round].remove(payload.getInt("id") ?: badRequest("invalid game id")) ?: badRequest("invalid game id")
Event.dispatch(Event.resultUpdated, Json.Object("tournament" to tournament.id, "round" to round, "data" to game))
return Json.Object("success" to true)
}
}

View File

@@ -8,6 +8,8 @@ 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.Event
import org.jeudego.pairgoth.web.Event.*
import org.w3c.dom.Element
import javax.servlet.http.HttpServletRequest
@@ -26,8 +28,8 @@ object TournamentHandler: PairgothApiHandler {
is Element -> OpenGotha.import(payload)
else -> badRequest("missing or invalid payload")
}
Store.addTournament(tournament)
Event.dispatch(tournamentAdded, tournament.toJson())
return Json.Object("success" to true, "id" to tournament.id)
}
@@ -42,12 +44,14 @@ object TournamentHandler: PairgothApiHandler {
updated.games.addAll(tournament.games)
updated.criteria.addAll(tournament.criteria)
Store.replaceTournament(updated)
Event.dispatch(tournamentUpdated, tournament.toJson())
return Json.Object("success" to true)
}
override fun delete(request: HttpServletRequest): Json {
val tournament = getTournament(request) ?: badRequest("missing or invalid tournament id")
Store.deleteTournament(tournament)
Event.dispatch(tournamentDeleted, tournament.id)
return Json.Object("success" to true)
}
}

View File

@@ -0,0 +1,27 @@
package org.jeudego.pairgoth.web
import info.macias.sse.events.MessageEvent
enum class Event {
tournamentAdded,
tournamentUpdated,
tournamentDeleted,
playerAdded,
playerUpdated,
playerDeleted,
gamesAdded,
gamesDeleted,
resultUpdated,
;
companion object {
private val sse: SSEServlet by lazy { SSEServlet.getInstance() }
private fun <T> buildEvent(event: Event, data: T) = MessageEvent.Builder()
.setEvent(event.name)
.setData(data.toString())
.build()
internal fun <T> dispatch(event: Event, data: T) {
sse.broadcast(buildEvent(event, data))
}
}
}

View File

@@ -1,6 +1,7 @@
package org.jeudego.pairgoth.web.sse
package org.jeudego.pairgoth.web
import info.macias.sse.EventBroadcast
import info.macias.sse.events.MessageEvent
import info.macias.sse.servlet3.ServletEventTarget
import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServlet
@@ -11,6 +12,12 @@ import javax.servlet.http.HttpServletResponse
class SSEServlet: HttpServlet() {
companion object {
private val logger = LoggerFactory.getLogger("sse")
private var zeInstance: SSEServlet? = null
internal fun getInstance(): SSEServlet = zeInstance ?: throw Error("SSE servlet not ready")
}
init {
if (zeInstance != null) throw Error("Multiple instances of SSE servlet found!")
zeInstance = this
}
private val broadcast = EventBroadcast()
@@ -18,4 +25,6 @@ class SSEServlet: HttpServlet() {
logger.trace("<< new channel")
broadcast.addSubscriber(ServletEventTarget(req), req.getHeader("Last-Event-Id"))
}
internal fun broadcast(message: MessageEvent) = broadcast.broadcast(message)
}

View File

@@ -33,7 +33,7 @@
</servlet>
<servlet>
<servlet-name>sse</servlet-name>
<servlet-class>org.jeudego.pairgoth.web.sse.SSEServlet</servlet-class>
<servlet-class>org.jeudego.pairgoth.web.SSEServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

View File

@@ -3,6 +3,7 @@ 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.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
@@ -31,6 +32,7 @@ object TestAPI {
fun Any?.toUnit() = Unit
private val apiServlet = ApiServlet()
private val sseServlet = SSEServlet()
private fun <T> testRequest(reqMethod: String, uri: String, payload: T? = null): Json {