204 lines
6.4 KiB
Python
204 lines
6.4 KiB
Python
# 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
|