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
4.9 KiB
4.9 KiB
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) — useteaCLI for issues/PRs, notgh - 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 loggingdocker 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 useconsole.log/errorin backend code. Use appropriate Pino levels:debugfor verbose/diagnostic data,infofor operational status,warnfor retries/degraded state,errorfor failures,fatalfor startup crashes. - Frontend JS:
console.erroris fine inpublic/js/— runs in browser, no Pino. - Commits: Conventional commits (
feat:,fix:,refactor:,chore:,ci:). - Releases: Automated via
.gitea/workflows/release.ymlon push tomain. Workflow scans conventional commits sedan senastev*-taggen och bumpar strikt semver:feat:→ minor,fix:→ patch,!:/BREAKING CHANGE→ major. Andra prefix (chore:,docs:,ci:,refactor:,style:,test:) bumpar inte. Workflow:n uppdaterarpackage.json+package-lock.json, committar som<version>, taggarv<version>och pushar. Tag-pushen triggar.gitea/workflows/deploy.ymlsom (1) bygger + pushar image tillgitea.shcizo.se/shcizo/pdga-rating:<tag>+:latest, sen (2) anroparpackage-updater-actionmotupdater.shcizo.se/update. Required secrets:PACKAGES_TOKEN(PAT medwrite: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-injiceradeGITEA_TOKENtriggar inte cross-workflows och saknar effective registry-access) ochUPDATER_API_KEY(för updater-endpointen). Action-repotshcizo/package-updater-actionrefereras via full Gitea-URL (https://gitea.shcizo.se/...) eftersomuses:defaultar till GitHub. Manuell tag är fortfarande möjlig om release-workflow:n skippas eller behöver kringgås — pusha barav<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()insrc/services/rating-calculator.jscomputes 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/:pdgaNumberenforces 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) |