Various packaging tweaks, documentation, etc.

This commit is contained in:
Claude Brisson
2024-04-13 00:55:38 +02:00
parent 837661e4b0
commit 2d0e3b48a5
12 changed files with 351 additions and 10 deletions

View File

@@ -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.

View File

@@ -27,5 +27,5 @@ smtp.user =
smtp.password =
# logging
logger.level = trace
logger.level = info
logger.format = [%level] %ip [%logger] %message

153
doc/API.md Normal file
View File

@@ -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": <error message> }`.
## 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":<rating> , "rank":<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": <handicap> }`
*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": <result> } ]` 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": <result> }` 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, "<crit>": double }, ... ]`
where `<crit>` 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.

81
doc/configuration.md Normal file
View File

@@ -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
```

17
doc/md2pdf Executable file
View File

@@ -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

80
doc/model.md Normal file
View File

@@ -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"
```

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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")
}