Update documentation for API, configuration, and model
API.md: - Add export formats (JSON, XML, EGF, FFG, CSV) - Document /explain endpoint for pairing analysis - Add PUT /standings for freezing - Improve parameter documentation - Fix typos (regitration -> registration, #tip -> #tid) configuration.md: - Add property loading hierarchy - Document SSL/TLS configuration - Add OAuth provider configuration - Add ratings.path property - Include example configurations model.md: - Complete entity diagram with Team, external IDs - Document all tournament types (PAIRGO, RENGO, TEAM) - Add TimeSystem types and parameters - Document all pairing parameters - List all 39 tiebreak criteria - Add external database (AGA, EGF, FFG) documentation
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.claude
|
||||
target
|
||||
/docker/data
|
||||
/.idea
|
||||
|
||||
160
CLAUDE.md
Normal file
160
CLAUDE.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Pairgoth Project
|
||||
|
||||
## Purpose
|
||||
|
||||
**Pairgoth** is a modern Go tournament pairing engine - successor to OpenGotha. It manages tournaments using Swiss and MacMahon pairing systems, handling player registration, automatic pairing, results entry, and standings calculation.
|
||||
|
||||
**Version:** 0.20 | **License:** org.jeudego (French Go Association)
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Backend:** Kotlin 2.1 + Maven + Jetty 10
|
||||
- **Frontend:** Fomantic UI CSS 2.9.2 + Vanilla JavaScript (no jQuery/React)
|
||||
- **Templates:** Apache Velocity 2.4
|
||||
- **Storage:** File-based XML (no database required)
|
||||
- **JDK:** 11+
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
pairgoth/
|
||||
├── pairgoth-common/ # Shared utilities (JSON, XML, crypto, logging)
|
||||
├── api-webapp/ # REST API backend (port 8085)
|
||||
│ └── model/ # Domain: Tournament, Player, Game, Pairing
|
||||
│ └── pairing/ # Solvers: Swiss, MacMahon algorithms
|
||||
│ └── store/ # Persistence: File/Memory storage
|
||||
│ └── api/ # Handlers: Tournament, Player, Results, etc.
|
||||
│ └── ext/ # OpenGotha import/export
|
||||
├── view-webapp/ # Web UI frontend (port 8080)
|
||||
│ └── webapp/js/ # Vanilla JS: domhelper, api, main, tour-*.inc
|
||||
│ └── webapp/sass/ # Styles: main, tour, explain, index
|
||||
│ └── templates/ # Velocity: index, tour, explain, login
|
||||
│ └── kotlin/ # Servlets, OAuth, Ratings integration
|
||||
├── webserver/ # Standalone Jetty launcher
|
||||
├── application/ # Final JAR packaging
|
||||
└── docker/ # Container deployment
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Dual-Webapp Pattern
|
||||
|
||||
```
|
||||
[Browser] <--8080--> [view-webapp] <--8085--> [api-webapp]
|
||||
│ │ │
|
||||
Velocity HTML ApiClient.kt REST JSON
|
||||
+ vanilla JS + FileStore
|
||||
```
|
||||
|
||||
- **api-webapp** - Pure REST API, business logic, pairing engine
|
||||
- **view-webapp** - Web UI, proxies API calls, handles auth/i18n/ratings
|
||||
|
||||
### Key Architectural Decisions
|
||||
|
||||
1. **No JS Framework** - 2200 lines of vanilla JS vs typical 50KB+ bundle
|
||||
2. **Fomantic CSS Only** - Using CSS framework without its jQuery-dependent JS
|
||||
3. **CSS @layer** - Clean cascade: `semantic` layer < `pairgoth` layer
|
||||
4. **File Storage** - XML files for portability, no database setup needed
|
||||
5. **Read/Write Locks** - Simple concurrency on API servlet
|
||||
6. **SSE Events** - Real-time updates via Server-Sent Events
|
||||
|
||||
## Domain Model
|
||||
|
||||
```
|
||||
Tournament (sealed class)
|
||||
├── IndividualTournament
|
||||
├── PairTournament
|
||||
├── TeamTournament
|
||||
└── RengoTournament
|
||||
|
||||
Player → Pairable (interface)
|
||||
Game { white, black, result, handicap }
|
||||
Pairing { Swiss | MacMahon }
|
||||
TimeSystem { ByoYomi | SuddenDeath | Canadian | Fischer }
|
||||
Rules { French | Japanese | AGA | Chinese }
|
||||
```
|
||||
|
||||
## Pairing Engine
|
||||
|
||||
Location: `api-webapp/src/main/kotlin/org/jeudego/pairgoth/pairing/`
|
||||
|
||||
- **SwissSolver** - Swiss system pairing algorithm
|
||||
- **MacMahonSolver** - MacMahon bands system
|
||||
- **HistoryHelper** - Criteria: wins, SOS, SOSOS, colors, CUSS, etc.
|
||||
- **PairingListener** - Progress callbacks for UI
|
||||
|
||||
## Key Files
|
||||
|
||||
| Purpose | Path |
|
||||
|---------|------|
|
||||
| DOM utilities | `view-webapp/.../js/domhelper.js` |
|
||||
| API client | `view-webapp/.../js/api.js` |
|
||||
| Core UI | `view-webapp/.../js/main.js` |
|
||||
| Main styles | `view-webapp/.../sass/main.scss` |
|
||||
| Tournament model | `api-webapp/.../model/Tournament.kt` |
|
||||
| Swiss solver | `api-webapp/.../pairing/solver/SwissSolver.kt` |
|
||||
| API router | `api-webapp/.../server/ApiServlet.kt` |
|
||||
| App launcher | `webserver/.../application/Pairgoth.kt` |
|
||||
|
||||
## Build & Run
|
||||
|
||||
```bash
|
||||
# Build
|
||||
mvn clean package
|
||||
|
||||
# Run standalone (both webapps)
|
||||
java -jar application/target/pairgoth-engine.jar
|
||||
|
||||
# Or separate:
|
||||
# API: localhost:8085/api
|
||||
# UI: localhost:8080/
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
File: `pairgoth.properties` (user) or `pairgoth.default.properties` (defaults)
|
||||
|
||||
```properties
|
||||
webapp.port = 8080
|
||||
api.port = 8085
|
||||
store = file # file | memory
|
||||
store.file.path = tournamentfiles
|
||||
auth = none # none | oauth | sesame
|
||||
```
|
||||
|
||||
## Frontend Patterns
|
||||
|
||||
### State via CSS Classes
|
||||
- `.active` - tabs, accordions, visible elements
|
||||
- `.shown` - modals/popups
|
||||
- `.hidden` / `.disabled` / `.selected` / `.dimmed`
|
||||
|
||||
### Component Communication
|
||||
```javascript
|
||||
// Custom events
|
||||
box.dispatchEvent(new CustomEvent('listitem-dblclk', { detail: id }));
|
||||
|
||||
// jQuery-like API (domhelper.js)
|
||||
$('.item').addClass('active').on('click', handler);
|
||||
```
|
||||
|
||||
### API Integration
|
||||
```javascript
|
||||
api.getJson('/tour/123/players')
|
||||
.then(players => render(players))
|
||||
.catch(err => error(err));
|
||||
```
|
||||
|
||||
## External Integrations
|
||||
|
||||
- **Ratings:** FFG (French), EGF (European), AGA (Australian)
|
||||
- **OAuth:** FFG, Google, Facebook, Twitter, Instagram
|
||||
- **Import/Export:** OpenGotha XML format compatibility
|
||||
|
||||
## i18n
|
||||
|
||||
Translations in `view-webapp/.../WEB-INF/translations/`
|
||||
- English (default)
|
||||
- French (fr)
|
||||
- German (de)
|
||||
- Korean (ko)
|
||||
195
doc/API.md
195
doc/API.md
@@ -2,11 +2,20 @@
|
||||
|
||||
## 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.
|
||||
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:
|
||||
- `application/json` - JSON output (default)
|
||||
- `application/xml` - OpenGotha XML export
|
||||
- `application/egf` - EGF format
|
||||
- `application/ffg` - FFG format
|
||||
- `text/csv` - CSV 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> }`.
|
||||
POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success": true }` (with an optional `"id"` field for some POST requests), or an invalid HTTP code and (for some errors) the body `{ "success": false, "error": <error message> }`.
|
||||
|
||||
All POST/PUT/DELETE requests use read/write locks for concurrency. GET requests use read locks.
|
||||
|
||||
When authentication is enabled, all requests require an `Authorization` header.
|
||||
|
||||
## Synopsis
|
||||
|
||||
@@ -18,22 +27,48 @@ POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success":
|
||||
+ /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 DELETE Results
|
||||
+ /api/tour/#tid/standings GET Standings
|
||||
+ /api/tour/#tid/standings GET PUT Standings
|
||||
+ /api/tour/#tid/stand/#rn GET Standings
|
||||
+ /api/tour/#tid/explain/#rn GET Pairing explanation
|
||||
+ /api/token GET POST DELETE Authentication
|
||||
|
||||
## Tournament handling
|
||||
|
||||
+ `GET /api/tour` Get a list of known tournaments ids
|
||||
|
||||
*output* json map (id towards shortName) of known tournaments (subject to change)
|
||||
*output* json map (id towards shortName) of known tournaments
|
||||
|
||||
+ `GET /api/tour/#tid` Get the details of tournament #tid
|
||||
|
||||
*output* json object for tournament #tid
|
||||
|
||||
Supports `Accept: application/xml` to get OpenGotha XML export.
|
||||
|
||||
+ `POST /api/tour` Create a new tournament
|
||||
|
||||
*input* json object for new tournament (see `Tournament.fromJson` in the sources)
|
||||
*input* json object for new tournament, or OpenGotha XML with `Content-Type: application/xml`
|
||||
|
||||
Tournament JSON structure:
|
||||
```json
|
||||
{
|
||||
"type": "INDIVIDUAL",
|
||||
"name": "Tournament Name",
|
||||
"shortName": "TN",
|
||||
"startDate": "2024-01-15",
|
||||
"endDate": "2024-01-16",
|
||||
"country": "fr",
|
||||
"location": "Paris",
|
||||
"online": false,
|
||||
"rounds": 5,
|
||||
"gobanSize": 19,
|
||||
"rules": "FRENCH",
|
||||
"komi": 7.5,
|
||||
"timeSystem": { ... },
|
||||
"pairing": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Tournament types: `INDIVIDUAL`, `PAIRGO`, `RENGO2`, `RENGO3`, `TEAM2`, `TEAM3`, `TEAM4`, `TEAM5`
|
||||
|
||||
*output* `{ "success": true, "id": #tid }`
|
||||
|
||||
@@ -43,19 +78,40 @@ POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success":
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
+ `DELETE /api/tour/#tid` Delete a tournament
|
||||
|
||||
*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
|
||||
+ `GET /api/tour/#tid/part/#pid` Get registration 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 ] }`
|
||||
*input*
|
||||
```json
|
||||
{
|
||||
"name": "Lastname",
|
||||
"firstname": "Firstname",
|
||||
"rating": 1500,
|
||||
"rank": -5,
|
||||
"country": "FR",
|
||||
"club": "Club Name",
|
||||
"final": true,
|
||||
"mmsCorrection": 0,
|
||||
"egfId": "12345678",
|
||||
"ffgId": "12345",
|
||||
"agaId": "12345"
|
||||
}
|
||||
```
|
||||
|
||||
Rank values: -30 (30k) to 8 (9D). Rating in EGF-style (100 = 1 stone).
|
||||
|
||||
*output* `{ "success": true, "id": #pid }`
|
||||
|
||||
@@ -67,35 +123,41 @@ POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success":
|
||||
|
||||
+ `DELETE /api/tour/#tid/part/#pid` Delete a player registration
|
||||
|
||||
*input* `{ "id": #pid }`
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
## Teams handling
|
||||
|
||||
For team tournaments (PAIRGO, RENGO2, RENGO3, TEAM2-5).
|
||||
|
||||
+ `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
|
||||
+ `GET /api/tour/#tid/team/#teamid` Get registration details for team #teamid
|
||||
|
||||
*output* json object for team #tid
|
||||
*output* json object for team #teamid
|
||||
|
||||
+ `POST /api/tour/#tid/team` Register a new team
|
||||
|
||||
*input* json object for new team
|
||||
*input*
|
||||
```json
|
||||
{
|
||||
"name": "Team Name",
|
||||
"playerIds": [1, 2, 3],
|
||||
"final": true,
|
||||
"mmsCorrection": 0
|
||||
}
|
||||
```
|
||||
|
||||
*output* `{ "success": true, "id": #tid }`
|
||||
*output* `{ "success": true, "id": #teamid }`
|
||||
|
||||
+ `PUT /api/tour/#tid/team/#tid` Modify a team registration
|
||||
+ `PUT /api/tour/#tid/team/#teamid` 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 }`
|
||||
+ `DELETE /api/tour/#tid/team/#teamid` Delete a team registration
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
@@ -104,56 +166,121 @@ POST, PUT and DELETE requests return either the 200 HTTP code with `{ "success":
|
||||
|
||||
+ `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 ] }`
|
||||
*output*
|
||||
```json
|
||||
{
|
||||
"games": [ { "id": 1, "t": 1, "w": 2, "b": 3, "h": 0 }, ... ],
|
||||
"pairables": [ 4, 5, ... ],
|
||||
"unpairables": [ 6, 7, ... ]
|
||||
}
|
||||
```
|
||||
|
||||
+ `POST /api/tour/#tip/pair/#n` Generate pairing for round #n and given players (or string "all") ; error if already generated for provided players
|
||||
- `games`: existing pairings for the round
|
||||
- `pairables`: player IDs available for pairing (not skipping, not already paired)
|
||||
- `unpairables`: player IDs skipping the round
|
||||
|
||||
+ `POST /api/tour/#tid/pair/#rn` Generate pairing for round #rn
|
||||
|
||||
*input* `[ "all" ]` or `[ #pid, ... ]`
|
||||
|
||||
Optional query parameters:
|
||||
- `legacy=true` - Use legacy pairing algorithm
|
||||
- `weights_output=<file>` - Output weights to file for debugging
|
||||
- `append=true` - Append to weights output file
|
||||
|
||||
*output* `[ { "id": #gid, "t": table, "w": #wpid, "b": #bpid, "h": handicap }, ... ]`
|
||||
|
||||
+ `PUT /api/tour/#tip/pair/#n` Manual pairing (with optional handicap)
|
||||
+ `PUT /api/tour/#tid/pair/#rn` Manual pairing or table renumbering
|
||||
|
||||
For manual pairing:
|
||||
*input* `{ "id": #gid, "w": #wpid, "b": #bpid, "h": <handicap> }`
|
||||
|
||||
For table renumbering:
|
||||
*input* `{ "renumber": <game_id or null>, "orderBy": "mms" | "table" }`
|
||||
|
||||
*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
|
||||
+ `DELETE /api/tour/#tid/pair/#rn` Delete pairing for round #rn
|
||||
|
||||
*input* `[ "all" ]` or `[ #gid, ... ]`
|
||||
|
||||
Games with results already entered are skipped unless `"all"` is specified.
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
## Results
|
||||
|
||||
+ `GET /api/tour/#tip/res/#rn` Get results for round #rn
|
||||
+ `GET /api/tour/#tid/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).
|
||||
*output* `[ { "id": #gid, "res": <result> }, ... ]`
|
||||
|
||||
+ `PUT /api/tour/#tip/res/#rn` Save a result (or put it back to unknown)
|
||||
Result codes:
|
||||
- `"w"` - White won
|
||||
- `"b"` - Black won
|
||||
- `"="` - Jigo (draw)
|
||||
- `"X"` - Cancelled
|
||||
- `"?"` - Unknown (not yet played)
|
||||
- `"#"` - Both win (unusual)
|
||||
- `"0"` - Both lose (unusual)
|
||||
|
||||
*input* `{ "id": #gid, "res": <result> }` with `res` being one of: `"w"`, `"b"`, `"="` (jigo), `"x"` (cancelled)
|
||||
+ `PUT /api/tour/#tid/res/#rn` Save a result
|
||||
|
||||
*input* `{ "id": #gid, "res": <result> }`
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
+ `DELETE /api/tour/#tip/res/#rn` Clear all results (put back all results to unknown)
|
||||
|
||||
*input* none
|
||||
+ `DELETE /api/tour/#tid/res/#rn` Clear all results for round
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
## Standings
|
||||
|
||||
+ `GET /api/tour/#tid/stand/#rn` Get standings after round #rn (or initial standings for round '0')
|
||||
+ `GET /api/tour/#tid/standings` Get standings after final round
|
||||
|
||||
*output* `[ { "id": #pid, "place": place, "<crit>": double }, ... ]`
|
||||
where `<crit>` is the name of a criterium, among "score", "nbw", "mms", "sosm", "sososm", ...
|
||||
*output* `[ { "id": #pid, "place": place, "<crit>": value }, ... ]`
|
||||
|
||||
Supports multiple output formats via Accept header:
|
||||
- `application/json` - JSON (default)
|
||||
- `application/egf` - EGF format
|
||||
- `application/ffg` - FFG format
|
||||
- `text/csv` - CSV format
|
||||
|
||||
Optional query parameters:
|
||||
- `include_preliminary=true` - Include preliminary standings
|
||||
- `individual_standings=true` - For team tournaments with individual scoring
|
||||
|
||||
+ `GET /api/tour/#tid/stand/#rn` Get standings after round #rn
|
||||
|
||||
Use round `0` for initial standings.
|
||||
|
||||
*output* `[ { "id": #pid, "place": place, "<crit>": value }, ... ]`
|
||||
|
||||
Criteria names include: `nbw`, `mms`, `sts`, `cps`, `sosw`, `sosm`, `sososw`, `sososm`, `sodosw`, `sodosm`, `cussw`, `cussm`, `dc`, `sdc`, `ext`, `exr`, etc.
|
||||
|
||||
+ `PUT /api/tour/#tid/standings` Freeze/lock standings
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
## Pairing explanation
|
||||
|
||||
+ `GET /api/tour/#tid/explain/#rn` Get detailed pairing criteria weights for round #rn
|
||||
|
||||
*output* Detailed pairing weight analysis and criteria breakdown
|
||||
|
||||
Used for debugging and understanding pairing decisions.
|
||||
|
||||
## Authentication
|
||||
|
||||
+ `GET /api/token` Get the token of the currently logged user, or give an error.
|
||||
+ `GET /api/token` Check authentication status
|
||||
|
||||
+ `POST /api/token` Create an access token. Expects an authentication json object.
|
||||
*output* Token information for the currently logged user, or error if not authenticated.
|
||||
|
||||
+ `DELETE /api/token` Delete the token of the currently logged user.
|
||||
+ `POST /api/token` Create an access token
|
||||
|
||||
*input* Authentication credentials (format depends on auth mode)
|
||||
|
||||
*output* `{ "success": true, "token": "..." }`
|
||||
|
||||
+ `DELETE /api/token` Logout / revoke token
|
||||
|
||||
*output* `{ "success": true }`
|
||||
|
||||
@@ -2,39 +2,96 @@
|
||||
|
||||
Pairgoth general configuration is done using the `pairgoth.properties` file in the installation folder.
|
||||
|
||||
## environment
|
||||
Properties are loaded in this order (later overrides earlier):
|
||||
|
||||
Controls the running environment: `dev` for development, `prod` for distributed instances.
|
||||
1. Default properties embedded in WAR/JAR
|
||||
2. User properties file (`./pairgoth.properties`) in current working directory
|
||||
3. System properties prefixed with `pairgoth.` (command-line: `-Dpairgoth.key=value`)
|
||||
|
||||
## Environment
|
||||
|
||||
Controls the running environment.
|
||||
|
||||
```
|
||||
env = prod
|
||||
```
|
||||
|
||||
## mode
|
||||
Values:
|
||||
- `dev` - Development mode: enables CORS headers and additional logging
|
||||
- `prod` - Production: for distributed instances
|
||||
|
||||
Running mode: `standalone`, `client` or `server`.
|
||||
## Mode
|
||||
|
||||
Running mode for the application.
|
||||
|
||||
```
|
||||
mode = standalone
|
||||
```
|
||||
|
||||
## authentication
|
||||
Values:
|
||||
- `standalone` - Both web and API in a single process (default for jar execution)
|
||||
- `server` - API only
|
||||
- `client` - Web UI only (connects to remote API)
|
||||
|
||||
Authentication: `none`, `sesame` for a shared unique password, `oauth` for email and/or oauth accounts.
|
||||
## Authentication
|
||||
|
||||
Authentication method for the application.
|
||||
|
||||
```
|
||||
auth = none
|
||||
```
|
||||
|
||||
When running in client or server mode, if `auth` is not `none`, the following extra property is needed:
|
||||
Values:
|
||||
- `none` - No authentication required
|
||||
- `sesame` - Shared unique password
|
||||
- `oauth` - Email and/or OAuth accounts
|
||||
|
||||
### Shared secret
|
||||
|
||||
When running in client or server mode with authentication enabled:
|
||||
|
||||
```
|
||||
auth.shared_secret = <16 ascii characters string>
|
||||
```
|
||||
|
||||
## webapp connector
|
||||
This secret is shared between API and View webapps. Auto-generated in standalone mode.
|
||||
|
||||
Pairgoth webapp connector configuration.
|
||||
### Sesame password
|
||||
|
||||
When using sesame authentication:
|
||||
|
||||
```
|
||||
auth.sesame = <password>
|
||||
```
|
||||
|
||||
## OAuth configuration
|
||||
|
||||
When using OAuth authentication:
|
||||
|
||||
```
|
||||
oauth.providers = ffg,google,facebook
|
||||
```
|
||||
|
||||
Comma-separated list of enabled providers: `ffg`, `facebook`, `google`, `instagram`, `twitter`
|
||||
|
||||
For each enabled provider, configure credentials:
|
||||
|
||||
```
|
||||
oauth.<provider>.client_id = <client_id>
|
||||
oauth.<provider>.secret = <client_secret>
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
oauth.ffg.client_id = your-ffg-client-id
|
||||
oauth.ffg.secret = your-ffg-client-secret
|
||||
oauth.google.client_id = your-google-client-id
|
||||
oauth.google.secret = your-google-client-secret
|
||||
```
|
||||
|
||||
## Webapp connector
|
||||
|
||||
Pairgoth webapp (UI) connector configuration.
|
||||
|
||||
```
|
||||
webapp.protocol = http
|
||||
@@ -44,7 +101,10 @@ webapp.context = /
|
||||
webapp.external.url = http://localhost:8080
|
||||
```
|
||||
|
||||
## api connector
|
||||
- `webapp.host` (or `webapp.interface`) - Hostname/interface to bind to
|
||||
- `webapp.external.url` - External URL for OAuth redirects and client configuration
|
||||
|
||||
## API connector
|
||||
|
||||
Pairgoth API connector configuration.
|
||||
|
||||
@@ -56,28 +116,91 @@ api.context = /api
|
||||
api.external.url = http://localhost:8085/api
|
||||
```
|
||||
|
||||
## store
|
||||
Note: In standalone mode, API port defaults to 8080 and context to `/api/tour`.
|
||||
|
||||
Persistent storage for tournaments, `memory` (mainly used for tests) or `file`.
|
||||
## SSL/TLS configuration
|
||||
|
||||
For HTTPS connections:
|
||||
|
||||
```
|
||||
webapp.ssl.key = path/to/localhost.key
|
||||
webapp.ssl.cert = path/to/localhost.crt
|
||||
webapp.ssl.pass = <key passphrase>
|
||||
```
|
||||
|
||||
Supports `jar:` URLs for embedded resources.
|
||||
|
||||
## Store
|
||||
|
||||
Persistent storage for tournaments.
|
||||
|
||||
```
|
||||
store = file
|
||||
store.file.path = tournamentfiles
|
||||
```
|
||||
|
||||
## smtp
|
||||
Values for `store`:
|
||||
- `file` - Persistent XML files (default)
|
||||
- `memory` - RAM-based (mainly for tests)
|
||||
|
||||
SMTP configuration. Not yet functional.
|
||||
The `store.file.path` is relative to the current working directory.
|
||||
|
||||
## Ratings
|
||||
|
||||
### Ratings directory
|
||||
|
||||
```
|
||||
smtp.sender =
|
||||
smtp.host =
|
||||
ratings.path = ratings
|
||||
```
|
||||
|
||||
Directory for caching downloaded ratings files.
|
||||
|
||||
### Rating sources
|
||||
|
||||
For each rating source (`aga`, `egf`, `ffg`):
|
||||
|
||||
```
|
||||
ratings.<source> = <url or file path>
|
||||
```
|
||||
|
||||
If not set, ratings are auto-downloaded from the default URL. Set to a local file path to freeze ratings at a specific date.
|
||||
|
||||
Example to freeze EGF ratings:
|
||||
```
|
||||
ratings.egf = ratings/EGF-20240115.json
|
||||
```
|
||||
|
||||
### Enable/disable ratings
|
||||
|
||||
```
|
||||
ratings.<source>.enable = true | false
|
||||
```
|
||||
|
||||
Whether to display the rating source button in the Add Player popup.
|
||||
|
||||
```
|
||||
ratings.<source>.show = true | false
|
||||
```
|
||||
|
||||
Whether to show player IDs from this rating source on the registration page.
|
||||
|
||||
Defaults:
|
||||
- For tournaments in France: FFG enabled and shown by default
|
||||
- Otherwise: all disabled by default
|
||||
|
||||
## SMTP
|
||||
|
||||
SMTP configuration for email notifications. Not yet functional.
|
||||
|
||||
```
|
||||
smtp.sender = sender@example.com
|
||||
smtp.host = smtp.example.com
|
||||
smtp.port = 587
|
||||
smtp.user =
|
||||
smtp.password =
|
||||
smtp.user = username
|
||||
smtp.password = password
|
||||
```
|
||||
|
||||
## logging
|
||||
## Logging
|
||||
|
||||
Logging configuration.
|
||||
|
||||
@@ -86,34 +209,48 @@ logger.level = info
|
||||
logger.format = [%level] %ip [%logger] %message
|
||||
```
|
||||
|
||||
## ratings
|
||||
Log levels: `trace`, `debug`, `info`, `warn`, `error`
|
||||
|
||||
Ratings configuration. `<ratings>` stands for `egf` or `ffg` in the following.
|
||||
Format placeholders: `%level`, `%ip`, `%logger`, `%message`
|
||||
|
||||
### freeze ratings date
|
||||
## Example configurations
|
||||
|
||||
If the following property is given:
|
||||
### Standalone development
|
||||
|
||||
```
|
||||
ratings.<ratings>.file = ...
|
||||
```properties
|
||||
env = dev
|
||||
mode = standalone
|
||||
auth = none
|
||||
store = file
|
||||
store.file.path = tournamentfiles
|
||||
logger.level = trace
|
||||
```
|
||||
|
||||
then the given ratings file will be used (it must use the Pairgoth ratings json format). If not, the corresponding ratings will be automatically downloaded and stored into `ratings/EGF-yyyymmdd.json` or `ratings/FFG-yyyymmdd.json`.
|
||||
### Client-server deployment
|
||||
|
||||
The typical use case, for a big tournament lasting several days or a congress, is to let Pairgoth download the latest expected ratings, then to add this property to freeze the ratings at a specific date.
|
||||
|
||||
### enable or disable ratings
|
||||
|
||||
Whether to display the EGF or FFG ratings button in the Add Player popup:
|
||||
|
||||
```
|
||||
ratings.<ratings>.enable = true | false
|
||||
**Server (API):**
|
||||
```properties
|
||||
env = prod
|
||||
mode = server
|
||||
auth = oauth
|
||||
auth.shared_secret = 1234567890abcdef
|
||||
api.port = 8085
|
||||
store = file
|
||||
store.file.path = /var/tournaments
|
||||
logger.level = info
|
||||
```
|
||||
|
||||
Whether to show the ratings player IDs on the registration page:
|
||||
|
||||
**Client (Web UI):**
|
||||
```properties
|
||||
env = prod
|
||||
mode = client
|
||||
auth = oauth
|
||||
auth.shared_secret = 1234567890abcdef
|
||||
oauth.providers = ffg,google
|
||||
oauth.ffg.client_id = your-ffg-id
|
||||
oauth.ffg.secret = your-ffg-secret
|
||||
oauth.google.client_id = your-google-id
|
||||
oauth.google.secret = your-google-secret
|
||||
webapp.port = 8080
|
||||
api.external.url = http://api-server:8085/api
|
||||
```
|
||||
ratings.<ratings>.show = true | false
|
||||
```
|
||||
|
||||
For a tournament in France, both are true for `ffg` by default, false otherwise.
|
||||
|
||||
306
doc/model.md
306
doc/model.md
@@ -1,9 +1,7 @@
|
||||
# PairGoth model
|
||||
# Pairgoth Model
|
||||
|
||||
## Entity Relationship Diagram
|
||||
|
||||
For simplicity, teams (pairgo, rengo) and teams of individuals (clubs championships) are not included.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
|
||||
@@ -11,22 +9,23 @@ erDiagram
|
||||
|
||||
Tournament {
|
||||
int id
|
||||
string type
|
||||
Type type
|
||||
string name
|
||||
string shortName
|
||||
date startDate
|
||||
date endDate
|
||||
string director
|
||||
string country
|
||||
string location
|
||||
bool isOnline
|
||||
bool online
|
||||
int rounds
|
||||
int gobanSize
|
||||
string rules
|
||||
int komi
|
||||
Rules rules
|
||||
double komi
|
||||
}
|
||||
|
||||
TimeSystem {
|
||||
string type
|
||||
TimeSystemType type
|
||||
int mainTime
|
||||
int increment
|
||||
int maxTime
|
||||
@@ -37,18 +36,17 @@ erDiagram
|
||||
|
||||
Pairing {
|
||||
PairingType type
|
||||
BaseParams base
|
||||
MainParams main
|
||||
SecondaryParams secondary
|
||||
GeographicalParams geo
|
||||
HandicapParams handicap
|
||||
PlacementParams place
|
||||
PairingParams pairingParams
|
||||
PlacementParams placementParams
|
||||
}
|
||||
|
||||
Game {
|
||||
int id
|
||||
int table
|
||||
int handicap
|
||||
string result
|
||||
Result result
|
||||
int drawnUpDown
|
||||
bool forcedTable
|
||||
}
|
||||
|
||||
Player {
|
||||
@@ -58,13 +56,26 @@ erDiagram
|
||||
string country
|
||||
string club
|
||||
int rating
|
||||
string rank
|
||||
int rank
|
||||
bool final
|
||||
array skip
|
||||
int mmsCorrection
|
||||
set skip
|
||||
map externalIds
|
||||
}
|
||||
|
||||
Team {
|
||||
int id
|
||||
string name
|
||||
set playerIds
|
||||
int rating
|
||||
int rank
|
||||
bool final
|
||||
int mmsCorrection
|
||||
set skip
|
||||
}
|
||||
|
||||
Standings {
|
||||
array criteria
|
||||
list criteria
|
||||
}
|
||||
|
||||
%% relationships
|
||||
@@ -72,9 +83,266 @@ erDiagram
|
||||
Tournament ||--|{ TimeSystem: "time system"
|
||||
Tournament ||--|{ Pairing: "pairing"
|
||||
Tournament ||--|{ Game: "round"
|
||||
Tournament }o--|{ Player: "participate(round)"
|
||||
Tournament }o--|{ Player: "players"
|
||||
Tournament }o--|{ Team: "teams"
|
||||
Team }o--|{ Player: "members"
|
||||
Game ||--|| Player: "black"
|
||||
Game ||--|| Player: "white"
|
||||
Player }|--|| Standings: "position"
|
||||
|
||||
```
|
||||
|
||||
## Tournament
|
||||
|
||||
Sealed class hierarchy for different tournament formats.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| id | int | Tournament identifier |
|
||||
| type | Type | Tournament format |
|
||||
| name | string | Full tournament name |
|
||||
| shortName | string | Abbreviated name |
|
||||
| startDate | date | Start date |
|
||||
| endDate | date | End date |
|
||||
| director | string | Tournament director |
|
||||
| country | string | Country code (default: "fr") |
|
||||
| location | string | Venue location |
|
||||
| online | bool | Is online tournament |
|
||||
| rounds | int | Total number of rounds |
|
||||
| gobanSize | int | Board size (default: 19) |
|
||||
| rules | Rules | Scoring rules |
|
||||
| komi | double | Komi value (default: 7.5) |
|
||||
| timeSystem | TimeSystem | Time control |
|
||||
| pairing | Pairing | Pairing system |
|
||||
| tablesExclusion | list | Table exclusion rules per round |
|
||||
|
||||
### Tournament Types
|
||||
|
||||
| Type | Players/Team | Description |
|
||||
|------|--------------|-------------|
|
||||
| INDIVIDUAL | 1 | Individual players |
|
||||
| PAIRGO | 2 | Pair Go (alternating) |
|
||||
| RENGO2 | 2 | Rengo with 2 players |
|
||||
| RENGO3 | 3 | Rengo with 3 players |
|
||||
| TEAM2 | 2 | Team with 2 boards |
|
||||
| TEAM3 | 3 | Team with 3 boards |
|
||||
| TEAM4 | 4 | Team with 4 boards |
|
||||
| TEAM5 | 5 | Team with 5 boards |
|
||||
|
||||
### Rules
|
||||
|
||||
- `AGA` - American Go Association
|
||||
- `FRENCH` - French Go Association
|
||||
- `JAPANESE` - Japanese rules
|
||||
- `CHINESE` - Chinese rules
|
||||
|
||||
## Player
|
||||
|
||||
Individual tournament participant.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| id | int | Player identifier |
|
||||
| name | string | Last name |
|
||||
| firstname | string | First name |
|
||||
| country | string | Country code |
|
||||
| club | string | Club affiliation |
|
||||
| rating | int | EGF-style rating |
|
||||
| rank | int | Rank (-30=30k to 8=9D) |
|
||||
| final | bool | Is registration confirmed |
|
||||
| mmsCorrection | int | MacMahon score correction |
|
||||
| skip | set | Skipped round numbers |
|
||||
| externalIds | map | External IDs (AGA, EGF, FFG) |
|
||||
|
||||
## Team
|
||||
|
||||
Team participant (for team tournaments).
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| id | int | Team identifier |
|
||||
| name | string | Team name |
|
||||
| playerIds | set | Member player IDs |
|
||||
| rating | int | Computed from members |
|
||||
| rank | int | Computed from members |
|
||||
| final | bool | Is registration confirmed |
|
||||
| mmsCorrection | int | MacMahon score correction |
|
||||
| skip | set | Skipped round numbers |
|
||||
|
||||
## Game
|
||||
|
||||
Single game in a round.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| id | int | Game identifier |
|
||||
| table | int | Table number (0 = unpaired) |
|
||||
| white | int | White player ID (0 = bye) |
|
||||
| black | int | Black player ID (0 = bye) |
|
||||
| handicap | int | Handicap stones |
|
||||
| result | Result | Game outcome |
|
||||
| drawnUpDown | int | DUDD value |
|
||||
| forcedTable | bool | Is table manually assigned |
|
||||
|
||||
### Result
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| ? | Unknown (not yet played) |
|
||||
| w | White won |
|
||||
| b | Black won |
|
||||
| = | Jigo (draw) |
|
||||
| X | Cancelled |
|
||||
| # | Both win (unusual) |
|
||||
| 0 | Both lose (unusual) |
|
||||
|
||||
## TimeSystem
|
||||
|
||||
Time control configuration.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| type | TimeSystemType | System type |
|
||||
| mainTime | int | Main time in seconds |
|
||||
| increment | int | Fischer increment |
|
||||
| maxTime | int | Fischer max time |
|
||||
| byoyomi | int | Byoyomi time per period |
|
||||
| periods | int | Number of byoyomi periods |
|
||||
| stones | int | Stones per period (Canadian) |
|
||||
|
||||
### TimeSystemType
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| CANADIAN | Canadian byoyomi |
|
||||
| JAPANESE | Japanese byoyomi |
|
||||
| FISCHER | Fischer increment |
|
||||
| SUDDEN_DEATH | No overtime |
|
||||
|
||||
## Pairing
|
||||
|
||||
Pairing system configuration.
|
||||
|
||||
### Pairing Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| SWISS | Swiss system |
|
||||
| MAC_MAHON | MacMahon system |
|
||||
| ROUND_ROBIN | Round robin (not implemented) |
|
||||
|
||||
### MacMahon-specific
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| mmFloor | int | MacMahon floor (default: -20 = 20k) |
|
||||
| mmBar | int | MacMahon bar (default: 0 = 1D) |
|
||||
|
||||
### Base Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| nx1 | Concavity curve factor (0.0-1.0) |
|
||||
| dupWeight | Duplicate game avoidance weight |
|
||||
| random | Randomization factor |
|
||||
| deterministic | Deterministic pairing |
|
||||
| colorBalanceWeight | Color balance importance |
|
||||
| byeWeight | Bye assignment weight |
|
||||
|
||||
### Main Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| categoriesWeight | Avoid mixing categories |
|
||||
| scoreWeight | Minimize score differences |
|
||||
| drawUpDownWeight | Draw-up/draw-down weighting |
|
||||
| compensateDrawUpDown | Enable DUDD compensation |
|
||||
| drawUpDownUpperMode | TOP, MIDDLE, or BOTTOM |
|
||||
| drawUpDownLowerMode | TOP, MIDDLE, or BOTTOM |
|
||||
| seedingWeight | Seeding importance |
|
||||
| lastRoundForSeedSystem1 | Round cutoff for system 1 |
|
||||
| seedSystem1 | First seeding method |
|
||||
| seedSystem2 | Second seeding method |
|
||||
| mmsValueAbsent | MMS for absent players |
|
||||
| roundDownScore | Floor vs round scores |
|
||||
|
||||
### Seed Methods
|
||||
|
||||
- `SPLIT_AND_FOLD`
|
||||
- `SPLIT_AND_RANDOM`
|
||||
- `SPLIT_AND_SLIP`
|
||||
|
||||
### Secondary Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| barThresholdActive | Don't apply below bar |
|
||||
| rankSecThreshold | Rank limit for criteria |
|
||||
| nbWinsThresholdActive | Score threshold |
|
||||
| defSecCrit | Secondary criteria weight |
|
||||
|
||||
### Geographical Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| avoidSameGeo | Avoid same region |
|
||||
| preferMMSDiffRatherThanSameCountry | Country preference |
|
||||
| preferMMSDiffRatherThanSameClubsGroup | Club group preference |
|
||||
| preferMMSDiffRatherThanSameClub | Club preference |
|
||||
|
||||
### Handicap Parameters
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| weight | Handicap minimization weight |
|
||||
| useMMS | Use MMS vs rank |
|
||||
| rankThreshold | Rank threshold |
|
||||
| correction | Handicap reduction |
|
||||
| ceiling | Max handicap stones |
|
||||
|
||||
## Placement Criteria
|
||||
|
||||
Tiebreak criteria for standings, in order of priority.
|
||||
|
||||
### Score-based
|
||||
|
||||
| Criterion | Description |
|
||||
|-----------|-------------|
|
||||
| NBW | Number of wins |
|
||||
| MMS | MacMahon score |
|
||||
| STS | Strasbourg score |
|
||||
| CPS | Cup score |
|
||||
| SCOREX | Congress score |
|
||||
|
||||
### Opponent-based (W = wins, M = MMS)
|
||||
|
||||
| Criterion | Description |
|
||||
|-----------|-------------|
|
||||
| SOSW / SOSM | Sum of opponent scores |
|
||||
| SOSWM1 / SOSMM1 | SOS minus worst |
|
||||
| SOSWM2 / SOSMM2 | SOS minus two worst |
|
||||
| SODOSW / SODOSM | Sum of defeated opponent scores |
|
||||
| SOSOSW / SOSOSM | Sum of opponent SOS |
|
||||
| CUSSW / CUSSM | Cumulative score sum |
|
||||
|
||||
### Other
|
||||
|
||||
| Criterion | Description |
|
||||
|-----------|-------------|
|
||||
| CATEGORY | Player category |
|
||||
| RANK | Player rank |
|
||||
| RATING | Player rating |
|
||||
| DC | Direct confrontation |
|
||||
| SDC | Simplified direct confrontation |
|
||||
| EXT | Exploits attempted |
|
||||
| EXR | Exploits successful |
|
||||
|
||||
## External Databases
|
||||
|
||||
Player IDs can be linked to external rating databases:
|
||||
|
||||
| Database | Description |
|
||||
|----------|-------------|
|
||||
| AGA | American Go Association |
|
||||
| EGF | European Go Federation |
|
||||
| FFG | French Go Association |
|
||||
|
||||
Reference in New Issue
Block a user