File storage looks good
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
target
|
target
|
||||||
docker/data
|
/docker/data
|
||||||
.idea
|
/.idea
|
||||||
docker/.env
|
/docker/.env
|
||||||
pairgoth.properties
|
/pairgoth.properties
|
||||||
*.err
|
*.err
|
||||||
|
/tournamentfiles
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package org.jeudego.pairgoth.api
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import org.jeudego.pairgoth.store.Store
|
import org.jeudego.pairgoth.store.Store
|
||||||
|
import org.jeudego.pairgoth.web.Event
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
interface PairgothApiHandler: ApiHandler {
|
interface PairgothApiHandler: ApiHandler {
|
||||||
@@ -11,5 +13,11 @@ interface PairgothApiHandler: ApiHandler {
|
|||||||
return Store.getTournament(tournamentId) ?: ApiHandler.badRequest("unknown tournament id")
|
return Store.getTournament(tournamentId) ?: ApiHandler.badRequest("unknown tournament id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Tournament<*>.dispatchEvent(event: Event, data: Json? = null) {
|
||||||
|
Event.dispatch(event, Json.Object("tournament" to id, "data" to data))
|
||||||
|
// when storage is not in memory, the tournament has to be persisted
|
||||||
|
if (event != Event.tournamentAdded && event != Event.tournamentDeleted && event != Event.gameUpdated)
|
||||||
|
Store.replaceTournament(this)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -46,7 +46,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
}
|
}
|
||||||
val games = tournament.pair(round, pairables)
|
val games = tournament.pair(round, pairables)
|
||||||
val ret = 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))
|
tournament.dispatchEvent(gamesAdded, Json.Object("round" to round, "games" to ret))
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
game.black = payload.getID("b") ?: badRequest("missing black player id")
|
game.black = payload.getID("b") ?: badRequest("missing black player id")
|
||||||
game.white = payload.getID("w") ?: badRequest("missing white player id")
|
game.white = payload.getID("w") ?: badRequest("missing white player id")
|
||||||
if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap")
|
if (payload.containsKey("h")) game.handicap = payload.getString("h")?.toIntOrNull() ?: badRequest("invalid handicap")
|
||||||
Event.dispatch(gameUpdated, Json.Object("tournament" to tournament.id, "round" to round, "data" to game.toJson()))
|
tournament.dispatchEvent(gameUpdated, Json.Object("round" to round, "game" to game.toJson()))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ object PairingHandler: PairgothApiHandler {
|
|||||||
tournament.games(round).remove(id)
|
tournament.games(round).remove(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event.dispatch(gamesDeleted, Json.Object("tournament" to tournament.id, "round" to round, "data" to payload))
|
tournament.dispatchEvent(gamesDeleted, Json.Object("round" to round, "games" to payload))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val player = Player.fromJson(payload)
|
val player = Player.fromJson(payload)
|
||||||
tournament.players[player.id] = player
|
tournament.players[player.id] = player
|
||||||
Event.dispatch(playerAdded, Json.Object("tournament" to tournament.id, "data" to player.toJson()))
|
tournament.dispatchEvent(playerAdded, player.toJson())
|
||||||
return Json.Object("success" to true, "id" to player.id)
|
return Json.Object("success" to true, "id" to player.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val updated = Player.fromJson(payload, player)
|
val updated = Player.fromJson(payload, player)
|
||||||
tournament.players[updated.id] = updated
|
tournament.players[updated.id] = updated
|
||||||
Event.dispatch(playerUpdated, Json.Object("tournament" to tournament.id, "data" to player.toJson()))
|
tournament.dispatchEvent(playerUpdated, player.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ object PlayerHandler: PairgothApiHandler {
|
|||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid player selector")
|
||||||
tournament.players.remove(id) ?: badRequest("invalid player id")
|
tournament.players.remove(id) ?: badRequest("invalid player id")
|
||||||
Event.dispatch(playerDeleted, Json.Object("tournament" to tournament.id, "data" to id))
|
tournament.dispatchEvent(playerDeleted, Json.Object("id" to id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ object ResultsHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
|
val game = tournament.games(round)[payload.getInt("id")] ?: badRequest("invalid game id")
|
||||||
game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result"))
|
game.result = Game.Result.fromSymbol(payload.getChar("result") ?: badRequest("missing result"))
|
||||||
Event.dispatch(Event.resultUpdated, Json.Object("tournament" to tournament.id, "round" to round, "data" to game))
|
tournament.dispatchEvent(Event.resultUpdated, Json.Object("round" to round, "data" to game))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val team = tournament.teamFromJson(payload)
|
val team = tournament.teamFromJson(payload)
|
||||||
tournament.teams[team.id] = team
|
tournament.teams[team.id] = team
|
||||||
Event.dispatch(teamAdded, Json.Object("tournament" to tournament.id, "data" to team.toJson()))
|
tournament.dispatchEvent(teamAdded, team.toJson())
|
||||||
return Json.Object("success" to true, "id" to team.id)
|
return Json.Object("success" to true, "id" to team.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
val payload = getObjectPayload(request)
|
val payload = getObjectPayload(request)
|
||||||
val updated = tournament.teamFromJson(payload, team)
|
val updated = tournament.teamFromJson(payload, team)
|
||||||
tournament.teams[updated.id] = updated
|
tournament.teams[updated.id] = updated
|
||||||
Event.dispatch(teamUpdated, Json.Object("tournament" to tournament.id, "data" to team.toJson()))
|
tournament.dispatchEvent(teamUpdated, team.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ object TeamHandler: PairgothApiHandler {
|
|||||||
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
|
||||||
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
|
val id = getSubSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid team selector")
|
||||||
tournament.teams.remove(id) ?: badRequest("invalid team id")
|
tournament.teams.remove(id) ?: badRequest("invalid team id")
|
||||||
Event.dispatch(teamDeleted, Json.Object("tournament" to tournament.id, "data" to id))
|
tournament.dispatchEvent(teamDeleted, Json.Object("id" to id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -43,7 +43,7 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
else -> badRequest("missing or invalid payload")
|
else -> badRequest("missing or invalid payload")
|
||||||
}
|
}
|
||||||
Store.addTournament(tournament)
|
Store.addTournament(tournament)
|
||||||
Event.dispatch(tournamentAdded, tournament.toJson())
|
tournament.dispatchEvent(tournamentAdded, tournament.toJson())
|
||||||
return Json.Object("success" to true, "id" to tournament.id)
|
return Json.Object("success" to true, "id" to tournament.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,14 +65,14 @@ object TournamentHandler: PairgothApiHandler {
|
|||||||
}
|
}
|
||||||
updated.criteria.addAll(tournament.criteria)
|
updated.criteria.addAll(tournament.criteria)
|
||||||
Store.replaceTournament(updated)
|
Store.replaceTournament(updated)
|
||||||
Event.dispatch(tournamentUpdated, tournament.toJson())
|
tournament.dispatchEvent(tournamentUpdated, tournament.toJson())
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun delete(request: HttpServletRequest): Json {
|
override fun delete(request: HttpServletRequest): Json {
|
||||||
val tournament = getTournament(request)
|
val tournament = getTournament(request)
|
||||||
Store.deleteTournament(tournament)
|
Store.deleteTournament(tournament)
|
||||||
Event.dispatch(tournamentDeleted, tournament.id)
|
tournament.dispatchEvent(tournamentDeleted, Json.Object("id" to tournament.id))
|
||||||
return Json.Object("success" to true)
|
return Json.Object("success" to true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,7 @@ sealed class Tournament <P: Pairable>(
|
|||||||
private val games = mutableListOf<MutableMap<ID, Game>>()
|
private val games = mutableListOf<MutableMap<ID, Game>>()
|
||||||
|
|
||||||
fun games(round: Int) = games.getOrNull(round - 1) ?:
|
fun games(round: Int) = games.getOrNull(round - 1) ?:
|
||||||
if (round > games.size) throw Error("invalid round")
|
if (round > games.size + 1) throw Error("invalid round")
|
||||||
else mutableMapOf<ID, Game>().also { games.add(it) }
|
else mutableMapOf<ID, Game>().also { games.add(it) }
|
||||||
fun lastRound() = games.size
|
fun lastRound() = games.size
|
||||||
|
|
||||||
|
@@ -16,9 +16,9 @@ abstract class OAuthHelper {
|
|||||||
abstract val name: String
|
abstract val name: String
|
||||||
abstract fun getLoginURL(sessionId: String?): String
|
abstract fun getLoginURL(sessionId: String?): String
|
||||||
protected val clientId: String
|
protected val clientId: String
|
||||||
protected get() = WebappManager.getProperty("oauth." + name + ".client_id")
|
protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".client_id")
|
||||||
protected val secret: String
|
protected val secret: String
|
||||||
protected get() = WebappManager.getProperty("oauth." + name + ".secret")
|
protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".secret")
|
||||||
protected val redirectURI: String?
|
protected val redirectURI: String?
|
||||||
protected get() = try {
|
protected get() = try {
|
||||||
val uri: String = WebappManager.Companion.getProperty("webapp.url") + "/oauth.html"
|
val uri: String = WebappManager.Companion.getProperty("webapp.url") + "/oauth.html"
|
||||||
|
@@ -32,6 +32,10 @@ class FileStore(pathStr: String): StoreImplementation {
|
|||||||
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory")
|
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.store.file.path must be a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
_nextTournamentId.set(getTournamentsIDs().maxOrNull() ?: 0.toID())
|
||||||
|
}
|
||||||
|
|
||||||
override fun getTournamentsIDs(): Set<ID> {
|
override fun getTournamentsIDs(): Set<ID> {
|
||||||
return path.useDirectoryEntries("*.tour") { entries ->
|
return path.useDirectoryEntries("*.tour") { entries ->
|
||||||
entries.mapNotNull { entry ->
|
entries.mapNotNull { entry ->
|
||||||
@@ -59,8 +63,8 @@ class FileStore(pathStr: String): StoreImplementation {
|
|||||||
val file = path.useDirectoryEntries("${id.toString().padStart(LEFT_PAD, '0')}-*.tour") { entries ->
|
val file = path.useDirectoryEntries("${id.toString().padStart(LEFT_PAD, '0')}-*.tour") { entries ->
|
||||||
entries.map { entry ->
|
entries.map { entry ->
|
||||||
entry.fileName.toString()
|
entry.fileName.toString()
|
||||||
}
|
}.firstOrNull() ?: throw Error("no such tournament")
|
||||||
}.firstOrNull() ?: throw Error("no such tournament")
|
}
|
||||||
val json = Json.parse(path.resolve(file).readText())?.asObject() ?: throw Error("could not read tournament")
|
val json = Json.parse(path.resolve(file).readText())?.asObject() ?: throw Error("could not read tournament")
|
||||||
val tournament = Tournament.fromJson(json)
|
val tournament = Tournament.fromJson(json)
|
||||||
val players = json["players"] as Json.Array? ?: Json.Array()
|
val players = json["players"] as Json.Array? ?: Json.Array()
|
||||||
|
@@ -2,14 +2,14 @@ package org.jeudego.pairgoth.store
|
|||||||
|
|
||||||
import org.jeudego.pairgoth.model.ID
|
import org.jeudego.pairgoth.model.ID
|
||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.web.WebappManager
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
private fun createStoreImplementation(): StoreImplementation {
|
private fun createStoreImplementation(): StoreImplementation {
|
||||||
val storeProperty = System.getProperty("pairgoth.store") ?: "memory"
|
return when (val storeProperty = WebappManager.getProperty("store") ?: "memory") {
|
||||||
return when (storeProperty) {
|
|
||||||
"memory" -> MemoryStore()
|
"memory" -> MemoryStore()
|
||||||
"file" -> {
|
"file" -> {
|
||||||
val filePath = System.getProperty("pairgoth.store.file.path") ?: "."
|
val filePath = WebappManager.getMandatoryProperty("store.file.path") ?: "."
|
||||||
FileStore(filePath)
|
FileStore(filePath)
|
||||||
}
|
}
|
||||||
else -> throw Error("unknown store: $storeProperty")
|
else -> throw Error("unknown store: $storeProperty")
|
||||||
|
@@ -4,9 +4,9 @@ import org.jeudego.pairgoth.model.ID
|
|||||||
import org.jeudego.pairgoth.model.Tournament
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
private val _nextTournamentId = AtomicInteger()
|
internal val _nextTournamentId = AtomicInteger()
|
||||||
private val _nextPlayerId = AtomicInteger()
|
internal val _nextPlayerId = AtomicInteger()
|
||||||
private val _nextGameId = AtomicInteger()
|
internal val _nextGameId = AtomicInteger()
|
||||||
|
|
||||||
interface StoreImplementation {
|
interface StoreImplementation {
|
||||||
|
|
||||||
|
@@ -107,9 +107,12 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
|
|||||||
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
||||||
var logger = LoggerFactory.getLogger(WebappManager::class.java)
|
var logger = LoggerFactory.getLogger(WebappManager::class.java)
|
||||||
val properties = Properties()
|
val properties = Properties()
|
||||||
fun getProperty(prop: String?): String {
|
fun getProperty(prop: String): String? {
|
||||||
return properties.getProperty(prop)
|
return properties.getProperty(prop)
|
||||||
}
|
}
|
||||||
|
fun getMandatoryProperty(prop: String): String {
|
||||||
|
return properties.getProperty(prop) ?: throw Error("missing property: ${prop}")
|
||||||
|
}
|
||||||
|
|
||||||
val webappURL by lazy { getProperty("webapp.url") }
|
val webappURL by lazy { getProperty("webapp.url") }
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user