Chess Club Admin System -- a Scala 3 / ZIO application that integrates with the Chess.com public API for club membership tracking, player recruitment, and match history analysis.
- Scala 3.8.3, SBT 1.12.8
- ZIO 2.x (zio-http, zio-json, zio-config)
- Magnum ORM + PostgreSQL 17
- Docker Compose for local dev
Prerequisites: JDK 21+, SBT, Docker
# Start PostgreSQL (creates both ccas and ccas_test databases)
docker compose up -d
# Configure environment
cp .env.example .env # edit with your values
# Run the server (default port 8080)
sbt "runMain ccas.cli.Main serve"
# Run tests (requires ccas_test database from docker compose)
sbt testsbt stage (sbt-native-packager) produces two launcher scripts under target/universal/stage/bin/:
ccas— the CLI / primary entry pointccas-server— the standalone server entry (used by hosted deploys)
To run ccas from anywhere, stage once and symlink the launcher into a directory on your PATH:
sbt stage
ln -sf "$PWD/target/universal/stage/bin/ccas" ~/.local/bin/ccas # ensure ~/.local/bin is on your PATH
ccas --helpThe symlink keeps working across rebuilds — sbt stage regenerates the launcher in place. Re-run sbt stage after changing code.
The CLI is a thin HTTP client to a local CcasServer, so start a server first (it is not auto-spawned):
# Terminal 1 — the server. Needs DATABASE_URL/DB_* and CCAS_CONTACT_EMAIL.
# Under sbt these are sourced from .env automatically:
sbt "runMain ccas.cli.Main serve" # boots CcasServer on 127.0.0.1:8080
# Or, with those vars exported into the environment, the staged binary: ccas serve
# Terminal 2 — commands. No environment needed; they just call the server.
ccas jobs
ccas membership <club-slug>
ccas recruit --target 30 <club-slug>
ccas blacklist list <club-slug>
ccas --help # full command tree
ccas <command> --help # per-command flagsCommands: serve, membership, history, recruit, stats, jobs, logs, blacklist {add|list|remove}, schedule {list|add|remove}. A global --server <url> overrides the default http://127.0.0.1:8080.
ccas --version prints the version; ccas --help and ccas <command> --help show usage.
Two gotchas. The parser (zio-cli) expects options before positional arguments —
ccas membership --no-trust-usernames team-alpha, not… team-alpha --no-trust-usernames(a misplaced flag is silently dropped). And the staged binary reads configuration from the process environment only — it does not load.env(that is auto-sourced forsbt run/sbt runMain).
bash (instant, recommended). A static script ships in completions/; it runs entirely in the shell (no JVM), so <TAB> is immediate. Install once:
mkdir -p ~/.local/share/bash-completion/completions
ln -sf "$PWD/completions/ccas.bash" ~/.local/share/bash-completion/completions/ccasbash-completion auto-loads it on first use (or source completions/ccas.bash from ~/.bashrc). The symlink picks up updates automatically; use cp instead to pin a copy. It mirrors the command tree (subcommands + flags) and the TestCcasCompletion test fails if the two drift apart.
zsh / fish. zio-cli generates a dynamic script:
ccas --shell-completion-script "$(command -v ccas)" --shell-type zsh 2>/dev/null > <dest> # or --shell-type fishCaveat: the dynamic script calls back into the binary, so each <TAB> boots the JVM (~1.5s). The static bash script avoids this; a fast cache-based completion for all shells is tracked in #49.
Jobs can be submitted via the REST API or run on a schedule.
| Job | Description |
|---|---|
| Recruitment | Scouts players from source clubs, filters by rating/activity/experience criteria, produces recruitment candidates |
| Membership | Reconciles the club member list against the Chess.com API, tracks joins and departures |
| History | Crawls match archives for club members, discovering new players wave by wave |
| MatchRef | Resolves player/club references to concrete match boards (runs automatically after other jobs) |
| Blacklist | Adds a player to the recruitment blacklist |
GET /health 200 (liveness)
GET /health/ready 200 | 503 (checks DB connectivity)
POST /api/jobs/recruitment
{ clubSlug, alias?, target?, cumulative?, sourceClubs?, timeLimitMinutes?, explore? }
POST /api/jobs/membership
{ clubSlug, trustUsernames? }
POST /api/jobs/history
{ clubSlug, full?, refresh? }
POST /api/jobs/matchref
(no body)
POST /api/jobs/blacklist
{ clubSlug, username, reason?, expiresAt? }
GET /api/jobs List recent jobs (last 50)
GET /api/jobs/:id Job status by ID
GET /api/schedules List all schedules
POST /api/schedules { kind, clubSlug?, params?, intervalHours }
PUT /api/schedules/:id { intervalHours?, enabled?, params? }
DELETE /api/schedules/:id
All environment variables are listed in .env.example. Required variables:
| Variable | Description |
|---|---|
CCAS_CONTACT_EMAIL |
Used in User-Agent header for Chess.com API requests |
DATABASE_URL or DB_USER + DB_PASSWORD + DB_NAME + DB_PORT + DB_HOST + DB_SCHEMA |
PostgreSQL connection. DATABASE_URL (JDBC form, single-quoted to escape &) takes priority; the DB_* fields are only consulted when it's absent |
Optional overrides with defaults:
| Variable | Default | Description |
|---|---|---|
SERVER_PORT |
8080 | HTTP server port |
SCHEDULER_POLL_MINUTES |
15 | How often the scheduler checks for due jobs. Keep ≥ 15 against Neon so polls don't keep the compute always-warm (it auto-suspends after ~5 min idle, budget is 192 active-hr/mo on free tier) |
DB_POOL_MAX / DB_POOL_MIN_IDLE |
20 / 2 | HikariCP pool sizing (set MIN_IDLE=0 for Neon scale-to-zero) |
DB_POOL_CONNECTION_TIMEOUT / DB_POOL_IDLE_TIMEOUT / DB_POOL_MAX_LIFETIME / DB_POOL_KEEPALIVE_TIME |
30 000 / 600 000 / 1 800 000 / 120 000 ms | HikariCP timeouts |
CHESS_COM_API_PERMITS |
16 | Max parallel Chess.com API requests |
CHESS_COM_API_COOLDOWN_SECONDS |
30 | Backoff cooldown after rate limiting |
CHESS_COM_API_CACHE_RETENTION_DAYS |
7 | How long cached Chess.com responses are kept before startup pruning |
See application.conf for the full set of tunable parameters.
src/main/scala/ccas/
api/ Chess.com API models and client
analysis/ Domain tables and business logic (apps)
server/ HTTP server, job runner, scheduler
utils/ Shared infrastructure (HTTP client, JSON, SQL, logging)
- Adaptive rate limiting -- design notes on the Chess.com API throttling strategy
- CLAUDE.md -- detailed architecture and code patterns for AI-assisted development