141dc90db7
Release / release (push) Successful in 5s
- New .gitea/workflows/release.yml triggers on push to main - Scans commits since last v* tag and bumps strict semver - feat: -> minor, fix: -> patch, !: or BREAKING CHANGE -> major - Updates package.json + package-lock.json, commits as <version>, tags v<version>, pushes both - Tag push triggers existing deploy.yml (PACKAGES_TOKEN is a PAT so cross-workflow triggers work) - Skips silently when no releaseable commits are found - Updates CLAUDE.md release section to reflect automation
69 lines
4.9 KiB
Markdown
69 lines
4.9 KiB
Markdown
# PDGA Rating Tracker
|
||
|
||
PDGA rating scraper and display app. Scrapes player ratings and course data from pdga.com, stores in SQLite, serves via Express with EJS templates and HTMX.
|
||
|
||
## Tech Stack
|
||
|
||
- **Runtime:** Node.js 22 (slim/Debian-based in Docker)
|
||
- **Hosting:** Gitea (`gitea.shcizo.se/shcizo/pdga-rating`) — use `tea` CLI for issues/PRs, not `gh`
|
||
- **Server:** Express with EJS templates
|
||
- **Database:** SQLite3 (file: `ratings.db`, Docker: `/app/data/ratings.db`)
|
||
- **Frontend:** HTMX + vanilla JS (in `public/js/`)
|
||
- **Scraping:** Puppeteer (with stealth plugin) + direct HTTP
|
||
- **Logging:** Pino (JSON in production, pino-pretty in dev)
|
||
- **CI/CD:** Gitea Actions (tag-triggered build + push + deploy via `.gitea/workflows/deploy.yml`)
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
server.js # Express app entrypoint
|
||
src/
|
||
logger.js # Pino logger instance
|
||
db.js # SQLite init, migrations, seeding
|
||
models/ # Data access (player.js, course.js)
|
||
routes/ # Express routes (players, courses, pages)
|
||
scrapers/ # PDGA scrapers (HTTP + Puppeteer)
|
||
services/ # Business logic (player-service, rating-calculator)
|
||
views/
|
||
pages/ # EJS page templates
|
||
partials/ # EJS partials (shared layout)
|
||
public/
|
||
css/ # Stylesheets
|
||
js/ # Client-side JS (HTMX interactions)
|
||
```
|
||
|
||
## Commands
|
||
|
||
- `npm start` — Start production server (port 3000)
|
||
- `npm run dev` — Start with nodemon (auto-reload)
|
||
- `LOG_LEVEL=debug npm start` — Enable debug logging
|
||
- `docker compose up` — Run via Docker
|
||
|
||
**No test framework or lint setup** — `package.json` has only `start` and `dev` scripts. If adding either, document it here.
|
||
|
||
## Conventions
|
||
|
||
- **Logging:** Use `require('./logger')` (or relative path). Never use `console.log/error` in backend code. Use appropriate Pino levels: `debug` for verbose/diagnostic data, `info` for operational status, `warn` for retries/degraded state, `error` for failures, `fatal` for startup crashes.
|
||
- **Frontend JS:** `console.error` is fine in `public/js/` — runs in browser, no Pino.
|
||
- **Commits:** Conventional commits (`feat:`, `fix:`, `refactor:`, `chore:`, `ci:`).
|
||
- **Releases:** Automated via `.gitea/workflows/release.yml` on push to `main`. Workflow scans conventional commits sedan senaste `v*`-taggen och bumpar strikt semver: `feat:` → minor, `fix:` → patch, `!:`/`BREAKING CHANGE` → major. Andra prefix (`chore:`, `docs:`, `ci:`, `refactor:`, `style:`, `test:`) bumpar inte. Workflow:n uppdaterar `package.json` + `package-lock.json`, committar som `<version>`, taggar `v<version>` och pushar. Tag-pushen triggar `.gitea/workflows/deploy.yml` som (1) bygger + pushar image till `gitea.shcizo.se/shcizo/pdga-rating:<tag>` + `:latest`, sen (2) anropar `package-updater-action` mot `updater.shcizo.se/update`. Required secrets: `PACKAGES_TOKEN` (PAT med `write:package` + repo-push-scope — används av både release.yml för att pusha main/tag och deploy.yml för registry-auth; auto-injicerade `GITEA_TOKEN` triggar inte cross-workflows och saknar effective registry-access) och `UPDATER_API_KEY` (för updater-endpointen). Action-repot `shcizo/package-updater-action` refereras via full Gitea-URL (`https://gitea.shcizo.se/...`) eftersom `uses:` defaultar till GitHub. **Manuell tag** är fortfarande möjlig om release-workflow:n skippas eller behöver kringgås — pusha bara `v<version>`-tag direkt.
|
||
- **Scraping:** Two strategies per entity: direct HTTP (fast, preferred) with Puppeteer fallback (stealth plugin for anti-bot). Rate limiting must be respected.
|
||
- **Database:** Migrations run automatically on startup in `db.js`. Schema changes go there.
|
||
- **Templates:** EJS with shared layout in `views/partials/`. Pages use HTMX for dynamic content loading.
|
||
|
||
## PDGA Domain Notes
|
||
|
||
- **Rating publication cycle:** PDGA officially recalculates ratings on the **second Tuesday of each month**. `getNextPDGAUpdateDate()` in `src/services/rating-calculator.js` computes this — round filtering uses it as cutoff.
|
||
- **Predicted rating algorithm:** `calculatePredictedRating(roundRatings)` replicates PDGA's formula — 12-mo window (expands to 24 if <8 rounds), outlier removal at ≥7 rounds (2.5σ + 100pt threshold), double-weighting of recent 25% at ≥9 rounds. Returns `{rating, stdDev, debugLog}`.
|
||
- **Rate limits:** `POST /api/refresh-round-history/:pdgaNumber` enforces a 24h cooldown per player (`src/routes/players.js`). Don't bypass — PDGA's site rate-limits aggressively.
|
||
- **Round history refresh** uses Puppeteer (stealth plugin), other scraping prefers direct HTTP. Predicted rating is recomputed and stored on each refresh.
|
||
|
||
## Environment Variables
|
||
|
||
| Variable | Default | Description |
|
||
|----------|---------|-------------|
|
||
| `LOG_LEVEL` | `info` | Pino log level |
|
||
| `NODE_ENV` | — | Set to `production` for JSON logs |
|
||
| `DB_PATH` | `./ratings.db` | SQLite database path |
|
||
| `PUPPETEER_EXECUTABLE_PATH` | — | Chromium path (set in Docker) |
|