Files
bacchus/apps/backend/app/api/admin_transactions.py
2025-09-28 19:13:01 +02:00

109 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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