from fastapi import APIRouter, Depends, UploadFile, File, HTTPException from fastapi import status from sqlalchemy.orm import Session as DBSession from pathlib import Path import shutil from typing import Optional from pydantic import BaseModel, EmailStr, field_validator from app.core.database import SessionLocal from app.core.auth import get_current_user from app.models.user import User router = APIRouter(prefix="/profile", tags=["profile"]) def get_db(): db = SessionLocal() try: yield db finally: db.close() # ----------------------------- # 1) Neuer PIN anfordern (Mock) # ----------------------------- @router.post("/request-new-pin", status_code=status.HTTP_202_ACCEPTED) def request_new_pin(current_user: User = Depends(get_current_user)): return {"status": "queued", "message": "PIN request queued (mock)"} # ----------------------------- # 2) Profil-Avatar hochladen (persistiert avatar_url) # ----------------------------- UPLOAD_DIR = Path("media/avatars") @router.post("/avatar", status_code=status.HTTP_200_OK) def upload_avatar( file: UploadFile = File(...), db: DBSession = Depends(get_db), current_user: User = Depends(get_current_user), ): UPLOAD_DIR.mkdir(parents=True, exist_ok=True) filename = file.filename or "avatar" ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "" if ext not in {"png", "jpg", "jpeg", "webp", "gif", ""}: raise HTTPException(status_code=400, detail="Unsupported file type") final_ext = ext if ext else "png" dest = UPLOAD_DIR / f"{current_user.id}.{final_ext}" with dest.open("wb") as out: shutil.copyfileobj(file.file, out) # User in diese Session laden u = db.query(User).filter(User.id == current_user.id).first() if not u: raise HTTPException(status_code=404, detail="User not found") u.avatar_url = f"/media/avatars/{u.id}.{final_ext}" db.commit() db.refresh(u) import time return { "status": "ok", "avatar_url": f"{u.avatar_url}?t={int(time.time())}", "path": str(dest), } # ----------------------------- # 3) Eigene Basisdaten ändern (inkl. PayPal + Passwort) # ----------------------------- class ProfileUpdate(BaseModel): alias: Optional[str] = None paypal_email: Optional[EmailStr] = None visible_in_stats: Optional[bool] = None public_stats: Optional[bool] = None # Passwortwechsel current_password: Optional[str] = None new_password: Optional[str] = None @field_validator("new_password") @classmethod def _min_len_pw(cls, v): if v is not None and len(v) < 8: raise ValueError("Password too short (min. 8)") return v @router.put("/me", response_model=dict) def update_profile( payload: ProfileUpdate, db: DBSession = Depends(get_db), current_user: User = Depends(get_current_user), ): u = db.query(User).filter(User.id == current_user.id).first() if not u: raise HTTPException(status_code=404, detail="User not found") changed = False # Alias if payload.alias is not None: u.alias = payload.alias changed = True # PayPal-Mail if payload.paypal_email is not None: u.paypal_email = str(payload.paypal_email).lower() changed = True # Sichtbarkeit (Kompatibilität: beide Keys akzeptieren) flag = payload.public_stats if payload.public_stats is not None else payload.visible_in_stats if flag is not None: u.public_stats = bool(flag) changed = True # Passwortwechsel nur mit current_password if payload.new_password is not None: from passlib.context import CryptContext pwd = CryptContext(schemes=["bcrypt"], deprecated="auto") # Wenn ein Passwort gesetzt ist, muss current_password mitgeschickt werden if getattr(u, "hashed_password", None): if not payload.current_password: raise HTTPException(status_code=400, detail="Current password required") if not pwd.verify(payload.current_password, u.hashed_password): raise HTTPException(status_code=400, detail="Current password invalid") u.hashed_password = pwd.hash(payload.new_password) changed = True if changed: db.commit() db.refresh(u) return {"status": "ok"}