Add full SkinsPins application
- 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>
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
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
|
||||
Reference in New Issue
Block a user