Files
trade-assistant/backend/app/api/v1/admin.py
T

277 lines
8.3 KiB
Python

import uuid
from typing import Optional
from datetime import date, datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.services.admin import AdminService
from app.services.translation_quota import TranslationQuotaService
from app.services.certification import CertificationService
from app.services.invoice import InvoiceService
from app.api.v1.deps import get_current_user
router = APIRouter()
async def require_admin(current_user: dict = Depends(get_current_user)) -> dict:
if current_user.get("role") != "admin":
raise HTTPException(status_code=403, detail="Admin access required")
return current_user
@router.get("/dashboard")
async def get_dashboard(
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_dashboard()
@router.get("/users")
async def list_users(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
role: Optional[str] = Query(None),
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.list_users(page, size, role)
def _validate_uuid(user_id: str):
try:
uuid.UUID(user_id)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid user ID format")
@router.patch("/users/{target_user_id}/tier")
async def update_user_tier(
target_user_id: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(target_user_id)
service = AdminService(db)
tier = data.get("tier")
if tier not in ("free", "pro", "enterprise"):
raise HTTPException(status_code=400, detail="Invalid tier")
success = await service.update_user_tier(target_user_id, tier)
if not success:
raise HTTPException(status_code=404, detail="User not found")
return {"message": f"User tier updated to {tier}"}
@router.post("/users/{target_user_id}/toggle-active")
async def toggle_user_active(
target_user_id: str,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(target_user_id)
service = AdminService(db)
success = await service.toggle_user_active(target_user_id)
if not success:
raise HTTPException(status_code=404, detail="User not found")
return {"message": "User active status toggled"}
@router.patch("/users/{target_user_id}/role")
async def update_user_role(
target_user_id: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(target_user_id)
service = AdminService(db)
role = data.get("role")
if role not in ("user", "admin"):
raise HTTPException(status_code=400, detail="Invalid role. Must be 'user' or 'admin'")
result = await service.update_user_role(target_user_id, role)
if not result:
raise HTTPException(status_code=404, detail="User not found")
return result
@router.get("/users/search")
async def search_users(
q: str = Query(..., min_length=1),
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.search_users(q)
@router.get("/users/{target_user_id}")
async def get_user_detail(
target_user_id: str,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(target_user_id)
service = AdminService(db)
result = await service.get_user_detail(target_user_id)
if not result:
raise HTTPException(status_code=404, detail="User not found")
return result
@router.get("/usage-stats")
async def get_usage_stats(
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_usage_stats()
@router.get("/logs")
async def get_logs(
page: int = Query(1, ge=1),
size: int = Query(50, ge=1, le=200),
action: Optional[str] = Query(None),
user_id: Optional[str] = Query(None),
date_from: Optional[date] = Query(None),
date_to: Optional[date] = Query(None),
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
dt_from = datetime.combine(date_from, datetime.min.time()) if date_from else None
dt_to = datetime.combine(date_to, datetime.max.time()) if date_to else None
return await service.get_logs(page, size, action, user_id, dt_from, dt_to)
@router.get("/config")
async def list_config(
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.list_config()
@router.put("/config/{key}")
async def update_config(
key: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
item = await service.update_config(key, data.get("value"))
if not item:
raise HTTPException(status_code=404, detail="Config not found")
return item
@router.get("/health")
async def system_health(
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_system_health()
@router.get("/translation-quotas")
async def list_translation_quotas(
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = TranslationQuotaService(db)
return await service.get_all_quotas()
@router.put("/translation-quotas/{version}")
async def update_translation_quota(
version: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
allowed = {"monthly_limit", "enabled", "description"}
filtered = {k: v for k, v in data.items() if k in allowed}
service = TranslationQuotaService(db)
result = await service.update_quota(version, filtered)
if not result:
raise HTTPException(status_code=404, detail="Quota not found")
return result
@router.post("/translation-quotas/{version}/reset")
async def reset_translation_quota(
version: str,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = TranslationQuotaService(db)
result = await service.reset_usage(version)
if not result:
raise HTTPException(status_code=404, detail="Quota not found")
return result
@router.get("/certifications")
async def admin_list_certifications(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
status: Optional[str] = Query(None),
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = CertificationService(db)
return await service.list_all(page, size, status)
@router.post("/certifications/{cert_id}/review")
async def admin_review_certification(
cert_id: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(cert_id)
service = CertificationService(db)
action = data.get("action")
if action not in ("approve", "reject"):
raise HTTPException(status_code=400, detail="Action must be 'approve' or 'reject'")
result = await service.review(cert_id, action, data.get("reason"))
if not result:
raise HTTPException(status_code=404, detail="Certification not found")
return result
@router.get("/invoices")
async def admin_list_invoices(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
status: Optional[str] = Query(None),
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
service = InvoiceService(db)
return await service.list_all(page, size, status)
@router.post("/invoices/{invoice_id}/process")
async def admin_process_invoice(
invoice_id: str,
data: dict,
_: dict = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
_validate_uuid(invoice_id)
service = InvoiceService(db)
action = data.get("action")
if action not in ("issue", "reject"):
raise HTTPException(status_code=400, detail="Action must be 'issue' or 'reject'")
result = await service.process(invoice_id, action, data.get("reason"))
if not result:
raise HTTPException(status_code=404, detail="Invoice not found")
return result