Players registration
This commit is contained in:
24
test.sh
24
test.sh
@@ -1,11 +1,27 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
curl -s -D - --header "Accept: application/json" http://localhost:8080/api/tournament
|
|
||||||
|
|
||||||
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \
|
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \
|
||||||
--request POST \
|
--request POST \
|
||||||
--data '{"type":"INDIVIDUAL","name":"Mon Tournoi", "shortName": "mon-tournoi", "startDate": "2023-05-10", "endDate": "2023-05-12", "country": "FR", "location": "Marseille", "online": false, "timeSystem": { "type": "fisher", "mainTime": "1200", "increment": "10" }, "pairing": { "type": "ROUNDROBIN" } }' \
|
--data '{ "type":"INDIVIDUAL","name":"Mon Tournoi", "shortName": "mon-tournoi", "startDate": "2023-05-10", "endDate": "2023-05-12", "country": "FR", "location": "Marseille", "online": false, "timeSystem": { "type": "fisher", "mainTime": "1200", "increment": "10" }, "pairing": { "type": "ROUNDROBIN" } }' \
|
||||||
http://localhost:8080/api/tournament
|
http://localhost:8080/api/tournament
|
||||||
|
|
||||||
curl -s -D - --header "Accept: application/json" http://localhost:8080/api/tournament/1
|
curl -s --header "Accept: application/json" http://localhost:8080/api/tournament
|
||||||
|
|
||||||
|
curl -s --header "Accept: application/json" http://localhost:8080/api/tournament/1
|
||||||
|
|
||||||
|
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \
|
||||||
|
--request POST \
|
||||||
|
--data '{ "name": "Burma", "firstname": "Nestor", "rating": 1600, "rank": -2, "country": "FR", "club": "13Ma" }' \
|
||||||
|
http://localhost:8080/api/player
|
||||||
|
|
||||||
|
curl -s --header "Accept: application/json" http://localhost:8080/api/player
|
||||||
|
|
||||||
|
curl -s --header "Accept: application/json" --header "Content-Type: application/json" \
|
||||||
|
--request POST \
|
||||||
|
--data '{ "id": 1 }' \
|
||||||
|
http://localhost:8080/api/tournament/1/registration
|
||||||
|
|
||||||
|
curl -s --header "Accept: application/json" http://localhost:8080/api/tournament/1/registration
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@@ -38,16 +38,33 @@ interface ApiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getPayload(request: HttpServletRequest): Json {
|
fun getPayload(request: HttpServletRequest): Json {
|
||||||
return request.getAttribute(PAYLOAD_KEY) as Json ?: throw ApiException(HttpServletResponse.SC_BAD_REQUEST, "no payload")
|
return request.getAttribute(PAYLOAD_KEY) as Json? ?: throw ApiException(HttpServletResponse.SC_BAD_REQUEST, "no payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getObjectPayload(request: HttpServletRequest): Json.Object {
|
||||||
|
val json = request.getAttribute(PAYLOAD_KEY) as Json? ?: throw ApiException(HttpServletResponse.SC_BAD_REQUEST, "no payload")
|
||||||
|
if (!json.isObject) badRequest("expecting a json object")
|
||||||
|
return json.asObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getArrayPayload(request: HttpServletRequest): Json.Array {
|
||||||
|
val json = request.getAttribute(PAYLOAD_KEY) as Json? ?: throw ApiException(HttpServletResponse.SC_BAD_REQUEST, "no payload")
|
||||||
|
if (!json.isArray) badRequest("expecting a json array")
|
||||||
|
return json.asArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelector(request: HttpServletRequest): String? {
|
fun getSelector(request: HttpServletRequest): String? {
|
||||||
return request.getAttribute(SELECTOR_KEY) as String?
|
return request.getAttribute(SELECTOR_KEY) as String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getSubSelector(request: HttpServletRequest): String? {
|
||||||
|
return request.getAttribute(SUBSELECTOR_KEY) as String?
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PAYLOAD_KEY = "PAYLOAD"
|
const val PAYLOAD_KEY = "PAYLOAD"
|
||||||
const val SELECTOR_KEY = "SELECTOR"
|
const val SELECTOR_KEY = "SELECTOR"
|
||||||
|
const val SUBSELECTOR_KEY = "SUBSELECTOR"
|
||||||
val logger = LoggerFactory.getLogger("api")
|
val logger = LoggerFactory.getLogger("api")
|
||||||
fun badRequest(msg: String = "bad request"): Nothing = throw ApiException(HttpServletResponse.SC_BAD_REQUEST, msg)
|
fun badRequest(msg: String = "bad request"): Nothing = throw ApiException(HttpServletResponse.SC_BAD_REQUEST, msg)
|
||||||
}
|
}
|
||||||
|
@@ -4,15 +4,14 @@ import com.republicate.kson.Json
|
|||||||
import org.jeudego.pairgoth.model.Player
|
import org.jeudego.pairgoth.model.Player
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
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.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
class PlayerHandler: ApiHandler {
|
object PlayerHandler: ApiHandler {
|
||||||
|
|
||||||
override fun post(request: HttpServletRequest): Json {
|
override fun post(request: HttpServletRequest): Json {
|
||||||
val json = getPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
if (!json.isObject) ApiHandler.badRequest("expecting a json object")
|
|
||||||
val payload = json.asObject()
|
|
||||||
|
|
||||||
// player parsing
|
// player parsing
|
||||||
val player = Player.fromJson(payload)
|
val player = Player.fromJson(payload)
|
||||||
@@ -20,4 +19,17 @@ class PlayerHandler: ApiHandler {
|
|||||||
Store.addPlayer(player)
|
Store.addPlayer(player)
|
||||||
return Json.Object("success" to true, "id" to player.id)
|
return Json.Object("success" to true, "id" to player.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun get(request: HttpServletRequest): Json {
|
||||||
|
return when (val id = getSelector(request)?.toIntOrNull()) {
|
||||||
|
null -> Json.Array(Store.getPlayersIDs())
|
||||||
|
else -> Store.getPlayer(id)?.toJson() ?: ApiHandler.badRequest("no player with id #${id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun put(request: HttpServletRequest): Json {
|
||||||
|
val id = getSelector(request)?.toIntOrNull() ?: ApiHandler.badRequest("missing or invalid player selector")
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -0,0 +1,43 @@
|
|||||||
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.store.Store
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
|
object RegistrationHandler: ApiHandler {
|
||||||
|
|
||||||
|
private fun getTournament(request: HttpServletRequest): Tournament {
|
||||||
|
val tournamentId = getSelector(request)?.toIntOrNull() ?: badRequest("invalid tournament id")
|
||||||
|
return Store.getTournament(tournamentId) ?: badRequest("unknown tournament id")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(request: HttpServletRequest): Json {
|
||||||
|
val tournament = getTournament(request)
|
||||||
|
return when (val pairableId = getSubSelector(request)?.toIntOrNull()) {
|
||||||
|
null -> when (val round = request.getParameter("round")?.toIntOrNull()) {
|
||||||
|
null -> Json.Array(tournament.pairables.map {
|
||||||
|
Json.Object(
|
||||||
|
"id" to it.key,
|
||||||
|
"skip" to Json.Array(it.value)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
else -> Json.Array(tournament.pairables.filter { !it.value.contains(round) }.keys)
|
||||||
|
}
|
||||||
|
else -> Json.Array(tournament.pairables[pairableId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun post(request: HttpServletRequest): Json {
|
||||||
|
val tournament = getTournament(request)
|
||||||
|
val payload = getObjectPayload(request)
|
||||||
|
val pairableId = payload.getInt("id") ?: badRequest("missing player id")
|
||||||
|
val skip = ( payload.getArray("skip") ?: Json.Array() ).map { Json.TypeUtils.toInt(it) ?: badRequest("invalid round number") }
|
||||||
|
if (tournament.pairables.contains(pairableId)) badRequest("already registered player: $pairableId")
|
||||||
|
/* CB TODO - update action for SSE channel */
|
||||||
|
tournament.pairables[pairableId] = skip.toMutableSet()
|
||||||
|
return Json.Object("success" to true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -16,12 +16,10 @@ import org.jeudego.pairgoth.model.toJson
|
|||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
class TournamentHandler(): ApiHandler {
|
object TournamentHandler: ApiHandler {
|
||||||
|
|
||||||
override fun post(request: HttpServletRequest): Json {
|
override fun post(request: HttpServletRequest): Json {
|
||||||
val json = getPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
if (!json.isObject) badRequest("expecting a json object")
|
|
||||||
val payload = json.asObject()
|
|
||||||
|
|
||||||
// tournament parsing
|
// tournament parsing
|
||||||
val tournament = Tournament.fromJson(payload)
|
val tournament = Tournament.fromJson(payload)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package org.jeudego.pairgoth.model
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
sealed class Pairable(val id: Int, val name: String, val rating: Double, val rank: Int)
|
sealed class Pairable(val id: Int, val name: String, val rating: Double, val rank: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
fun Pairable.displayRank(): String = when {
|
fun Pairable.displayRank(): String = when {
|
||||||
rank < 0 -> "${-rank}k"
|
rank < 0 -> "${-rank}k"
|
||||||
@@ -22,3 +23,4 @@ fun Pairable.setRank(rankStr: String): Int {
|
|||||||
else -> throw Error("impossible")
|
else -> throw Error("impossible")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,8 +33,9 @@ data class Tournament(
|
|||||||
var gobanSize: Int = 19,
|
var gobanSize: Int = 19,
|
||||||
var komi: Double = 7.5
|
var komi: Double = 7.5
|
||||||
) {
|
) {
|
||||||
companion object {}
|
companion object
|
||||||
val pairables = mutableMapOf<Int, Pairable>()
|
// player/team id -> set of skipped rounds
|
||||||
|
val pairables = mutableMapOf<Int, MutableSet<Int>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
|
@@ -22,8 +22,13 @@ object Store {
|
|||||||
fun getTournament(id: Int) = tournaments[id]
|
fun getTournament(id: Int) = tournaments[id]
|
||||||
|
|
||||||
fun getTournamentsIDs(): Set<Int> = tournaments.keys
|
fun getTournamentsIDs(): Set<Int> = tournaments.keys
|
||||||
|
|
||||||
fun addPlayer(player: Player) {
|
fun addPlayer(player: Player) {
|
||||||
if (players.containsKey(player.id)) throw Error("player id #${player.id} already exists")
|
if (players.containsKey(player.id)) throw Error("player id #${player.id} already exists")
|
||||||
players[player.id] = player
|
players[player.id] = player
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPlayer(id: Int) = players[id]
|
||||||
|
|
||||||
|
fun getPlayersIDs(): Set<Int> = players.keys
|
||||||
}
|
}
|
||||||
|
@@ -2,28 +2,22 @@ package org.jeudego.pairgoth.web
|
|||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.api.ApiHandler
|
import org.jeudego.pairgoth.api.ApiHandler
|
||||||
|
import org.jeudego.pairgoth.api.RegistrationHandler
|
||||||
import org.jeudego.pairgoth.api.PlayerHandler
|
import org.jeudego.pairgoth.api.PlayerHandler
|
||||||
import org.jeudego.pairgoth.api.TournamentHandler
|
import org.jeudego.pairgoth.api.TournamentHandler
|
||||||
import org.jeudego.pairgoth.util.Colorizer
|
|
||||||
import org.jeudego.pairgoth.util.Colorizer.green
|
import org.jeudego.pairgoth.util.Colorizer.green
|
||||||
import org.jeudego.pairgoth.util.Colorizer.red
|
import org.jeudego.pairgoth.util.Colorizer.red
|
||||||
import org.jeudego.pairgoth.util.parse
|
import org.jeudego.pairgoth.util.parse
|
||||||
import org.jeudego.pairgoth.util.toString
|
import org.jeudego.pairgoth.util.toString
|
||||||
import org.jeudego.pairgoth.web.ApiException
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.StringWriter
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.servlet.ServletException
|
|
||||||
import javax.servlet.http.HttpServlet
|
import javax.servlet.http.HttpServlet
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
class ApiServlet : HttpServlet() {
|
class ApiServlet : HttpServlet() {
|
||||||
|
|
||||||
val tournamentHandler = TournamentHandler()
|
|
||||||
val playerHandler = PlayerHandler()
|
|
||||||
|
|
||||||
public override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
|
public override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
doRequest(request, response)
|
doRequest(request, response)
|
||||||
}
|
}
|
||||||
@@ -47,29 +41,47 @@ class ApiServlet : HttpServlet() {
|
|||||||
var payload: Json? = null
|
var payload: Json? = null
|
||||||
var reason = "OK"
|
var reason = "OK"
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
// validate request
|
||||||
|
|
||||||
if ("dev" == WebappManager.getProperty("webapp.env")) {
|
if ("dev" == WebappManager.getProperty("webapp.env")) {
|
||||||
response.addHeader("Access-Control-Allow-Origin", "*")
|
response.addHeader("Access-Control-Allow-Origin", "*")
|
||||||
}
|
}
|
||||||
validateContentType(request)
|
validateContentType(request)
|
||||||
validateAccept(request);
|
validateAccept(request);
|
||||||
|
|
||||||
val parts = uri.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
// parse request URI
|
||||||
if (parts.size < 3 || parts.size > 5) throw ApiException(HttpServletResponse.SC_BAD_REQUEST)
|
|
||||||
if (parts.size >= 4) {
|
val parts = uri.split("/").filter { !it.isEmpty() }
|
||||||
request.setAttribute(ApiHandler.SELECTOR_KEY, parts[3])
|
if (parts.size !in 2..5 || parts[0] != "api") throw ApiException(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
}
|
|
||||||
val entity = parts[2]
|
val entity = parts[1]
|
||||||
|
val selector = parts.getOrNull(2)?.also { request.setAttribute(ApiHandler.SELECTOR_KEY, it) }
|
||||||
|
val subEntity = parts.getOrNull(3)
|
||||||
|
val subSelector = parts.getOrNull(4)?.also { request.setAttribute(ApiHandler.SUBSELECTOR_KEY, it) }
|
||||||
|
|
||||||
|
// choose handler
|
||||||
|
|
||||||
val handler = when (entity) {
|
val handler = when (entity) {
|
||||||
"tournament" -> tournamentHandler
|
"tournament" ->
|
||||||
"player" -> playerHandler
|
when (subEntity) {
|
||||||
else -> ApiHandler.badRequest("unknown entity")
|
null -> TournamentHandler
|
||||||
|
"registration" -> RegistrationHandler
|
||||||
|
else -> ApiHandler.badRequest("unknown sub-entity: $subEntity")
|
||||||
}
|
}
|
||||||
|
"player" -> PlayerHandler
|
||||||
|
else -> ApiHandler.badRequest("unknown entity: $entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
// call handler
|
||||||
|
|
||||||
payload = handler.route(request, response)
|
payload = handler.route(request, response)
|
||||||
// if payload is null, it means the handler already sent the response
|
// if payload is null, it means the handler already sent the response
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
setContentType(response)
|
setContentType(response)
|
||||||
payload.toString(response.writer)
|
payload.toString(response.writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (apiException: ApiException) {
|
} catch (apiException: ApiException) {
|
||||||
reason = apiException.message ?: "unknown API error"
|
reason = apiException.message ?: "unknown API error"
|
||||||
if (reason == null) error(response, apiException.code) else error(
|
if (reason == null) error(response, apiException.code) else error(
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
format = %date [%level] %ip [%logger] %message (@%file:%line:%column)
|
format = [%level] %ip [%logger] %message (@%file:%line:%column)
|
||||||
level = trace
|
level = trace
|
||||||
|
Reference in New Issue
Block a user