Example tournaments

This commit is contained in:
Claude Brisson
2024-03-04 11:39:21 +01:00
parent b7508a85f8
commit 274734aee8
9 changed files with 12276 additions and 10 deletions

View File

@@ -235,8 +235,18 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
)
json["pairables"]?.let { pairables ->
(json["players"] as Json.Array?)?.forEach { obj ->
val pairable = obj as Json.Object
tournament.players[pairable.getID("id")!!] = Player.fromJson(pairable)
}
(json["games"] as Json.Array?)?.forEachIndexed { i, arr ->
val round = i + 1
val tournamentGames = tournament.games(round)
val games = arr as Json.Array
games.forEach { obj ->
val game = obj as Json.Object
tournamentGames[game.getID("id")!!] = Game.fromJson(game)
}
}
return tournament
}

View File

@@ -20,7 +20,8 @@ object Upload {
// Check that we have a file upload request
val isMultipart: Boolean = ServletFileUpload.isMultipartContent(request)
if (!isMultipart) {
throw IOException("multipart content expected")
logger.warn("multipart content expected")
return listOf()
}
val files = mutableListOf<Pair<String, ByteArray>>()

View File

@@ -1,6 +1,12 @@
package org.jeudego.pairgoth.view
import com.republicate.kson.Json
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.walk
/**
* Generic utilities
@@ -71,4 +77,21 @@ class PairgothTool {
games.filter {
it.getInt("b")!! != 0 && it.getInt("w")!! != 0
}
@OptIn(ExperimentalPathApi::class)
fun getExampleTournaments(): List<String> {
val classLoader: ClassLoader = PairgothTool::class.java.classLoader
val examplesPath = Paths.get(classLoader.getResource(EXAMPLES_DIRECTORY).toURI())
return examplesPath.walk().filter(Files::isRegularFile).map { it.fileName.toString().removeSuffix(".tour") }.sorted().toList()
}
fun getExampleTournament(name: String): Json.Object {
val classLoader: ClassLoader = PairgothTool::class.java.classLoader
return Json.parse(classLoader.getResource("$EXAMPLES_DIRECTORY/$name.tour").readText())?.asObject()
?: throw Error("wrong resource file")
}
companion object {
const val EXAMPLES_DIRECTORY = "examples"
}
}

View File

@@ -1,7 +1,9 @@
package org.jeudego.pairgoth.web
import com.republicate.kson.Json
import org.jeudego.pairgoth.util.Upload
import org.jeudego.pairgoth.view.ApiTool
import org.jeudego.pairgoth.view.PairgothTool.Companion.EXAMPLES_DIRECTORY
import java.nio.charset.StandardCharsets
import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
@@ -11,16 +13,55 @@ class ImportServlet: HttpServlet() {
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
val api = ApiTool().apply { setRequest(req) }
val uploads = Upload.handleFileUpload(req)
if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
val example = req.getParameter("example") as String?
if (example != null) uploadExample(req, resp)
else {
val xml = uploads.first().second.toString(StandardCharsets.UTF_8)
val apiResp = api.post("tour", xml)
val uploads = Upload.handleFileUpload(req)
if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
else {
val name = uploads.first().first
val bytes = uploads.first().second
var apiResp: Json? = null
if (name.endsWith(".tour")) {
val json = Json.parse(bytes.toString(StandardCharsets.UTF_8))
if (json == null || !json.isObject) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
} else {
apiResp = api.post("tour", json.asObject())
}
}
else { // xml ?
val xml = bytes.toString(StandardCharsets.UTF_8)
apiResp = api.post("tour", xml)
}
if (apiResp != null) {
if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
resp.contentType = "application/json; charset=UTF-8"
resp.writer.println(apiResp.toString())
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
}
}
}
}
}
private fun uploadExample(request: HttpServletRequest, response: HttpServletResponse) {
val name = request.getParameter("example")
val classLoader = ImportServlet::class.java.classLoader
val example = classLoader.getResource("$EXAMPLES_DIRECTORY/$name.tour")?.readText()?.let {
Json.parse(it)
}
if (example == null || !example.isObject) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST)
} else {
val api = ApiTool().apply { setRequest(request) }
val apiResp = api.post("tour", example)
if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
resp.contentType = "application/json; charset=UTF-8"
resp.writer.println(apiResp.toString())
response.contentType = "application/json; charset=UTF-8"
response.writer.println(apiResp.toString())
} else {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,8 @@ Cancel Annuler
Change Mettre à jour
Chinese rules Règles chinoises
Choose format Choix du format
Clone Dupliquer
Clone example tournament Dupliquer un tournoi d'exemple
Close Fermer
Club Club
Compile from the sources Compiler depuis les sources&nbsp;
@@ -34,6 +36,7 @@ Download the standalone web interface module which suits your need, then follow
Edit Éditer
Encoding Encodage
Enter the magic word Entrer le mot magique
Example tournament Tournoi
Export Exporter
Export tournament Exporter le tournoi
Family name Nom de famille

View File

@@ -7,6 +7,10 @@
<i class="fa fa-upload"></i>
Import tournament
</a>
<a id="example-tournament" class="ui green icon floating button">
<i class="fa fa-copy"></i>
Clone example tournament
</a>
</div>
<div class="tournaments section">
#set($files = $api.get('tour'))
@@ -52,6 +56,33 @@
</form>
</div>
</div>
<div id="clone-popup" class="popup">
<div class="popup-body">
<form id="clone-form" class="ui form">
<div class="popup-content">
<div class="field">
<label>Example tournament</label>
<select id="exampleTournamentName">
<option value=""></option>
#foreach($tour in $utils.exampleTournaments)
<option value="$tour">$tour</option>
#end
</select>
</div>
</div>
<div class="popup-footer">
<button id="cancel-clone" type="button" class="ui gray right labeled icon floating close button">
<i class="times icon"></i>
Cancel
</button>
<button id="clone" type="button" class="ui green right labeled icon floating button">
<i class="plus icon"></i>
Clone
</button>
</div>
</form>
</div>
</div>
<script type="text/javascript">
// #[[
function doImport() {
@@ -75,6 +106,26 @@
});
}
function doClone(name) {
fetch(`/api/import?example=${name}`, {
method: 'POST',
body: {}
}).then(resp => {
if (resp.ok) return resp.json();
else throw resp;
}).then(json => {
if (json.success) {
console.log(`/tour?id=${json.id}`)
document.location.href = `/tour?id=${json.id}`
} else {
showError(json.error || 'unknown error')
}
}).catch(err => {
error(err);
});
}
onLoad(()=>{
$('#import-tournament').on('click', e => {
modal('import-popup');
@@ -88,6 +139,15 @@
} else showError('no file choosen');
close_modal();
});
$('#example-tournament').on('click', e => {
modal('clone-popup');
e.preventDefault();
return false;
});
$('#clone').on('click', e => {
let example = $('#exampleTournamentName')[0].value;
doClone(example);
});
});
// ]]#
</script>