# app/api/topups.py from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.orm import Session from typing import List, Optional, Literal from pydantic import BaseModel from app.core.database import get_db from app.core.auth import get_current_user, requires_role from app.models.user import User from app.models.topup import Topup, TopupStatus as DBTopupStatus from app.schemas.topup import TopupOut, TopupCreate # Pydantic v2 Schemas # Optionales Audit-Log – nur nutzen, wenn vorhanden try: from app.models.audit_log import AuditLog, AuditAction HAS_AUDIT = True except Exception: AuditLog = None AuditAction = None HAS_AUDIT = False router = APIRouter(prefix="/topups", tags=["topups"]) # --------------------------- # CREATE (User oder Admin) # --------------------------- @router.post("/", response_model=TopupOut, status_code=status.HTTP_201_CREATED) def create_topup( topup: TopupCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): """ Legt ein Topup an. amount_cents ist Pflicht. user_id ist optional; wenn nicht gesetzt, wird der aktuelle User verwendet. """ payload = topup.model_dump() # Pydantic v2 # user_id für User-Flow ergänzen user_id = payload.get("user_id") or current_user.id amount_cents = int(payload.get("amount_cents") or 0) if amount_cents <= 0: raise HTTPException(status_code=400, detail="amount_cents must be > 0") db_topup = Topup( user_id=user_id, amount_cents=amount_cents, paypal_email=payload.get("paypal_email"), note=payload.get("note"), # status default: pending (siehe Model) ) db.add(db_topup) db.commit() db.refresh(db_topup) if HAS_AUDIT: db.add( AuditLog( user_id=db_topup.user_id, action=getattr(AuditAction, "topup_create", "topup_create"), amount_cents=db_topup.amount_cents, info=f"created by {current_user.id}", ) ) db.commit() return db_topup # --------------------------- # LIST (Admin/Manager, gefiltert) # --------------------------- @router.get("/", response_model=List[TopupOut], dependencies=[Depends(requires_role("manager", "admin"))]) def list_topups( user_id: Optional[int] = Query(default=None), status_filter: Optional[str] = Query(default=None, description="pending|confirmed|rejected"), limit: int = Query(default=100, ge=1, le=1000), offset: int = Query(default=0, ge=0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): q = db.query(Topup) if user_id is not None: q = q.filter(Topup.user_id == user_id) if status_filter: try: enum_val = DBTopupStatus(status_filter) except ValueError: raise HTTPException(status_code=400, detail="invalid status_filter") q = q.filter(Topup.status == enum_val) q = q.order_by(Topup.id.desc()).limit(limit).offset(offset) return q.all() # --------------------------- # LIST OWN (alle eingeloggten) # --------------------------- @router.get("/me", response_model=List[TopupOut]) def list_my_topups( limit: int = Query(default=100, ge=1, le=1000), offset: int = Query(default=0, ge=0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): q = ( db.query(Topup) .filter(Topup.user_id == current_user.id) .order_by(Topup.id.desc()) .limit(limit) .offset(offset) ) return q.all() # --------------------------- # STATUS ändern (Admin/Manager) # --------------------------- class TopupStatusIn(BaseModel): status: Literal["confirmed", "rejected"] @router.patch("/{topup_id}/status", dependencies=[Depends(requires_role("manager", "admin"))]) def patch_topup_status( topup_id: int, payload: TopupStatusIn, db: Session = Depends(get_db), admin: User = Depends(get_current_user), ): t = db.query(Topup).filter(Topup.id == topup_id).first() if not t: raise HTTPException(status_code=404, detail="Topup not found") # robustes Enum-Handling old_enum = t.status if isinstance(t.status, DBTopupStatus) else DBTopupStatus(str(t.status)) try: new_enum = DBTopupStatus(payload.status) except ValueError: raise HTTPException(status_code=400, detail="invalid status") if old_enum == new_enum: return {"status": "ok", "topup_id": t.id, "new_status": old_enum.value} # Status setzen t.status = new_enum # Balance nur bei Übergang zu 'confirmed' buchen (idempotent) if new_enum == DBTopupStatus.confirmed and old_enum != DBTopupStatus.confirmed: u = db.query(User).filter(User.id == t.user_id).first() if not u: raise HTTPException(status_code=404, detail="User for topup not found") before = int(u.balance_cents or 0) u.balance_cents = before + int(t.amount_cents or 0) if HAS_AUDIT: db.add( AuditLog( user_id=u.id, action=getattr(AuditAction, "topup_confirmed", "topup_confirmed"), amount_cents=int(t.amount_cents or 0), old_balance_cents=before, new_balance_cents=u.balance_cents, info=f"topup:{t.id} confirmed by {admin.id}", ) ) # (Kein automatisches Reversal bei Wechsel zu rejected) db.commit() db.refresh(t) return {"status": "ok", "topup_id": t.id, "new_status": t.status.value} # --------------------------- # DELETE (Admin/Manager) # --------------------------- @router.delete("/{topup_id}", status_code=status.HTTP_204_NO_CONTENT, dependencies=[Depends(requires_role("manager", "admin"))]) def delete_topup( topup_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_user), ): t = db.query(Topup).filter(Topup.id == topup_id).first() if not t: raise HTTPException(status_code=404, detail="Topup not found") db.delete(t) db.commit() if HAS_AUDIT: db.add( AuditLog( user_id=t.user_id, action=getattr(AuditAction, "topup_delete", "topup_delete"), amount_cents=int(t.amount_cents or 0), info=f"topup:{t.id} deleted by {admin.id}", ) ) db.commit() return None