diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt index e7b5fcf..f4e0662 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt @@ -49,7 +49,7 @@ data class MainCritParams( val additionalPlacementCritSystem1: Criterion = Criterion.RATING, val additionalPlacementCritSystem2: Criterion = Criterion.NONE, ) { - enum class DrawUpDown {TOP, MIDDLE, BOTTOM} + enum class DrawUpDown { TOP, MIDDLE, BOTTOM } enum class SeedMethod { SPLIT_AND_FOLD, SPLIT_AND_RANDOM, SPLIT_AND_SLIP } companion object { const val MAX_CATEGORIES_WEIGHT = 20000000000000.0 // 2e13 diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt index 42c5664..e6301b3 100644 --- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt +++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt @@ -235,7 +235,7 @@ class ApiServlet : HttpServlet() { cause: Throwable? = null ) { try { - if (code == 500) { + if (code == 500 || response.isCommitted) { logger.error( "Request {} {} gave error {} {}", request.method, @@ -245,7 +245,13 @@ class ApiServlet : HttpServlet() { cause ) } - response.sendError(code, message) + response.status = code + if (response.isCommitted) return + val errorPayload = Json.Object( + "error" to (message ?: "unknown error") + ) + setContentType(response) + errorPayload.toString(response.writer) } catch (ioe: IOException) { logger.error("Could not send back error", ioe) } diff --git a/view-webapp/pom.xml b/view-webapp/pom.xml index 3fd4c90..a9be6fa 100644 --- a/view-webapp/pom.xml +++ b/view-webapp/pom.xml @@ -68,6 +68,7 @@ ${project.build.directory}/generated-resources/css + 1.69.5 @@ -88,6 +89,8 @@ ${pairgoth.store} ${pairgoth.logger.level} ${pairgoth.logger.format} + debug + debug ${pairgoth.webapp.context}/ @@ -157,6 +160,12 @@ ${servlet.api.version} provided + + + org.eclipse.jetty + jetty-proxy + ${jetty.version} + com.sun.mail jakarta.mail diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt index f17b06c..41c4268 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt @@ -10,9 +10,7 @@ import okhttp3.internal.EMPTY_REQUEST class ApiTool { companion object { const val JSON = "application/json" - val apiRoot = - System.getProperty("pairgoth.api.external.url")?.let { "${it.removeSuffix("/")}/" } - ?: System.getProperty("pairgoth.webapp.external.url")?.let { "${it.removeSuffix("/")}/api/" } + val apiRoot = System.getProperty("pairgoth.api.external.url")?.let { "${it.removeSuffix("/")}/" } ?: throw Error("no configured API url") } private val client = OkHttpClient() diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/CountriesTool.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/CountriesTool.kt new file mode 100644 index 0000000..ffa217f --- /dev/null +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/CountriesTool.kt @@ -0,0 +1,239 @@ +package org.jeudego.pairgoth.view + +import org.apache.velocity.tools.config.ValidScope +import org.jeudego.pairgoth.web.WebappManager +import javax.servlet.http.HttpServletRequest + +@ValidScope("request") +class CountriesTool { + + public var country: Pair? = null + + public fun configure(params: Map<*, *>) { + val request = params["request"]!! as HttpServletRequest + country = request.getHeader("Accept-Language")?.let { header -> + langHeaderParser.find(header) + }?.let { match -> + match.groupValues.getOrNull(2)?.lowercase() ?: match.groupValues[1].lowercase() + }?.let { iso -> + countries[iso]?.let { name -> + Pair(iso, name) + } + } + } + + public fun getCountries() = countries.entries + + companion object { + private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)([a-z]{2}))?)(?:;q=([0-9.]+))?", RegexOption.IGNORE_CASE) + public val countries = mapOf( + "ad" to "Andorra", + "ae" to "United Arab Emirates", + "af" to "Afghanistan", + "ai" to "Anguilla", + "al" to "Albania", + "am" to "Armenia", + "ao" to "Angola", + "ar" to "Argentina", + "at" to "Austria", + "au" to "Australia", + "aw" to "Aruba", + "az" to "Azerbaijan", + "ba" to "Bosnia and Herzegovina", + "bb" to "Barbados", + "bd" to "Bangladesh", + "be" to "Belgium", + "bf" to "Burkina Faso", + "bg" to "Bulgaria", + "bh" to "Bahrain", + "bi" to "Burundi", + "bj" to "Benin", + "bl" to "Saint Barthélemy", + "bm" to "Bermuda", + "bo" to "Bolivia", + "br" to "Brazil", + "bs" to "Bahamas", + "bt" to "Bhutan", + "bw" to "Botswana", + "by" to "Belarus", + "bz" to "Belize", + "ca" to "Canada", + "cd" to "Dem. Rep. of the Congo", + "cf" to "Central African Republic", + "cg" to "Congo", + "ch" to "Switzerland", + "ci" to "Ivory Coast", + "cl" to "Chile", + "cm" to "Cameroon", + "cn" to "China", + "co" to "Colombia", + "cr" to "Costa Rica", + "cu" to "Cuba", + "cv" to "Cabo Verde", + "cw" to "Curaçao", + "cy" to "Cyprus", + "cz" to "Czechia", + "de" to "Germany", + "dj" to "Djibouti", + "dk" to "Denmark", + "dm" to "Dominica", + "do" to "Dominican Republic", + "dz" to "Algeria", + "ec" to "Ecuador", + "ee" to "Estonia", + "eg" to "Egypt", + "eh" to "Western Sahara*", + "er" to "Eritrea", + "es" to "Spain", + "et" to "Ethiopia", + "fi" to "Finland", + "fj" to "Fiji", + "fr" to "France", + "ga" to "Gabon", + "gb" to "United Kingdom", + "gd" to "Grenada", + "ge" to "Georgia", + "gg" to "Guernsey", + "gh" to "Ghana", + "gi" to "Gibraltar", + "gl" to "Greenland", + "gm" to "Gambia", + "gn" to "Guinea", + "gq" to "Equatorial Guinea", + "gr" to "Greece", + "gt" to "Guatemala", + "gu" to "Guam", + "gw" to "Guinea-Bissau", + "gy" to "Guyana", + "hk" to "Hong Kong", + "hn" to "Honduras", + "hr" to "Croatia", + "ht" to "Haiti", + "hu" to "Hungary", + "id" to "Indonesia", + "ie" to "Ireland", + "il" to "Israel", + "im" to "Isle of Man", + "in" to "India", + "iq" to "Iraq", + "ir" to "Iran", + "is" to "Iceland", + "it" to "Italy", + "je" to "Jersey", + "jm" to "Jamaica", + "jo" to "Jordan", + "jp" to "Japan", + "ke" to "Kenya", + "kg" to "Kyrgyzstan", + "kh" to "Cambodia", + "ki" to "Kiribati", + "km" to "Comoros", + "kp" to "North Korea", + "kr" to "South Korea", + "kw" to "Kuwait", + "kz" to "Kazakhstan", + "la" to "Lao People's Dem. Rep.", + "lb" to "Lebanon", + "li" to "Liechtenstein", + "lk" to "Sri Lanka", + "lr" to "Liberia", + "ls" to "Lesotho", + "lt" to "Lithuania", + "lu" to "Luxembourg", + "lv" to "Latvia", + "ly" to "Libya", + "ma" to "Morocco", + "mc" to "Monaco", + "md" to "Moldova", + "me" to "Montenegro", + "mg" to "Madagascar", + "mk" to "Macedonia", + "ml" to "Mali", + "mm" to "Myanmar", + "mn" to "Mongolia", + "mo" to "Macao", + "mq" to "Martinique", + "mr" to "Mauritania", + "ms" to "Montserrat", + "mt" to "Malta", + "mu" to "Mauritius", + "mv" to "Maldives", + "mw" to "Malawi", + "mx" to "Mexico", + "my" to "Malaysia", + "mz" to "Mozambique", + "na" to "Namibia", + "nc" to "New Caledonia", + "ne" to "Niger", + "ng" to "Nigeria", + "ni" to "Nicaragua", + "nl" to "The Netherlands", + "no" to "Norway", + "np" to "Nepal", + "nr" to "Nauru", + "nu" to "Niue", + "nz" to "New Zealand", + "om" to "Oman", + "pa" to "Panama", + "pe" to "Peru", + "pf" to "French Polynesia", + "pg" to "Papua New Guinea", + "ph" to "The Philippines", + "pk" to "Pakistan", + "pl" to "Poland", + "pn" to "Pitcairn", + "pr" to "Puerto Rico", + "pt" to "Portugal", + "pw" to "Palau", + "py" to "Paraguay", + "qa" to "Qatar", + "re" to "Réunion", + "ro" to "Romania", + "rs" to "Serbia", + "ru" to "Russia", + "rw" to "Rwanda", + "sa" to "Saudi Arabia", + "sc" to "Seychelles", + "sd" to "Sudan", + "se" to "Sweden", + "sg" to "Singapore", + "si" to "Slovenia", + "sk" to "Slovakia", + "sl" to "Sierra Leone", + "sm" to "San Marino", + "sn" to "Senegal", + "so" to "Somalia", + "sr" to "Suriname", + "ss" to "South Sudan", + "sv" to "El Salvador", + "sy" to "Syrian Arab Republic", + "sz" to "Swaziland", + "td" to "Chad", + "tg" to "Togo", + "th" to "Thailand", + "tj" to "Tajikistan", + "tm" to "Turkmenistan", + "tn" to "Tunisia", + "to" to "Tonga", + "tr" to "Turkey", + "tv" to "Tuvalu", + "tw" to "Taiwan", + "tz" to "Tanzania", + "ua" to "Ukraine", + "ug" to "Uganda", + "us" to "United States of America", + "uy" to "Uruguay", + "uz" to "Uzbekistan", + "ve" to "Venezuela", + "vn" to "Viet Nam", + "vu" to "Vanuatu", + "ws" to "Samoa", + "xk" to "Kosovo", + "ye" to "Yemen", + "yt" to "Mayotte", + "za" to "South Africa", + "zm" to "Zambia", + "zw" to "Zimbabwe" + ) + } +} \ No newline at end of file diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt new file mode 100644 index 0000000..c1a5877 --- /dev/null +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt @@ -0,0 +1,25 @@ +package org.jeudego.pairgoth.web + +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.proxy.AsyncProxyServlet; +import javax.servlet.http.HttpServletRequest + +class ApiServlet : AsyncProxyServlet() { + + override fun addProxyHeaders(clientRequest: HttpServletRequest, proxyRequest: Request) { + // proxyRequest.header("X-EGC-User", some user id...) + } + + override fun rewriteTarget(clientRequest: HttpServletRequest): String { + val uri = clientRequest.requestURI + if (!uri.startsWith("/api/")) throw Error("unhandled API uri: $uri") + val path = uri.substringAfter("/api") + val qr = clientRequest.queryString?.let { "?$it" } ?: "" + return "$apiRoot$path$qr" + } + + companion object { + private val apiRoot = System.getProperty("pairgoth.api.external.url")?.let { "${it.removeSuffix("/")}" } + ?: throw Error("no configured API url") + } +} diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt index be413a4..5e6a0d8 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LanguageFilter.kt @@ -22,13 +22,18 @@ class LanguageFilter : Filter { override fun doFilter(req: ServletRequest, resp: ServletResponse, chain: FilterChain) { val request = req as HttpServletRequest val response = resp as HttpServletResponse + val uri = request.requestURI + + if (uri.startsWith("/api/")) { + chain.doFilter(request, response) + return + } val reqLang = request.getAttribute("lang") as String? if (reqLang != null) { TranslationTool.translator.set(Translator.getTranslator(reqLang)) chain.doFilter(request, response) } else { - val uri = request.requestURI val match = langPattern.matchEntire(uri) val lang = match?.groupValues?.get(1) val target = match?.groupValues?.get(2) ?: uri @@ -64,6 +69,6 @@ class LanguageFilter : Filter { companion object { private val langPattern = Regex("/([a-z]{2})(/.+)") - private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:_\\w+)?)(?:;q=([0-9.]+))?") + private val langHeaderParser = Regex("(?:\\b(\\*|[a-z]{2})(?:(?:_|-)\\w+)?)(?:;q=([0-9.]+))?") } } diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt index 07aa37b..166e737 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ViewServlet.kt @@ -5,6 +5,7 @@ import org.apache.velocity.context.Context import org.apache.velocity.exception.ResourceNotFoundException import org.apache.velocity.tools.view.ServletUtils import org.apache.velocity.tools.view.VelocityViewServlet +import org.slf4j.LoggerFactory import java.io.File import java.io.UnsupportedEncodingException import java.net.URLDecoder @@ -95,7 +96,14 @@ class ViewServlet : VelocityViewServlet() { } + override fun doRequest(request: HttpServletRequest, response: HttpServletResponse ) { + // val uri = request.requestURI + logger.logRequest(request) //, !uri.contains(".") && uri.length > 1) + super.doRequest(request, response) + } + companion object { + private var logger = LoggerFactory.getLogger("view") private const val STANDARD_LAYOUT = "/WEB-INF/layouts/standard.html" } } \ No newline at end of file diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt index f19e75d..fbc37b0 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt @@ -53,6 +53,8 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H override fun contextInitialized(sce: ServletContextEvent) { context = sce.servletContext logger.info("---------- Starting $WEBAPP_NAME ----------") + logger.debug("debug level is active") + logger.trace("trace level is active") webappRoot = context.getRealPath("/") try { // load default properties diff --git a/view-webapp/src/main/sass/main.scss b/view-webapp/src/main/sass/main.scss index e291058..2f6ea12 100644 --- a/view-webapp/src/main/sass/main.scss +++ b/view-webapp/src/main/sass/main.scss @@ -179,6 +179,7 @@ /* UI fixes */ .ui.form, .ui.segment, .ui.form .field > label { font-size: 1em; } + .ui.form .fields { } span > input[type="radio"] { vertical-align: text-top; } span > input[type="text"] { vertical-align: baseline; width: initial; } span > input.date { vertical-align: baseline; width: 8em; } @@ -190,10 +191,71 @@ .step:last-child { padding-right: 1em; } .step .description { display: none; } + .ui.form input[type=text], .ui.form input[type="number"], .ui.form select { + padding: 0.4em 0.2em; + } + .ui.form input[type="number"], input.duration { + text-align: center; + } + input[type="number"] { padding: 0.2em 0.1em 0.2em 1em; vertical-align: baseline; width: 3.5em; } + .hidden { + display: none; + } + + .roundbox { + border: solid 2px darkgray; + border-radius: 10px; + margin: 1em; + padding: 1em; + } + + #backdrop { + display: none; + &.active { + display: block; + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 999; + background-color: rgba(0,0,0,0.2); + cursor: wait; + } + } + + #backdrop.active { + } + + #feedback { + position: absolute; + top: 1em; + left: 50%; + font-weight: bold; + } + + #success, #error { + position: relative; + left: -50%; + border-radius: 10px; + padding: 0.5em 1em; + } + + #success { + background: lightgreen; + border: solid 2px green; + color: green; + } + + #error { + background: lightcoral; + border: solid 2px red; + color: red; + } } diff --git a/view-webapp/src/main/sass/tour.scss b/view-webapp/src/main/sass/tour.scss new file mode 100644 index 0000000..e69de29 diff --git a/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html b/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html index eee01b5..bdfdd80 100644 --- a/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html +++ b/view-webapp/src/main/webapp/WEB-INF/layouts/standard.html @@ -1,17 +1,33 @@ - + Pairgoth - - - + -#foreach($attr in $application.getAttributeNames())$attr #end +#* Debugging code to list all web context properties + +#foreach($attr in $application.getAttributeNames()) + $attr = $application.getAttribute($attr) +#end + +*# @@ -38,13 +54,22 @@ pairgoth v0.1 contact + + + + + - - - + + + + + + diff --git a/view-webapp/src/main/webapp/WEB-INF/logger.properties b/view-webapp/src/main/webapp/WEB-INF/logger.properties new file mode 100644 index 0000000..3b8f513 --- /dev/null +++ b/view-webapp/src/main/webapp/WEB-INF/logger.properties @@ -0,0 +1 @@ +level = info diff --git a/view-webapp/src/main/webapp/WEB-INF/tools.xml b/view-webapp/src/main/webapp/WEB-INF/tools.xml index 9b92836..3676d06 100644 --- a/view-webapp/src/main/webapp/WEB-INF/tools.xml +++ b/view-webapp/src/main/webapp/WEB-INF/tools.xml @@ -19,6 +19,7 @@ + diff --git a/view-webapp/src/main/webapp/WEB-INF/web.xml b/view-webapp/src/main/webapp/WEB-INF/web.xml index c14e220..26b0e9e 100644 --- a/view-webapp/src/main/webapp/WEB-INF/web.xml +++ b/view-webapp/src/main/webapp/WEB-INF/web.xml @@ -59,6 +59,12 @@ 1 true + + api + org.jeudego.pairgoth.web.ApiServlet + 1 + true + @@ -69,10 +75,26 @@ sse /events/* + + api + /api/* + webapp-slf4j-logger.format %logger [%level] [%ip] %message @%file:%line:%column + + org.apache.velocity.tools.loadDefaults + true + + + org.apache.velocity.tools.cleanConfiguration + true + + + org.apache.velocity.tools.userCanOverwriteTools + false + diff --git a/view-webapp/src/main/webapp/img/logov2.svg b/view-webapp/src/main/webapp/img/logov2.svg new file mode 100644 index 0000000..a0a7b59 --- /dev/null +++ b/view-webapp/src/main/webapp/img/logov2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/view-webapp/src/main/webapp/index.html b/view-webapp/src/main/webapp/index.html index ec1ecf3..3241344 100644 --- a/view-webapp/src/main/webapp/index.html +++ b/view-webapp/src/main/webapp/index.html @@ -1,15 +1,16 @@ - + New tournament - + #foreach($tour in $api.get('tour').entrySet()) - + $tour + - Open $tour.value - + Open + #end @@ -53,15 +54,16 @@ + + + +
+#foreach($attr in $application.getAttributeNames()) + $attr = $application.getAttribute($attr) +#end +