"""BAN YARO — User-Profil Routes""" import io import os import uuid from typing import Optional from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from pydantic import BaseModel from auth import get_current_user from database import db router = APIRouter() MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media") VALID_ERFAHRUNG = {"einsteiger", "erfahren", "trainer", "zuechter"} VALID_SICHTBARKEIT = {"public", "friends", "private"} class ProfileUpdate(BaseModel): real_name: Optional[str] = None bio: Optional[str] = None wohnort: Optional[str] = None erfahrung: Optional[str] = None social_link: Optional[str] = None profil_sichtbarkeit: Optional[str] = None notes_ki_enabled: Optional[int] = None def _load_user(user_id: int) -> dict: with db() as conn: row = conn.execute( """SELECT id, name, real_name, email, rolle, is_premium, email_verified, bio, wohnort, erfahrung, social_link, profil_sichtbarkeit, avatar_url, created_at FROM users WHERE id=?""", (user_id,) ).fetchone() if not row: raise HTTPException(404, "User nicht gefunden.") data = dict(row) data["is_premium"] = bool(data["is_premium"]) return data @router.patch("") async def update_profile(data: ProfileUpdate, user=Depends(get_current_user)): fields = data.model_dump(exclude_none=True) # Validierungen if "erfahrung" in fields and fields["erfahrung"] not in VALID_ERFAHRUNG: raise HTTPException(400, f"erfahrung muss eines von {sorted(VALID_ERFAHRUNG)} sein.") if "profil_sichtbarkeit" in fields and fields["profil_sichtbarkeit"] not in VALID_SICHTBARKEIT: raise HTTPException(400, f"profil_sichtbarkeit muss eines von {sorted(VALID_SICHTBARKEIT)} sein.") if "bio" in fields and len(fields["bio"]) > 300: raise HTTPException(400, "bio darf maximal 300 Zeichen lang sein.") if "wohnort" in fields and len(fields["wohnort"]) > 60: raise HTTPException(400, "wohnort darf maximal 60 Zeichen lang sein.") if "social_link" in fields and len(fields["social_link"]) > 120: raise HTTPException(400, "social_link darf maximal 120 Zeichen lang sein.") if not fields: return _load_user(user["id"]) set_clause = ", ".join(f"{k}=?" for k in fields) values = list(fields.values()) + [user["id"]] with db() as conn: conn.execute( f"UPDATE users SET {set_clause} WHERE id=?", values ) return _load_user(user["id"]) @router.post("/avatar") async def upload_avatar( file: UploadFile = File(...), user=Depends(get_current_user), ): # HEIC-Support registrieren falls vorhanden try: import pillow_heif pillow_heif.register_heif_opener() except ImportError: pass from PIL import Image, ImageOps content = await file.read() try: img = Image.open(io.BytesIO(content)) img = ImageOps.exif_transpose(img) # EXIF-Orientierung anwenden img = img.convert("RGB") buf = io.BytesIO() img.save(buf, format="JPEG", quality=90) content = buf.getvalue() except Exception: pass # Fallback: Originaldaten speichern filename = f"avatar_{user['id']}_{uuid.uuid4().hex[:8]}.jpg" path = os.path.join(MEDIA_DIR, "avatars", filename) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "wb") as f: f.write(content) avatar_url = f"/media/avatars/{filename}" with db() as conn: conn.execute( "UPDATE users SET avatar_url=? WHERE id=?", (avatar_url, user["id"]) ) return {"avatar_url": avatar_url}