3a1da1482c
- FastAPI + SQLite + Alpine.js + HTMX PWA for Bingo Bango Bongo golf scoring - Passwordless magic link auth with email (aiosmtplib) and admin invite system - Real-time score entry via WebSocket with drag-and-drop player ordering - Course management with per-hole par and stroke index - Scorecard with golf score markers (birdie, eagle, bogey, etc.) - Two-step new game form with tee selection per player - Dashboard with stats, live games social stream, and recent game history - Game summary view (read-only scorecard with leaderboard) - User profile pages with win stats - PWA install support with generated PNG icons - Admin panel for users, courses, games, and invitations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
30 lines
996 B
Python
30 lines
996 B
Python
import os
|
|
from pathlib import Path
|
|
|
|
from fastapi import UploadFile
|
|
|
|
AVATARS_DIR = Path(os.getenv("DATABASE_PATH", "/data/skinspins.db")).parent / "avatars"
|
|
ALLOWED_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
|
|
MAX_SIZE = 2 * 1024 * 1024 # 2 MB
|
|
|
|
|
|
async def save_avatar(file: UploadFile, user_id: str) -> tuple[str | None, str | None]:
|
|
"""Returns (url_path, error). url_path is like /avatars/user_id.jpg"""
|
|
if file.content_type not in ALLOWED_TYPES:
|
|
return None, "Only JPEG, PNG, WebP or GIF images are allowed."
|
|
|
|
data = await file.read()
|
|
if len(data) > MAX_SIZE:
|
|
return None, "Image must be smaller than 2 MB."
|
|
|
|
ext = file.content_type.split("/")[-1].replace("jpeg", "jpg")
|
|
filename = f"{user_id}.{ext}"
|
|
dest = AVATARS_DIR / filename
|
|
|
|
# Remove any previous avatar for this user
|
|
for old in AVATARS_DIR.glob(f"{user_id}.*"):
|
|
old.unlink(missing_ok=True)
|
|
|
|
dest.write_bytes(data)
|
|
return f"/avatars/{filename}", None
|