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"), rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing") 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 return tournament
} }

View File

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

View File

@@ -1,6 +1,12 @@
package org.jeudego.pairgoth.view package org.jeudego.pairgoth.view
import com.republicate.kson.Json 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 * Generic utilities
@@ -71,4 +77,21 @@ class PairgothTool {
games.filter { games.filter {
it.getInt("b")!! != 0 && it.getInt("w")!! != 0 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 package org.jeudego.pairgoth.web
import com.republicate.kson.Json
import org.jeudego.pairgoth.util.Upload import org.jeudego.pairgoth.util.Upload
import org.jeudego.pairgoth.view.ApiTool import org.jeudego.pairgoth.view.ApiTool
import org.jeudego.pairgoth.view.PairgothTool.Companion.EXAMPLES_DIRECTORY
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.servlet.http.HttpServlet import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
@@ -11,11 +13,28 @@ class ImportServlet: HttpServlet() {
override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
val api = ApiTool().apply { setRequest(req) } val api = ApiTool().apply { setRequest(req) }
val example = req.getParameter("example") as String?
if (example != null) uploadExample(req, resp)
else {
val uploads = Upload.handleFileUpload(req) val uploads = Upload.handleFileUpload(req)
if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST) if (uploads.size != 1) resp.sendError(HttpServletResponse.SC_BAD_REQUEST)
else { else {
val xml = uploads.first().second.toString(StandardCharsets.UTF_8) val name = uploads.first().first
val apiResp = api.post("tour", xml) 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) { if (apiResp.isObject && apiResp.asObject().getBoolean("success") == true) {
resp.contentType = "application/json; charset=UTF-8" resp.contentType = "application/json; charset=UTF-8"
resp.writer.println(apiResp.toString()) resp.writer.println(apiResp.toString())
@@ -25,3 +44,25 @@ class ImportServlet: HttpServlet() {
} }
} }
} }
}
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) {
response.contentType = "application/json; charset=UTF-8"
response.writer.println(apiResp.toString())
} else {
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 Change Mettre à jour
Chinese rules Règles chinoises Chinese rules Règles chinoises
Choose format Choix du format Choose format Choix du format
Clone Dupliquer
Clone example tournament Dupliquer un tournoi d'exemple
Close Fermer Close Fermer
Club Club Club Club
Compile from the sources Compiler depuis les sources&nbsp; 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 Edit Éditer
Encoding Encodage Encoding Encodage
Enter the magic word Entrer le mot magique Enter the magic word Entrer le mot magique
Example tournament Tournoi
Export Exporter Export Exporter
Export tournament Exporter le tournoi Export tournament Exporter le tournoi
Family name Nom de famille Family name Nom de famille

View File

@@ -7,6 +7,10 @@
<i class="fa fa-upload"></i> <i class="fa fa-upload"></i>
Import tournament Import tournament
</a> </a>
<a id="example-tournament" class="ui green icon floating button">
<i class="fa fa-copy"></i>
Clone example tournament
</a>
</div> </div>
<div class="tournaments section"> <div class="tournaments section">
#set($files = $api.get('tour')) #set($files = $api.get('tour'))
@@ -52,6 +56,33 @@
</form> </form>
</div> </div>
</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"> <script type="text/javascript">
// #[[ // #[[
function doImport() { 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(()=>{ onLoad(()=>{
$('#import-tournament').on('click', e => { $('#import-tournament').on('click', e => {
modal('import-popup'); modal('import-popup');
@@ -88,6 +139,15 @@
} else showError('no file choosen'); } else showError('no file choosen');
close_modal(); 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> </script>