from datetime import datetime, timezone from enum import Enum from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import select, func from sqlalchemy.orm import Session from sqlalchemy.exc import NoResultFound from app.core.database import get_db from app.core.auth import get_current_user from app.models.user import User from app.models.transaction import Transaction # siehe Modell unten falls noch nicht vorhanden from app.schemas.transaction import TransactionOut, TransactionCreate # siehe Schemas unten router = APIRouter(prefix="/admin/transactions", tags=["admin-transactions"]) def require_admin(current=Depends(get_current_user)): if getattr(current, "role", None) != "admin": raise HTTPException(status_code=403, detail="Admin only") return current @router.get("", response_model=List[TransactionOut]) def list_transactions( db: Session = Depends(get_db), _admin=Depends(require_admin), limit: int = Query(200, ge=1, le=500), offset: int = Query(0, ge=0), order: str = Query("desc", pattern="^(asc|desc)$"), status: Optional[str] = Query(None, pattern="^(waiting|approved|rejected)$"), ): q = select(Transaction).offset(offset).limit(limit) if status: q = q.where(Transaction.status == status) if order == "desc": q = q.order_by(Transaction.created_at.desc()) else: q = q.order_by(Transaction.created_at.asc()) return db.execute(q).scalars().all() @router.post("", response_model=TransactionOut, status_code=201) def create_transaction( body: TransactionCreate, db: Session = Depends(get_db), admin=Depends(require_admin), ): tx = Transaction( user_id=body.user_id, amount_cents=body.amount_cents, note=body.note or "", status="waiting", created_at=datetime.now(timezone.utc), created_by_id=admin.id, kind="manual", ) db.add(tx) db.commit() db.refresh(tx) return tx @router.post("/{tx_id}/approve", response_model=TransactionOut) def approve_transaction( tx_id: int, db: Session = Depends(get_db), admin=Depends(require_admin), ): tx = db.get(Transaction, tx_id) if not tx: raise HTTPException(404, "Transaktion nicht gefunden") if tx.status != "waiting": raise HTTPException(status_code=409, detail=f"Transaktion bereits {tx.status}") # Nutzer sperren, um Doppelbuchungen zu vermeiden user = db.execute( select(User).where(User.id == tx.user_id).with_for_update() ).scalar_one() new_balance = (user.balance_cents or 0) + tx.amount_cents if new_balance < 0: raise HTTPException(400, "Saldo würde negativ – Buchung abgelehnt") user.balance_cents = new_balance tx.status = "approved" tx.processed_at = datetime.now(timezone.utc) tx.processed_by_id = admin.id db.commit() db.refresh(tx) return tx @router.post("/{tx_id}/reject", response_model=TransactionOut) def reject_transaction( tx_id: int, db: Session = Depends(get_db), admin=Depends(require_admin), ): tx = db.get(Transaction, tx_id) if not tx: raise HTTPException(404, "Transaktion nicht gefunden") if tx.status != "waiting": raise HTTPException(status_code=409, detail=f"Transaktion bereits {tx.status}") tx.status = "rejected" tx.processed_at = datetime.now(timezone.utc) tx.processed_by_id = admin.id db.commit() db.refresh(tx) return tx