From 2d0e3b48a5946b5f6a25d2c601cfceaad59a881c Mon Sep 17 00:00:00 2001 From: Claude Brisson Date: Sat, 13 Apr 2024 00:55:38 +0200 Subject: [PATCH] Various packaging tweaks, documentation, etc. --- README.md | 4 +- .../WEB-INF/pairgoth.default.properties | 2 +- doc/API.md | 153 ++++++++++++++++++ doc/configuration.md | 81 ++++++++++ doc/md2pdf | 17 ++ doc/model.md | 80 +++++++++ installer/resources/installer.nsi | 4 +- .../pairgoth/ratings/EGFRatingsHandler.kt | 2 +- .../pairgoth/ratings/FFGRatingsHandler.kt | 2 +- .../pairgoth/ratings/RatingsHandler.kt | 3 +- .../pairgoth/ratings/RatingsManager.kt | 3 +- .../org/jeudego/pairgoth/web/WebappManager.kt | 10 ++ 12 files changed, 351 insertions(+), 10 deletions(-) create mode 100644 doc/API.md create mode 100644 doc/configuration.md create mode 100755 doc/md2pdf create mode 100644 doc/model.md diff --git a/README.md b/README.md index 1d6fd90..ca3c84c 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ api-webapp/src/main/kotlin/org/jeudego/pairgoth └── web .................................... Web interface ``` -Tests are located in `webapp/src/test/kotlin` +Tests are located in `api-webapp/src/test/kotlin` ## Building and running @@ -82,5 +82,3 @@ The `./server.sh` will launch the server in debugging mode, with a remote debugg The `./client.sh` will launch the web client in debugging mode, with a remote debugger socket on port 5006. The corresponding `./debug-...` scripts will do the same with additional debugging features like automatic re-compilation of CSS files, automatic reloading of template files, etc. - - diff --git a/api-webapp/src/main/webapp/WEB-INF/pairgoth.default.properties b/api-webapp/src/main/webapp/WEB-INF/pairgoth.default.properties index 63e2350..f1d8ce9 100644 --- a/api-webapp/src/main/webapp/WEB-INF/pairgoth.default.properties +++ b/api-webapp/src/main/webapp/WEB-INF/pairgoth.default.properties @@ -27,5 +27,5 @@ smtp.user = smtp.password = # logging -logger.level = trace +logger.level = info logger.format = [%level] %ip [%logger] %message diff --git a/doc/API.md b/doc/API.md new file mode 100644 index 0000000..4a079a8 --- /dev/null +++ b/doc/API.md @@ -0,0 +1,153 @@ +# Pairgoth API specification + +## General remarks + +The API expects an `Accept` header of `application/json`, with no encoding or an `UTF-8` encoding. Exceptions are some export operations which can have different MIME types to specify the expected format. + +GET requests return either an array or an object, as specified below. + +POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success": true }` (with an optional `"id"` field for some POST requests), or and invalid HTTP code and (for some errors) the body `{ "success": false, "error": }`. + +## Synopsis + ++ /api/tour GET POST Tournaments handling ++ /api/tour/#tid GET PUT DELETE Tournaments handling ++ /api/tour/#tid/part GET POST Registration handling ++ /api/tour/#tid/part/#pid GET PUT DELETE Registration handling ++ /api/tour/#tid/team GET POST Team handling ++ /api/tour/#tid/team/#tid GET PUT DELETE Team handling ++ /api/tour/#tid/pair/#rn GET POST PUT DELETE Pairing ++ /api/tour/#tid/res/#rn GET PUT Results ++ /api/tour/#tid/standings GET Standings ++ /api/tour/#tid/stand/#rn GET Standings + +## Tournament handling + ++ `GET /api/tour` Get a list of known tournaments ids + + *output* json map (id towards shortName) of known tournaments (subject to change) + ++ `GET /api/tour/#tid` Get the details of tournament #tid + + *output* json object for tournament #tid + ++ `POST /api/tour` Create a new tournament + + *input* json object for new tournament (see `Tournament.fromJson` in the sources) + + *output* `{ "success": true, "id": #tid }` + ++ `PUT /api/tour/#tid` Modify a tournament + + *input* json object for updated tournament (only id and updated fields required) + + *output* `{ "success": true }` + +## Players handling + ++ `GET /api/tour/#tid/part` Get a list of registered players + + *output* json array of known players + ++ `GET /api/tour/#tid/part/#pid` Get regitration details for player #pid + + *output* json object for player #pid + ++ `POST /api/tour/#tid/part` Register a new player + + *input* `{ "name":"..." , "firstname":"..." , "rating": , "rank": , "country":"XX" [ , "club":"Xxxx" ] [ , "final":true/false ] [ , "mmsCorrection":0 ] }` + + *output* `{ "success": true, "id": #pid }` + ++ `PUT /api/tour/#tid/part/#pid` Modify a player registration + + *input* json object for updated registration (only id and updated fields required) + + *output* `{ "success": true }` + ++ `DELETE /api/tour/#tid/part/#pid` Delete a player registration + + *input* `{ "id": #pid }` + + *output* `{ "success": true }` + +## Teams handling + ++ `GET /api/tour/#tid/team` Get a list of registered teams + + *output* json array of known teams + ++ `GET /api/tour/#tid/team/#tid` Get regitration details for team #tid + + *output* json object for team #tid + ++ `POST /api/tour/#tid/team` Register a new team + + *input* json object for new team + + *output* `{ "success": true, "id": #tid }` + ++ `PUT /api/tour/#tid/team/#tid` Modify a team registration + + *input* json object for updated registration (only id and updated fields required) + + *output* `{ "success": true }` + ++ `DELETE /api/tour/#tid/team/#tid` Delete a team registration + + *input* `{ "id": #tid }` + + *output* `{ "success": true }` + + +## Pairing + ++ `GET /api/tour/#tid/pair/#rn` Get pairable players for round #rn + + *output* `{ "games": [ games... ], "pairables:" [ #pid, ... of players not skipping and not playing the round ], "unpairables": [ #pid, ... of players skipping the round ] }` + ++ `POST /api/tour/#tip/pair/#n` Generate pairing for round #n and given players (or string "all") ; error if already generated for provided players + + *input* `[ "all" ]` or `[ #pid, ... ]` + + *output* `[ { "id": #gid, "t": table, "w": #wpid, "b": #bpid, "h": handicap }, ... ]` + ++ `PUT /api/tour/#tip/pair/#n` Manual pairing (with optional handicap) + + *input* `{ "id": #gid, "w": #wpid, "b": #bpid, "h": }` + + *output* `{ "success": true }` + ++ `DELETE /api/tour/#tip/pair/#n` Delete pairing for round #n and given players (or string "all") ; games with results entered are skipped + + *input* `[ "all" ]` or `[ #gid, ... ]` + + *output* `{ "success": true }` + +## Results + ++ `GET /api/tour/#tip/res/#rn` Get results for round #rn + + *output* `[ { "id": #gid, "res": } ]` with `res` being one of: `"w"`, `"b"`, `"="` (jigo), `"x"` (cancelled),`"?"` (unknown), `"#"` (both win), or `"0"` (both loose). + ++ `PUT /api/tour/#tip/res/#rn` Save a result (or put it back to unknown) + + *input* `{ "id": #gid, "res": }` with `res` being one of: `"w"`, `"b"`, `"="` (jigo), `"x"` (cancelled) + + *output* `{ "success": true }` + +## Standings + ++ `GET /api/tour/#tid/stand/#rn` Get standings after round #rn (or initial standings for round '0') + + *output* `[ { "id": #pid, "place": place, "": double }, ... ]` + where `` is the name of a criterium, among "score", "nbw", "mms", "sosm", "sososm", ... + +## Authentication + ++ `GET /api/token` Get the token of the currently logged user, or give an error. + ++ `POST /api/token` Create an access token. Expects an authentication json object. + ++ `DELETE /api/token` Delete the token of the currently logged user. + diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 0000000..333509a --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,81 @@ +# Configuration + +Pairgoth general configuration is done using the `pairgoth.properties` file in the installation folder. + +## environment + +Controls the running environment: `dev` for development, `prod` for distributed instances. + +``` +env = prod +``` + +## mode + +Running mode: `standalone`, `client` or `server`. + +``` +mode = standalone +``` + +## authentication + +Authentication: `none`, `sesame` for a shared unique password, `oauth` for email and/or oauth accounts. + +``` +auth = none +``` + +## webapp connector + +Pairgoth webapp connector configuration. + +``` +webapp.protocol = http +webapp.interface = localhost +webapp.port = 8080 +webapp.context = / +webapp.external.url = http://localhost:8080 +``` + +## api connector + +Pairgoth API connector configuration. + +``` +api.protocol = http +api.interface = localhost +api.port = 8085 +api.context = /api +api.external.url = http://localhost:8085/api +``` + +## store + +Persistent storage for tournaments, `memory` (mainly used for tests) or `file`. + +``` +store = file +store.file.path = tournamentfiles +``` + +## smtp + +SMTP configuration. Not yet functional. + +``` +smtp.sender = +smtp.host = +smtp.port = 587 +smtp.user = +smtp.password = +``` + +## logging + +Logging configuration. + +``` +logger.level = info +logger.format = [%level] %ip [%logger] %message +``` diff --git a/doc/md2pdf b/doc/md2pdf new file mode 100755 index 0000000..968b9b4 --- /dev/null +++ b/doc/md2pdf @@ -0,0 +1,17 @@ +#!/bin/bash + +# HTML doc generation. Needs 'pandoc'. + +INPUT=$1 +BASE=$(basename -s .md $INPUT) + +if test -f "$BASE.css" +then + pandoc --pdf-engine-opt=--enable-local-file-access -t html -F mermaid-filter --css $BASE.css $INPUT -o $BASE.pdf +else + pandoc --pdf-engine=xelatex -F mermaid-filter $INPUT -o $BASE.pdf +# pandoc --pdf-engine-opt=--enable-local-file-access -t html -F mermaid-filter $INPUT -o $BASE.pdf +fi + +## See also pandoc --number-sections + diff --git a/doc/model.md b/doc/model.md new file mode 100644 index 0000000..7a339d2 --- /dev/null +++ b/doc/model.md @@ -0,0 +1,80 @@ +# PairGoth model + +## Entity Relationship Diagram + +For simplicity, teams (pairgo, rengo) and teams of individuals (clubs championships) are not included. + +```mermaid +erDiagram + + %% entities + + Tournament { + int id + string type + string name + string shortName + date startDate + date endDate + string country + string location + bool isOnline + int rounds + int gobanSize + string rules + int komi + } + + TimeSystem { + string type + int mainTime + int increment + int maxTime + int byoyomi + int periods + int stones + } + + Pairing { + PairingType type + BaseParams base + MainParams main + SecondaryParams secondary + GeographicalParams geo + HandicapParams handicap + PlacementParams place + } + + Game { + int table + int handicap + string result + } + + Player { + int id + string name + string firstname + string country + string club + int rating + string rank + bool final + array skip + } + + Standings { + array criteria + } + + %% relationships + + Tournament ||--|{ TimeSystem: "time system" + Tournament ||--|{ Pairing: "pairing" + Tournament ||--|{ Game: "round" + Tournament }o--|{ Player: "participate(round)" + Game ||--|| Player: "black" + Game ||--|| Player: "white" + Player }|--|| Standings: "position" + +``` diff --git a/installer/resources/installer.nsi b/installer/resources/installer.nsi index f1f285e..c04bf86 100644 --- a/installer/resources/installer.nsi +++ b/installer/resources/installer.nsi @@ -15,9 +15,9 @@ !define LICENSE_TXT "resources/LICENSE.txt" !define INSTALL_DIR "$PROGRAMFILES64\${APP_NAME}" -!define INSTALL_TYPE "SetShellVarContext all" +!define INSTALL_TYPE "SetShellVarContext current" !define DATA_DIR "$LocalAppdata\Pairgoth" -!define REG_ROOT "HKLM" +!define REG_ROOT "HKCU" !define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}" !define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" !define REG_START_MENU "Start Menu Folder" diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt index fa38756..bb6abbf 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/EGFRatingsHandler.kt @@ -19,7 +19,7 @@ object EGFRatingsHandler: RatingsHandler(RatingsManager.Ratings.EGF) { }.mapNotNullTo(Json.MutableArray()) { val match = linePattern.matchEntire(it) if (match == null) { - logger.error("could not parse line: $it") + logger.debug("could not parse line: $it") null } else { val pairs = groups.map { diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt index a68f526..ea0e223 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/FFGRatingsHandler.kt @@ -17,7 +17,7 @@ object FFGRatingsHandler: RatingsHandler(RatingsManager.Ratings.FFG) { payload.lines().mapNotNullTo(Json.MutableArray()) { line -> val match = linePattern.matchEntire(line) if (match == null) { - logger.error("could not parse line: $line") + logger.debug("could not parse line: $line") null } else { val pairs = groups.map { diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt index 2b839f6..00c8ede 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt @@ -110,7 +110,8 @@ abstract class RatingsHandler(val origin: RatingsManager.Ratings) { return response.body!!.source().readString(contentType?.charset() ?: defaultCharset()) } } catch (ioe: IOException) { - logger.error("Could not refresh ${origin.name} ratings from ${url}", ioe) + logger.error("Could not refresh ${origin.name} ratings from ${url}: ${ioe.javaClass.name} ${ioe.message}") + logger.debug("Could not refresh ${origin.name} ratings from ${url}", ioe) return null } } diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt index 79267f1..f632aa0 100644 --- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt +++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt @@ -95,7 +95,8 @@ object RatingsManager: Runnable { } } catch (e: Exception) { - logger.error("could not build or refresh index", e) + logger.error("could not build or refresh index: ${e.javaClass.name} ${e.message}") + logger.debug("could not build or refresh index", e) } } } 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 d8cdbee..e9a3243 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 @@ -41,6 +41,16 @@ class WebappManager : BaseWebappManager("View Webapp", "view") { else -> throw Error("Unhandled auth: $auth") } + logger.info("") + logger.info("*****************************************************************") + logger.info("* *") + logger.info("* Web server is ready. *"); + logger.info("* Open a browser on http://localhost:8080 to access Pairgoth. *") + logger.info("* Press control-c to stop the server when you are done. *") + logger.info("* *") + logger.info("*****************************************************************") + logger.info("") + registerService("ratings", RatingsManager) startService("ratings") }