Initial skills documentation — 25 categories, all SKILL.md + references + scripts

This commit is contained in:
root
2026-07-15 17:42:20 -04:00
commit 1b4fcd4427
976 changed files with 188344 additions and 0 deletions
@@ -0,0 +1,98 @@
# Email Invite Implementation — Jul 9, 2026
## What was built
Commissioners can invite players via email from the league settings page. The invite sends a Sho'Nuff-styled HTML email with the league's invite code and a signed JWT link.
## Files changed
### server.py (backend)
**New models:**
- `InviteRequest` (Pydantic): `{email: str}`
**New function:**
- `send_invite_email(to_email, league_name, commish_name, invite_code, signed_link)` builds and sends HTML email via the same SMTP pipeline (mail.germainebrown.com:2525)
**New endpoint:**
- `POST /api/leagues/{league_id}/invite` — commissioner-only, validates league is in draft, not full, then generates a signed JWT invite token (7-day expiry) and sends the email.
**Signed token payload:**
```python
jwt.encode({
"league_id": league_id,
"invite_code": league["invite_code"],
"exp": datetime.utcnow() + timedelta(days=7),
"iat": datetime.utcnow(),
}, JWT_SECRET, algorithm="HS256")
```
**Signed link:** `https://shark.iamgmb.com/?invite={token}`
### league.html (frontend)
**Added HTML:**
- `<div id="inviteByEmailSection">` with amber-bordered `.settings-card` wrapping:
- Email input (`#inviteEmailInput`)
- Send button (`#sendInviteBtn`)
- Status div (`#inviteStatus`)
- Only visible to commissioner
**Added JS functions:**
- `renderInviteSection()` — called from `renderLeague()`, shows/hides based on `is_commissioner`
- `sendInvite()` — validates email (non-empty, has @ and .), POSTs to `/api/leagues/{id}/invite`, shows success/error, clears input on success
## Email template
- Dark ocean background (`#0a1628`), amber headers, Sho'Nuff signature
- Prominent invite code in amber monospace (28px, 4px letter-spacing)
- "Accept Invitation" CTA button (amber gradient)
- Fallback invite code instructions
- Random Sho'Nuff title + closing quote (inline lists in server.py)
- Base64 signature image from `/root/.hermes/references/shonuff-image-b64.txt`
- BCC to `g@germainebrown.com`
## Testing
```bash
# Register a commish
POST /api/auth/register {"email": "test@example.com", "password": "testpass123", "display_name": "TestCommish"}
# Create league
POST /api/leagues {"name": "Test League", "max_players": 6}
# Send invite
POST /api/leagues/{id}/invite {"email": "g@germainebrown.com"}
```
### Server restart pitfalls
If the old process holds port 8083:
```bash
fuser -k 8083/tcp
sleep 1
fuser 8083/tcp # should print nothing
# then restart
```
Verify route registered by checking OpenAPI schema or hitting it (expect 401 without auth, not 405):
```bash
python3 -c "
import json, urllib.request
resp = urllib.request.urlopen('http://localhost:8083/openapi.json')
data = json.loads(resp.read())
for p in sorted(data.get('paths', {}).keys()):
if 'invite' in p:
print(p, ':', list(data['paths'][p].keys()))
"
```
A 405 "Method Not Allowed" means the old server is running. Kill it and restart.
## Known limitations
1. **Invite query param (`?invite=...`) is not consumed client-side** — the landing page at `index.html` doesn't read the signed token or pre-fill the invite code. Recipients must use the plain invite code manually. The token exists in the URL but the frontend join flow doesn't process it yet.
2. **Inline Sho'Nuff title/closing lists**`send_invite_email()` has hardcoded arrays matching the reference files. If `shonuff-titles.py` or `shonuff-closings.py` change, server.py must be updated manually.
3. **No rate limiting** — commissioner can hit the endpoint rapidly. SMTP spam protection is at the mail relay level.
4. **No duplicate email detection** — same email can be invited multiple times.
5. **No invite history/audit trail** — invites are fire-and-forget, no DB record.