Added admin panel.
This commit is contained in:
@ -1,19 +1,54 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update
|
||||
from typing import List
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import User, SportEvent, SpreadBet, AdminSettings, EventStatus
|
||||
from app.models import (
|
||||
User, UserStatus, SportEvent, SpreadBet, AdminSettings, EventStatus,
|
||||
Wallet, Transaction, TransactionType, TransactionStatus, AdminAuditLog, Bet
|
||||
)
|
||||
from app.schemas.sport_event import SportEventCreate, SportEventUpdate, SportEvent as SportEventSchema
|
||||
from app.schemas.admin import (
|
||||
AuditLogResponse, AuditLogListResponse,
|
||||
WipePreviewResponse, WipeRequest, WipeResponse,
|
||||
SeedRequest, SeedResponse,
|
||||
SimulationConfig, SimulationStatusResponse, SimulationStartRequest, SimulationStartResponse, SimulationStopResponse,
|
||||
AdminUserListItem, AdminUserListResponse, AdminUserDetailResponse,
|
||||
AdminUserUpdateRequest, AdminUserStatusRequest,
|
||||
AdminBalanceAdjustRequest, AdminBalanceAdjustResponse,
|
||||
AdminDashboardStats,
|
||||
)
|
||||
from app.routers.auth import get_current_user
|
||||
from app.services.audit_service import (
|
||||
AuditService, log_event_create, log_event_update, log_event_delete,
|
||||
log_user_status_change, log_user_balance_adjust, log_user_admin_change,
|
||||
log_user_update, log_settings_update, log_simulation_start, log_simulation_stop
|
||||
)
|
||||
from app.services.wiper_service import WiperService
|
||||
from app.services.seeder_service import SeederService
|
||||
from app.services.simulation_service import simulation_manager
|
||||
|
||||
router = APIRouter(prefix="/api/v1/admin", tags=["admin"])
|
||||
|
||||
|
||||
# Dependency to check if user is admin
|
||||
# ============================================================
|
||||
# Helper to get client IP
|
||||
# ============================================================
|
||||
def get_client_ip(request: Request) -> Optional[str]:
|
||||
"""Extract client IP from request."""
|
||||
forwarded = request.headers.get("X-Forwarded-For")
|
||||
if forwarded:
|
||||
return forwarded.split(",")[0].strip()
|
||||
return request.client.host if request.client else None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Admin Dependency
|
||||
# ============================================================
|
||||
async def get_admin_user(current_user: User = Depends(get_current_user)) -> User:
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(
|
||||
@ -23,6 +58,506 @@ async def get_admin_user(current_user: User = Depends(get_current_user)) -> User
|
||||
return current_user
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Dashboard Stats
|
||||
# ============================================================
|
||||
@router.get("/dashboard", response_model=AdminDashboardStats)
|
||||
async def get_dashboard_stats(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Get dashboard statistics for admin panel."""
|
||||
# User counts
|
||||
total_users = (await db.execute(select(func.count(User.id)))).scalar() or 0
|
||||
active_users = (await db.execute(
|
||||
select(func.count(User.id)).where(User.status == UserStatus.ACTIVE)
|
||||
)).scalar() or 0
|
||||
suspended_users = (await db.execute(
|
||||
select(func.count(User.id)).where(User.status == UserStatus.SUSPENDED)
|
||||
)).scalar() or 0
|
||||
admin_users = (await db.execute(
|
||||
select(func.count(User.id)).where(User.is_admin == True)
|
||||
)).scalar() or 0
|
||||
|
||||
# Event counts
|
||||
total_events = (await db.execute(select(func.count(SportEvent.id)))).scalar() or 0
|
||||
upcoming_events = (await db.execute(
|
||||
select(func.count(SportEvent.id)).where(SportEvent.status == EventStatus.UPCOMING)
|
||||
)).scalar() or 0
|
||||
live_events = (await db.execute(
|
||||
select(func.count(SportEvent.id)).where(SportEvent.status == EventStatus.LIVE)
|
||||
)).scalar() or 0
|
||||
|
||||
# Bet counts
|
||||
total_bets = (await db.execute(select(func.count(SpreadBet.id)))).scalar() or 0
|
||||
open_bets = (await db.execute(
|
||||
select(func.count(SpreadBet.id)).where(SpreadBet.status == "open")
|
||||
)).scalar() or 0
|
||||
matched_bets = (await db.execute(
|
||||
select(func.count(SpreadBet.id)).where(SpreadBet.status == "matched")
|
||||
)).scalar() or 0
|
||||
|
||||
# Volume calculations
|
||||
total_volume_result = await db.execute(
|
||||
select(func.sum(SpreadBet.stake_amount))
|
||||
)
|
||||
total_volume = total_volume_result.scalar() or Decimal("0.00")
|
||||
|
||||
escrow_result = await db.execute(select(func.sum(Wallet.escrow)))
|
||||
escrow_locked = escrow_result.scalar() or Decimal("0.00")
|
||||
|
||||
return AdminDashboardStats(
|
||||
total_users=total_users,
|
||||
active_users=active_users,
|
||||
suspended_users=suspended_users,
|
||||
admin_users=admin_users,
|
||||
total_events=total_events,
|
||||
upcoming_events=upcoming_events,
|
||||
live_events=live_events,
|
||||
total_bets=total_bets,
|
||||
open_bets=open_bets,
|
||||
matched_bets=matched_bets,
|
||||
total_volume=total_volume,
|
||||
escrow_locked=escrow_locked,
|
||||
simulation_running=simulation_manager.is_running,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Audit Logs
|
||||
# ============================================================
|
||||
@router.get("/audit-logs", response_model=AuditLogListResponse)
|
||||
async def get_audit_logs(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
action: Optional[str] = None,
|
||||
admin_id: Optional[int] = None,
|
||||
target_type: Optional[str] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Get paginated audit logs with optional filters."""
|
||||
logs, total = await AuditService.get_logs(
|
||||
db=db,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
action_filter=action,
|
||||
admin_id_filter=admin_id,
|
||||
target_type_filter=target_type,
|
||||
)
|
||||
return AuditLogListResponse(
|
||||
logs=[AuditLogResponse.model_validate(log) for log in logs],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Data Wiper
|
||||
# ============================================================
|
||||
@router.get("/data/wipe/preview", response_model=WipePreviewResponse)
|
||||
async def preview_wipe(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Preview what will be deleted in a wipe operation."""
|
||||
return await WiperService.get_preview(db)
|
||||
|
||||
|
||||
@router.post("/data/wipe", response_model=WipeResponse)
|
||||
async def execute_wipe(
|
||||
request: WipeRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Execute a database wipe. Requires confirmation phrase."""
|
||||
try:
|
||||
ip = get_client_ip(req) if req else None
|
||||
return await WiperService.execute_wipe(db, admin, request, ip)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Data Seeder
|
||||
# ============================================================
|
||||
@router.post("/data/seed", response_model=SeedResponse)
|
||||
async def seed_database(
|
||||
request: SeedRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Seed the database with test data."""
|
||||
ip = get_client_ip(req) if req else None
|
||||
return await SeederService.seed(db, admin, request, ip)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Simulation Control
|
||||
# ============================================================
|
||||
@router.get("/simulation/status", response_model=SimulationStatusResponse)
|
||||
async def get_simulation_status(
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Get current simulation status."""
|
||||
return simulation_manager.get_status()
|
||||
|
||||
|
||||
@router.post("/simulation/start", response_model=SimulationStartResponse)
|
||||
async def start_simulation(
|
||||
request: SimulationStartRequest = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Start the activity simulation."""
|
||||
config = request.config if request else None
|
||||
|
||||
if simulation_manager.is_running:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Simulation is already running"
|
||||
)
|
||||
|
||||
success = await simulation_manager.start(admin.username, config)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to start simulation"
|
||||
)
|
||||
|
||||
# Log the action
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_simulation_start(
|
||||
db=db,
|
||||
admin=admin,
|
||||
config=config.model_dump() if config else {},
|
||||
ip_address=ip,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return SimulationStartResponse(
|
||||
success=True,
|
||||
message="Simulation started successfully",
|
||||
status=simulation_manager.get_status(),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/simulation/stop", response_model=SimulationStopResponse)
|
||||
async def stop_simulation(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Stop the activity simulation."""
|
||||
if not simulation_manager.is_running:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Simulation is not running"
|
||||
)
|
||||
|
||||
iterations, duration = await simulation_manager.stop()
|
||||
|
||||
# Log the action
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_simulation_stop(
|
||||
db=db,
|
||||
admin=admin,
|
||||
iterations=iterations,
|
||||
duration_seconds=duration,
|
||||
ip_address=ip,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return SimulationStopResponse(
|
||||
success=True,
|
||||
message="Simulation stopped successfully",
|
||||
total_iterations=iterations,
|
||||
ran_for_seconds=duration,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# User Management
|
||||
# ============================================================
|
||||
@router.get("/users", response_model=AdminUserListResponse)
|
||||
async def list_users(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
search: Optional[str] = None,
|
||||
status_filter: Optional[str] = None,
|
||||
is_admin: Optional[bool] = None,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Get paginated list of users."""
|
||||
query = select(User).options(selectinload(User.wallet))
|
||||
|
||||
# Apply filters
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
query = query.where(
|
||||
(User.username.ilike(search_term)) |
|
||||
(User.email.ilike(search_term)) |
|
||||
(User.display_name.ilike(search_term))
|
||||
)
|
||||
if status_filter:
|
||||
query = query.where(User.status == UserStatus(status_filter))
|
||||
if is_admin is not None:
|
||||
query = query.where(User.is_admin == is_admin)
|
||||
|
||||
# Get total count
|
||||
count_query = select(func.count()).select_from(query.subquery())
|
||||
total = (await db.execute(count_query)).scalar() or 0
|
||||
|
||||
# Apply pagination
|
||||
query = query.order_by(User.created_at.desc())
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
result = await db.execute(query)
|
||||
users = result.scalars().all()
|
||||
|
||||
user_items = []
|
||||
for user in users:
|
||||
wallet = user.wallet
|
||||
user_items.append(AdminUserListItem(
|
||||
id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
is_admin=user.is_admin,
|
||||
status=user.status.value,
|
||||
balance=wallet.balance if wallet else Decimal("0.00"),
|
||||
escrow=wallet.escrow if wallet else Decimal("0.00"),
|
||||
total_bets=user.total_bets,
|
||||
wins=user.wins,
|
||||
losses=user.losses,
|
||||
win_rate=user.win_rate,
|
||||
created_at=user.created_at,
|
||||
))
|
||||
|
||||
return AdminUserListResponse(
|
||||
users=user_items,
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/users/{user_id}", response_model=AdminUserDetailResponse)
|
||||
async def get_user_detail(
|
||||
user_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
):
|
||||
"""Get detailed user information."""
|
||||
result = await db.execute(
|
||||
select(User).options(selectinload(User.wallet)).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
wallet = user.wallet
|
||||
|
||||
# Get additional counts
|
||||
open_bets = (await db.execute(
|
||||
select(func.count(SpreadBet.id)).where(
|
||||
(SpreadBet.creator_id == user_id) & (SpreadBet.status == "open")
|
||||
)
|
||||
)).scalar() or 0
|
||||
|
||||
matched_bets = (await db.execute(
|
||||
select(func.count(SpreadBet.id)).where(
|
||||
((SpreadBet.creator_id == user_id) | (SpreadBet.taker_id == user_id)) &
|
||||
(SpreadBet.status == "matched")
|
||||
)
|
||||
)).scalar() or 0
|
||||
|
||||
transaction_count = (await db.execute(
|
||||
select(func.count(Transaction.id)).where(Transaction.user_id == user_id)
|
||||
)).scalar() or 0
|
||||
|
||||
return AdminUserDetailResponse(
|
||||
id=user.id,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
display_name=user.display_name,
|
||||
avatar_url=user.avatar_url,
|
||||
bio=user.bio,
|
||||
is_admin=user.is_admin,
|
||||
status=user.status.value,
|
||||
created_at=user.created_at,
|
||||
updated_at=user.updated_at,
|
||||
balance=wallet.balance if wallet else Decimal("0.00"),
|
||||
escrow=wallet.escrow if wallet else Decimal("0.00"),
|
||||
total_bets=user.total_bets,
|
||||
wins=user.wins,
|
||||
losses=user.losses,
|
||||
win_rate=user.win_rate,
|
||||
open_bets_count=open_bets,
|
||||
matched_bets_count=matched_bets,
|
||||
transaction_count=transaction_count,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/users/{user_id}")
|
||||
async def update_user(
|
||||
user_id: int,
|
||||
request: AdminUserUpdateRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Update user details."""
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
changes = {}
|
||||
ip = get_client_ip(req) if req else None
|
||||
|
||||
if request.display_name is not None and request.display_name != user.display_name:
|
||||
changes["display_name"] = {"old": user.display_name, "new": request.display_name}
|
||||
user.display_name = request.display_name
|
||||
|
||||
if request.email is not None and request.email != user.email:
|
||||
# Check if email already exists
|
||||
existing = await db.execute(select(User).where(User.email == request.email))
|
||||
if existing.scalar_one_or_none():
|
||||
raise HTTPException(status_code=400, detail="Email already in use")
|
||||
changes["email"] = {"old": user.email, "new": request.email}
|
||||
user.email = request.email
|
||||
|
||||
if request.is_admin is not None and request.is_admin != user.is_admin:
|
||||
# Cannot remove own admin status
|
||||
if user.id == admin.id and not request.is_admin:
|
||||
raise HTTPException(status_code=400, detail="Cannot remove your own admin privileges")
|
||||
|
||||
await log_user_admin_change(db, admin, user, request.is_admin, ip)
|
||||
changes["is_admin"] = {"old": user.is_admin, "new": request.is_admin}
|
||||
user.is_admin = request.is_admin
|
||||
|
||||
if changes:
|
||||
await log_user_update(db, admin, user, changes, ip)
|
||||
|
||||
await db.commit()
|
||||
return {"message": "User updated successfully", "changes": changes}
|
||||
|
||||
|
||||
@router.patch("/users/{user_id}/status")
|
||||
async def change_user_status(
|
||||
user_id: int,
|
||||
request: AdminUserStatusRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Enable or disable a user."""
|
||||
result = await db.execute(select(User).where(User.id == user_id))
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Cannot suspend yourself
|
||||
if user.id == admin.id:
|
||||
raise HTTPException(status_code=400, detail="Cannot change your own status")
|
||||
|
||||
old_status = user.status.value
|
||||
new_status = UserStatus(request.status)
|
||||
|
||||
if user.status == new_status:
|
||||
return {"message": "Status unchanged"}
|
||||
|
||||
user.status = new_status
|
||||
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_user_status_change(db, admin, user, old_status, request.status, request.reason, ip)
|
||||
|
||||
await db.commit()
|
||||
return {"message": f"User status changed to {request.status}"}
|
||||
|
||||
|
||||
@router.post("/users/{user_id}/balance", response_model=AdminBalanceAdjustResponse)
|
||||
async def adjust_user_balance(
|
||||
user_id: int,
|
||||
request: AdminBalanceAdjustRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
"""Adjust user balance (add or subtract funds)."""
|
||||
result = await db.execute(
|
||||
select(User).options(selectinload(User.wallet)).where(User.id == user_id)
|
||||
)
|
||||
user = result.scalar_one_or_none()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
wallet = user.wallet
|
||||
if not wallet:
|
||||
raise HTTPException(status_code=400, detail="User has no wallet")
|
||||
|
||||
previous_balance = wallet.balance
|
||||
new_balance = previous_balance + request.amount
|
||||
|
||||
# Validate new balance
|
||||
if new_balance < Decimal("0.00"):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot reduce balance below $0. Current: ${previous_balance}, Adjustment: ${request.amount}"
|
||||
)
|
||||
|
||||
if new_balance < wallet.escrow:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot reduce balance below escrow amount (${wallet.escrow})"
|
||||
)
|
||||
|
||||
wallet.balance = new_balance
|
||||
|
||||
# Create transaction record
|
||||
tx_type = TransactionType.ADMIN_CREDIT if request.amount > 0 else TransactionType.ADMIN_DEBIT
|
||||
transaction = Transaction(
|
||||
user_id=user.id,
|
||||
wallet_id=wallet.id,
|
||||
type=tx_type,
|
||||
amount=request.amount,
|
||||
balance_after=new_balance,
|
||||
description=f"Admin adjustment: {request.reason}",
|
||||
status=TransactionStatus.COMPLETED,
|
||||
)
|
||||
db.add(transaction)
|
||||
await db.flush()
|
||||
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_user_balance_adjust(
|
||||
db, admin, user,
|
||||
float(previous_balance), float(new_balance), float(request.amount),
|
||||
request.reason, transaction.id, ip
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
return AdminBalanceAdjustResponse(
|
||||
success=True,
|
||||
user_id=user.id,
|
||||
username=user.username,
|
||||
previous_balance=previous_balance,
|
||||
adjustment=request.amount,
|
||||
new_balance=new_balance,
|
||||
reason=request.reason,
|
||||
transaction_id=transaction.id,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Settings Management (existing endpoints, enhanced with audit)
|
||||
# ============================================================
|
||||
@router.get("/settings")
|
||||
async def get_admin_settings(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
@ -42,7 +577,8 @@ async def get_admin_settings(
|
||||
async def update_admin_settings(
|
||||
updates: dict,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
result = await db.execute(select(AdminSettings).limit(1))
|
||||
settings = result.scalar_one_or_none()
|
||||
@ -50,32 +586,49 @@ async def update_admin_settings(
|
||||
settings = AdminSettings()
|
||||
db.add(settings)
|
||||
|
||||
changes = {}
|
||||
for key, value in updates.items():
|
||||
if hasattr(settings, key):
|
||||
setattr(settings, key, value)
|
||||
old_value = getattr(settings, key)
|
||||
if old_value != value:
|
||||
changes[key] = {"old": str(old_value), "new": str(value)}
|
||||
setattr(settings, key, value)
|
||||
|
||||
if changes:
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_settings_update(db, admin, changes, ip)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(settings)
|
||||
return settings
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Event Management (existing endpoints, enhanced with audit)
|
||||
# ============================================================
|
||||
@router.post("/events", response_model=SportEventSchema)
|
||||
async def create_event(
|
||||
event_data: SportEventCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
event = SportEvent(
|
||||
**event_data.model_dump(),
|
||||
created_by=admin.id
|
||||
)
|
||||
db.add(event)
|
||||
await db.flush()
|
||||
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_event_create(db, admin, event.id, f"{event.home_team} vs {event.away_team}", ip)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(event)
|
||||
return event
|
||||
|
||||
|
||||
@router.get("/events", response_model=List[SportEventSchema])
|
||||
@router.get("/events", response_model=list[SportEventSchema])
|
||||
async def list_events(
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
@ -93,7 +646,8 @@ async def update_event(
|
||||
event_id: int,
|
||||
updates: SportEventUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
result = await db.execute(select(SportEvent).where(SportEvent.id == event_id))
|
||||
event = result.scalar_one_or_none()
|
||||
@ -101,8 +655,16 @@ async def update_event(
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
|
||||
update_data = updates.model_dump(exclude_unset=True)
|
||||
changes = {}
|
||||
for key, value in update_data.items():
|
||||
setattr(event, key, value)
|
||||
old_value = getattr(event, key)
|
||||
if old_value != value:
|
||||
changes[key] = {"old": str(old_value), "new": str(value)}
|
||||
setattr(event, key, value)
|
||||
|
||||
if changes:
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_event_update(db, admin, event.id, f"{event.home_team} vs {event.away_team}", changes, ip)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(event)
|
||||
@ -113,7 +675,8 @@ async def update_event(
|
||||
async def delete_event(
|
||||
event_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin: User = Depends(get_admin_user)
|
||||
admin: User = Depends(get_admin_user),
|
||||
req: Request = None,
|
||||
):
|
||||
result = await db.execute(select(SportEvent).where(SportEvent.id == event_id))
|
||||
event = result.scalar_one_or_none()
|
||||
@ -133,6 +696,11 @@ async def delete_event(
|
||||
detail="Cannot delete event with matched bets"
|
||||
)
|
||||
|
||||
event_title = f"{event.home_team} vs {event.away_team}"
|
||||
|
||||
ip = get_client_ip(req) if req else None
|
||||
await log_event_delete(db, admin, event_id, event_title, ip)
|
||||
|
||||
await db.delete(event)
|
||||
await db.commit()
|
||||
return {"message": "Event deleted"}
|
||||
|
||||
Reference in New Issue
Block a user