Initial skills documentation — 25 categories, all SKILL.md + references + scripts
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
# Shark Attack Fantasy Game — Full-Stack Pattern
|
||||
|
||||
## Architecture
|
||||
```
|
||||
iOS Safari → Cloudflare → Caddy (shark.iamgmb.com) → FastAPI (port 8083)
|
||||
├── /api/* (auth, leagues, draft, scores)
|
||||
└── /* (static frontend via FileResponse catch-all)
|
||||
```
|
||||
|
||||
## Domain / DNS setup
|
||||
- Domain: `shark.iamgmb.com` (Cloudflare zone f1fb2d357b8ff0fab54c5856130ec9ed)
|
||||
- Points to Core's IP (152.53.192.33) via A record (not CNAME — CNAME flattens fail for cross-account zones)
|
||||
- DNS-only initially, switch to proxied after TLS cert provisions
|
||||
- Caddy full-proxy pattern: EVERYTHING routes through the backend on 8083
|
||||
|
||||
## Backend features learned
|
||||
- **FastAPI monolithic server.py** — ~980 lines, no routers, all endpoints in one file
|
||||
- **SQLite** with WAL mode + foreign keys
|
||||
- **Auth:** bcrypt password hashing, PyJWT tokens, 30-day expiry
|
||||
- **JWT gotcha:** `datetime.utcnow()` vs `datetime.now(timezone.utc)` — PyJWT expects naive UTC
|
||||
- **Registration response must include `display_name`** — frontend uses it immediately
|
||||
- **Snake draft logic:** round 1: 1→N, round 2: N→1, round 3: 1→N. Track via round number: odd rounds are forward, even are reverse
|
||||
- **Scoring engine:** events table + materialized user_scores table recalculated on each insert
|
||||
- **Point values:** sighting=2, bite=5, fatality=10
|
||||
|
||||
## Draft regulation
|
||||
- **Minimum 2 members** to start a draft (server enforces)
|
||||
- **Commissioner-only** start draft endpoint
|
||||
- **Turn enforcement by draft_order** — server rejects out-of-turn picks with 403
|
||||
- **Region locking** — duplicate picks return 409
|
||||
|
||||
## 32 Regions with ISAF-based data
|
||||
|
||||
Top 5 regions by 10-year sighting count:
|
||||
| Region | Sightings | Bites | Fatalities |
|
||||
|--------|-----------|-------|-----------|
|
||||
| Australia East | 87 | 28 | 6 |
|
||||
| Florida East Coast | 85 | 25 | 1 |
|
||||
| Hawaii | 47 | 12 | 2 |
|
||||
| Australia West | 42 | 15 | 8 |
|
||||
| Caribbean | 40 | 10 | 2 |
|
||||
|
||||
Full data saved to `/root/shark-game/references/shark-incident-data-10yr.md` sourced from ISAF year-by-year reports.
|
||||
|
||||
## Caddy full-proxy pattern
|
||||
When the backend serves BOTH API and static files, use:
|
||||
```
|
||||
shark.iamgmb.com {
|
||||
reverse_proxy 127.0.0.1:8083
|
||||
}
|
||||
```
|
||||
Backend catch-all route:
|
||||
```python
|
||||
@app.api_route("/{path_name:path}", methods=["GET"])
|
||||
async def serve_frontend(path_name: str):
|
||||
if path_name.startswith("api/"):
|
||||
return {"error": "not found"}
|
||||
file_path = os.path.join(STATIC_DIR, path_name)
|
||||
if os.path.isfile(file_path):
|
||||
return FileResponse(file_path)
|
||||
return FileResponse(os.path.join(STATIC_DIR, "index.html"))
|
||||
```
|
||||
|
||||
## Frontend-JWT integration pattern
|
||||
- Token stored in `localStorage` as `shark_token`, user as `shark_user`
|
||||
- Auth guard on each page: `if (!getToken()) window.location.href = 'index.html';`
|
||||
- API base is empty string (same-origin)
|
||||
- Registration sends: `{email, password, display_name, phone?}`
|
||||
- After auth, checks `/api/leagues` to determine next screen
|
||||
- Pages: index.html (landing/register/login/league-mgmt), draft-room.html (draft/leaderboard/shark-feed/learn), league.html (settings/invite)
|
||||
|
||||
## Multi-tab draft room pattern
|
||||
The draft room has 6 tabs:
|
||||
1. DRAFT — Map view (Leaflet/OSM) | List view toggle
|
||||
2. MY TEAM — User's drafted regions + cumulative score
|
||||
3. STANDINGS — Leaderboard (all players ranked) + daily breakdown
|
||||
4. 🦈 LEARN — Species guide, shark facts, safety tips, conservation
|
||||
5. LEAGUE — Invite code, member list, start draft button (commissioner)
|
||||
6. 🦈 SHARK FEED — Upcoming shark media (Shark Week, movies) + general shark news
|
||||
|
||||
## AI scraper pattern
|
||||
- Daily cron: Firecrawl search → admin-ai classification → pending-scores.json
|
||||
- Region matching via keyword/fuzzy matching of article text/location
|
||||
- Dedup via processed-urls.json
|
||||
|
||||
## Common pitfalls
|
||||
- **Missing `.hidden` CSS class** — New standalone pages often define `.hidden` via JS but forget the CSS rule `.hidden { display: none !important; }` in `<style>`. Index pages get it; sibling pages don't. Always add it to every new page.
|
||||
- **Registration returns `display_name: null`** — The `/api/auth/register` endpoint must pass `display_name=req.display_name` in the response, not just write it to the DB. Login endpoint does this correctly (reads from DB); register endpoint must be patched to match.
|
||||
- **Cross-account DNS** — A records work where CNAMEs silently fail for Cloudflare cross-account setups.
|
||||
- **Serve both frontend and API from one backend** — Full-proxy through FastAPI + FileResponse catch-all avoids Caddy routing conflicts.
|
||||
Reference in New Issue
Block a user