249 lines
16 KiB
Markdown
249 lines
16 KiB
Markdown
# 🛠️ PinPoint Golf — Developer & Architecture Guide
|
||
|
||
This guide is written for engineers looking to maintain, customize, or extend **PinPoint Golf**. It explains the full-stack architecture, user authentication flows, OAuth/SSO integration, database persistence, community benchmark mathematics, and outlines the prioritized roadmap of future features.
|
||
|
||
---
|
||
|
||
## 1. Project Directory Structure
|
||
|
||
```
|
||
.
|
||
├── server.ts # Full-stack Express server (API endpoints + Vite middleware / static files)
|
||
├── Dockerfile # Multi-stage production container build (Node.js -> Node.js Runner)
|
||
├── docker-compose.yaml # Compose file with persistent volume and OAuth environment vars
|
||
├── .env.example # Template for Google Maps, JWT, Google OAuth, and Authentik OIDC
|
||
├── index.html # HTML entry point with Plus Jakarta Sans & JetBrains Mono
|
||
├── package.json # Dependencies, esbuild scripts, and npm scripts
|
||
├── vite.config.ts # Vite 8 configuration with Tailwind v4 & path aliases
|
||
├── data/
|
||
│ └── pinpoint_db.json # Atomic JSON persistent database (users, rounds, settings, courses)
|
||
├── src/
|
||
│ ├── main.tsx # React root mount point
|
||
│ ├── App.tsx # Root coordinator, global state, persistence, sync & modal hubs
|
||
│ ├── index.css # Tailwind CSS v4 setup & custom scrollbars
|
||
│ ├── context/
|
||
│ │ └── AuthContext.tsx # User session state, JWT tokens, login, register, and SSO hooks
|
||
│ ├── types/
|
||
│ │ ├── golf.ts # Core golf interfaces (Hole, Course, Round, Stats, GPS)
|
||
│ │ └── auth.ts # User profiles, PublicGolferStats, CommunityComparison schemas
|
||
│ ├── data/
|
||
│ │ ├── courses.ts # Default course presets (Pebble Cove, Pine Valley, St Andrews)
|
||
│ │ └── sampleRounds.ts # Preloaded 18-hole demonstration round with full stats
|
||
│ ├── services/
|
||
│ │ ├── api.ts # Full client HTTP client with Bearer auth headers
|
||
│ │ ├── golfApi.ts # OpenGolfAPI client for searching 32,000+ public courses
|
||
│ │ └── weatherService.ts # Open-Meteo Open Weather API client & GPS geolocation service
|
||
│ ├── server/
|
||
│ │ ├── db.ts # Database manager (atomic file writes, seed golfers, user scoping)
|
||
│ │ ├── authMiddleware.ts # JWT verification & request user injection
|
||
│ │ └── routes/
|
||
│ │ ├── authRoutes.ts # Local register/login, user profile, settings, and SSO endpoints
|
||
│ │ ├── roundsRoutes.ts # Scoped rounds CRUD, sync-batch, and custom course endpoints
|
||
│ │ └── communityRoutes.ts # Global public leaderboard and comparative benchmark engine
|
||
│ ├── utils/
|
||
│ │ ├── golfStats.ts # Pure statistical calculation engine & scoring formulas
|
||
│ │ ├── geoUtils.ts # Haversine distance, bearings, green edges & hole auto-detect
|
||
│ │ └── achievements.ts # Automated trophy & achievements evaluator (23 achievements)
|
||
│ └── components/
|
||
│ ├── Navbar.tsx # 3-Zone top bar + user avatar profile dropdown + mobile tab bar
|
||
│ ├── AuthModal.tsx # Tabbed modal: Local Auth (Email/Pass), Register, and SSO/Authentik
|
||
│ ├── CommunityComparisonModal.tsx # Head-to-head comparison cards & global public leaderboard
|
||
│ ├── HoleEntry.tsx # Tactile on-course scoring UI with large touch hitboxes
|
||
│ ├── GolfGpsRangefinder.tsx # Google Maps satellite map + Front/Mid/Back digital HUD
|
||
│ ├── Scorecard.tsx # Paper-style 18-hole digital scorecard with Out/In splits
|
||
│ ├── Analytics.tsx # Performance dashboard (FIR radar, GIR misses, bunker saves)
|
||
│ ├── Settings.tsx # Granular stat switchboard & 1-click tracking presets
|
||
│ ├── NewRoundModal.tsx # Start round dialog with OpenGolfAPI search & tees
|
||
│ ├── CourseEditorModal.tsx # Builder to create new courses or duplicate existing ones
|
||
│ ├── RoundsHistoryModal.tsx # Round history, round switching, and CSV/text export
|
||
│ └── AchievementsModal.tsx # Trophy cabinet with tier filters and progress tracking
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Authentication & Identity Architecture
|
||
|
||
PinPoint Golf supports dual-mode authentication:
|
||
1. **Local Authentication**:
|
||
- Passwords hashed with `bcryptjs` (salt rounds: 10).
|
||
- Generates signed JWTs (30-day expiry) containing user `id`, `email`, and `username`.
|
||
- Passwords and sensitive fields are stripped before transmitting user objects.
|
||
2. **OAuth & SSO Providers**:
|
||
- **Google OAuth 2.0**: Compatible with Google Identity Services. Configurable via `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`.
|
||
- **Authentik (OIDC)**: Designed for homelab setups, enterprise Identity Providers, and Keycloak. Configurable via `AUTHENTIK_ISSUER_URL`, `AUTHENTIK_CLIENT_ID`, and `AUTHENTIK_CLIENT_SECRET`.
|
||
- **GitHub OAuth**: Configurable via `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`.
|
||
- **Zero-Friction Test Mode**: If external secrets are not yet provisioned in `.env`, the modal allows one-click instant testing using authentic golfer SSO credentials.
|
||
|
||
### API Auth Endpoints:
|
||
| Endpoint | Method | Auth Required | Description |
|
||
|---|---|---|---|
|
||
| `/api/auth/register` | POST | No | Create local account with email, username, password, handicap |
|
||
| `/api/auth/login` | POST | No | Authenticate with email/username + password |
|
||
| `/api/auth/me` | GET | Bearer | Retrieve profile and synchronized tracking settings |
|
||
| `/api/auth/profile` | PUT | Bearer | Update name, handicap index, home club, avatar, or `isPublic` flag |
|
||
| `/api/auth/settings` | GET/PUT | Bearer | Read or persist user tracking switchboard preferences |
|
||
| `/api/auth/oauth/providers` | GET | No | Discovery endpoint for configured OAuth options |
|
||
| `/api/auth/oauth/sso-login` | POST | No | Ingest OAuth token / SSO identity and return JWT |
|
||
|
||
---
|
||
|
||
## 3. Persistent Database (`data/pinpoint_db.json`)
|
||
|
||
The datastore in `src/server/db.ts` uses atomic temp-file write-and-rename semantics (`fs.renameSync`) to ensure durability against crashes. In Docker, it mounts to the named volume `pinpoint_data:/app/data`.
|
||
|
||
### Database Schema:
|
||
```typescript
|
||
interface DatabaseSchema {
|
||
users: Array<User & { passwordHash?: string }>;
|
||
settings: Record<string, TrackingSettings>; // userId -> settings
|
||
rounds: Record<string, Round[]>; // userId -> rounds
|
||
customCourses: Record<string, Course[]>; // userId -> courses
|
||
publicSeedGolfers: PublicGolferStats[]; // Seed public players for comparison
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Community Benchmark & Public Comparison Engine
|
||
|
||
Users can toggle their profile between **Public** (default) and **Private** via the profile dropdown or the Community modal:
|
||
- When `isPublic: true`, their rounds automatically compute into the global database metrics:
|
||
- **Scoring Average** (18-hole stroke average)
|
||
- **FIR %** (Fairways in Regulation)
|
||
- **GIR %** (Greens in Regulation)
|
||
- **Putts Per Round** (PPR)
|
||
- **Scramble %** (Up & Down par saves after missing GIR)
|
||
- **Sand Save %** (Greenside bunker par conversion)
|
||
- **Penalties Per Round** (Strokes lost to hazards/OB)
|
||
- The `/api/community/compare` endpoint calculates:
|
||
- Global community averages across all public scorecards.
|
||
- Handicap bracket averages: **Scratch (< 5.0)**, **Mid-Cap (5.0–15.0)**, and **High-Cap (> 15.0)**.
|
||
- Percentile rankings (0–100%) for each metric.
|
||
- Automated coaching insights identifying the golfer's highest-leverage scoring improvements.
|
||
|
||
---
|
||
|
||
## 5. Real-Time Weather Forecast Integration (Open Weather API)
|
||
|
||
Before teeing off, golfers evaluate live environmental conditions—temperature, wind velocity, heading, and precipitation probability—to select appropriate clubs, prepare rain equipment, and adjust for air density effects on ball carry.
|
||
|
||
### Architecture & Service Design (`src/services/weatherService.ts`)
|
||
- **Open Weather API Provider**: Uses the **Open-Meteo Meteorological API** (`https://api.open-meteo.com/v1/forecast`), an open-source, non-commercial and commercial-ready meteorological service operated with Open Database License (ODbL).
|
||
- **Zero API-Key Requirement**: Works out-of-the-box with zero paid subscription dependencies or API keys.
|
||
- **Dual Geolocation Resolution Strategy**:
|
||
1. **Active Device GPS Position**: Calls browser `navigator.geolocation.getCurrentPosition` with `enableHighAccuracy: true` to get the player's immediate physical coordinates.
|
||
2. **Course Location Fallback**: If the player is planning a round indoors, has GPS disabled, or denies browser permission, the system automatically falls back to coordinates from the selected course's Hole 1 tee box or course center (e.g. Pebble Beach, Pine Valley, St Andrews).
|
||
3. **Seamless Toggle**: Users can switch between `[ 📍 My GPS ]` and `[ ⛳ Course GPS ]` at any time with a 1-click refresh button.
|
||
|
||
### Real-Time Weather Metrics Captured
|
||
| Metric | Unit | API Parameter | Relevance to Golf |
|
||
|---|---|---|---|
|
||
| **Current Temperature** | °C (with °F converted) | `current.temperature_2m` | Ball compression and air density (colder air = less carry distance) |
|
||
| **Feels-Like Temperature** | °C | `current.apparent_temperature` | Golfer comfort, layering, and thermal prep |
|
||
| **Wind Speed** | km/h | `current.wind_speed_10m` | Club adjustment (rule of thumb: ~1 club per 15 km/h wind) |
|
||
| **Wind Direction & Heading** | 16-point Compass (`SW`) + Degrees (0–360°) | `current.wind_direction_10m` | Aim lines and shot shaping (crosswind, headwind, tailwind) |
|
||
| **Wind Intensity Category** | `Calm` / `Breezy` / `Strong` / `Gusty` | Auto-classified based on speed | Stored directly in Round summary for fast filtering |
|
||
| **Precipitation Probability** | % (0–100) | `hourly.precipitation_probability` | Rain risk assessment and green speed (wet greens roll slower) |
|
||
| **Weather Condition & WMO Code** | WMO 4677 code | `current.weather_code` | Mapped to UI icons (`Sun`, `CloudSun`, `CloudRain`, `CloudLightning`) |
|
||
| **Relative Humidity** | % | `current.relative_humidity_2m` | Atmospheric moisture and swing comfort |
|
||
|
||
### Scorecard Persistence & Data Flow
|
||
When a player starts a new round via `NewRoundModal`:
|
||
1. The forecast is automatically preloaded via GPS.
|
||
2. The user can review the live weather card and make optional manual adjustments or notes.
|
||
3. Upon clicking **Tee Off**, the full `WeatherForecast` object is attached to `round.weatherData`, and summary strings (`weather` and `wind`) are recorded.
|
||
4. Saved rounds in `Scorecard`, `RoundsHistoryModal`, and exports reflect the actual weather encountered during the round.
|
||
|
||
---
|
||
|
||
## 6. How-To Guides for Extending the App
|
||
|
||
### Guide A: Adding a New Tracked Stat (e.g. Approach Shot Club)
|
||
1. **Update Data Types (`src/types/golf.ts`)**: Add `approachClub?: string` to `HoleScore` and `trackApproachClub: boolean` to `TrackingSettings`.
|
||
2. **Update Settings Defaults (`src/data/courses.ts`)**: Set default in `DEFAULT_TRACKING_SETTINGS`.
|
||
3. **Add Toggle Switch (`src/components/Settings.tsx`)**: Add toggle under the relevant category.
|
||
4. **Add Input Controls (`src/components/HoleEntry.tsx`)**: Render input when `settings.trackApproachClub` is active.
|
||
5. **Scorecard & Analytics (`Scorecard.tsx` & `golfStats.ts`)**: Display row in scorecard and aggregate in round stats.
|
||
|
||
### Guide B: Adding a New Achievement / Trophy
|
||
1. Add definition in `ALL_ACHIEVEMENTS` in `src/utils/achievements.ts`.
|
||
2. Add rule evaluation in `evaluateAchievements()`.
|
||
3. Add icon mapping in `src/components/AchievementsModal.tsx`.
|
||
|
||
---
|
||
|
||
## 7. 🎯 Wanted / Future Features Roadmap
|
||
|
||
Here is the prioritized list of planned future enhancements:
|
||
|
||
### High Priority
|
||
1. **Strokes Gained (SG) Analytics Engine**:
|
||
- Compare shot performance against PGA Tour and scratch golfer baselines.
|
||
- Categorize strokes gained into **SG: Off-the-Tee (OTT)**, **SG: Approach**, **SG: Around-the-Green (ARG)**, and **SG: Putting**.
|
||
2. **Official World Handicap System (WHS) Calculator**:
|
||
- Automatic calculation of **Handicap Differential**: $\text{Differential} = \frac{113}{\text{Slope}} \times (\text{Adjusted Gross Score} - \text{Course Rating} - \text{PCC})$.
|
||
- Automatic 20-round scoring record maintaining the official index from the best 8 differentials.
|
||
3. **Multiplayer Live Round & Foursome Matchplay**:
|
||
- Real-time WebSockets synchronization so groups playing together can view live leaderboards, skins, match play, and stableford games hole-by-hole.
|
||
|
||
### Medium Priority
|
||
4. **AI Caddie & Shot Recommender**:
|
||
- Based on user's logged club distances, live GPS yardage to the pin, wind speed, and elevation, automatically suggest club selection and target aim line.
|
||
5. **Ball Flight & Dispersion Heatmaps on GPS Green Overlays**:
|
||
- Plot approach shot landing spots visually on the satellite map green to detect left/right slice or pull patterns.
|
||
6. **Progressive Web App (PWA) Offline Service Worker**:
|
||
- Installable on iOS/Android home screens with offline caching for courses with no cellular reception.
|
||
7. **Apple Watch / Wear OS Companion App**:
|
||
- High-contrast wrist interface showing Bushnell-style Front/Center/Back yardages and quick 1-tap score increments.
|
||
|
||
### Community & Social
|
||
8. **Golf Society & Tournament Director Mode**:
|
||
- Flight divisions (A/B/C), net and gross prizes, two-man scrambles, and printable tournament scorecards.
|
||
9. **Course Review & Condition Notes**:
|
||
- Golfer feedback on green speeds (Stimp reading), aerated greens notices, and course ratings.
|
||
|
||
---
|
||
|
||
## 8. Scripts Reference
|
||
|
||
```bash
|
||
# Start full-stack development server on port 3000
|
||
npm run dev
|
||
|
||
# Run TypeScript typechecks
|
||
npm run lint
|
||
|
||
# Build production bundle (client + server.cjs)
|
||
npm run build
|
||
|
||
# Clean temporary files
|
||
npm run clean
|
||
```
|
||
|
||
---
|
||
|
||
## 9. Clean Deployment & Data Integrity Rules
|
||
|
||
To guarantee enterprise-grade data hygiene and tour-level integrity, PinPoint Golf enforces strict separation between development testing and production deployment:
|
||
|
||
### 1. Mock Data Elimination in Production
|
||
- **Server Store**: In production (`NODE_ENV=production` or `ENABLE_MOCK_DATA!=true`), the database manager `src/server/db.ts` suppresses demo accounts (`usr_demo_golfer`) and seeded community golfers (`user_seed_*`). Only real registered users and scorecards exist.
|
||
- **Client App**: The client initializes rounds state to an empty list `[]` instead of pre-seeding `SAMPLE_ROUND`. The "Load Dev Sample Round" button is stripped in production builds (`import.meta.env.PROD`).
|
||
|
||
### 2. Authentication-Gated Features
|
||
The following features are exclusively unlocked for registered golfers:
|
||
- **Community Benchmarks & Leaderboards**: Public comparison of handicap, FIR%, GIR%, putts, and scrambles against peers.
|
||
- **Career Achievements & Trophies**: 22 tier-ranked achievements across scoring, accuracy, and short game.
|
||
- **Satellite GPS Rangefinder**: Real-time Google Maps satellite flyover, Front/Center/Back green distance calculation, pin targeting, and shot distance measurement.
|
||
- *Guests & Unauthenticated Users*: Can start rounds, view course scorecards, and check local play, but receive clear in-app prompts to sign in or create a free account to unlock GPS, community leaderboards, and trophies.
|
||
|
||
### 3. Achievement Lifecycle Rules
|
||
- **Empty Initial State**: New registered users start with zero achievements unlocked (`unlockedIds: []`).
|
||
- **Finalization Requirement**: Achievements are never awarded during live play; they are evaluated and awarded **strictly upon finishing and saving a completed round** (`isCompleted: true`).
|
||
|
||
### 4. Round Immutability & Deletion Workflow
|
||
- **Confirmation Modal**: Clicking "Finalize & Save Round" triggers `SaveRoundModal`, showing a complete scoring summary and an explicit warning that the round will be permanently locked.
|
||
- **Post-Save Immutability**: Once saved (`isCompleted: true`), hole scores and statistical inputs are locked against modification.
|
||
- **Deletion Only**: The only permitted post-save action on a finalized round is deletion from the golfer's history with confirmation.
|
||
|