Initial skills documentation — 25 categories, all SKILL.md + references + scripts
This commit is contained in:
+98
@@ -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.
|
||||
Reference in New Issue
Block a user