This commit is contained in:
2026-06-15 18:40:54 +08:00
parent 216726337b
commit 1ca02a119f
9 changed files with 193 additions and 119 deletions
+56 -80
View File
@@ -77,7 +77,7 @@ router = APIRouter(prefix="/admin", tags=["admin"])
# ── User Management ──────────────────────────────────────
@router.get("/users", response_model=list[AdminUserOut])
@router.get("/users")
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
@@ -86,13 +86,15 @@ async def list_users(
db: AsyncSession = Depends(get_db),
):
query = select(User).order_by(User.created_at.desc())
count_query = select(func.count(User.id))
if search:
query = query.where(
(User.username.ilike(f"%{search}%"))
| (User.email.ilike(f"%{search}%"))
)
search_filter = (User.username.ilike(f"%{search}%")) | (User.email.ilike(f"%{search}%"))
query = query.where(search_filter)
count_query = count_query.where(search_filter)
total = (await db.execute(count_query)).scalar() or 0
result = await db.execute(query.offset((page - 1) * page_size).limit(page_size))
return result.scalars().all()
items = result.scalars().all()
return {"items": [AdminUserOut.model_validate(u) for u in items], "total": total}
@router.post("/users", response_model=AdminUserOut)
@@ -555,24 +557,6 @@ async def get_payment_stats(
)
month_row = month_result.one()
# Recent orders with filters
recent_filters = []
if payment_method:
recent_filters.append(PaymentOrder.payment_method == payment_method)
if status:
recent_filters.append(PaymentOrder.status == status)
recent_filters.append(PaymentOrder.created_at >= query_start)
recent_filters.append(PaymentOrder.created_at < query_end)
recent_result = await db.execute(
select(PaymentOrder, User)
.join(User, PaymentOrder.user_id == User.id)
.where(*recent_filters)
.order_by(PaymentOrder.created_at.desc())
.limit(50)
)
recent_data = recent_result.all()
return {
"by_status": by_status,
"today": {
@@ -583,76 +567,68 @@ async def get_payment_stats(
"paid_count": month_row.paid_count,
"paid_amount": round(float(month_row.paid_amount), 2),
},
"recent": [
{
"id": o.id,
"order_no": o.order_no,
"user_id": o.user_id,
"username": u.username,
"amount": round(o.amount, 2),
"credits": round(o.credits, 2),
"payment_method": o.payment_method,
"status": o.status if o.status in ("pending", "paid", "cancelled", "refunded") else "cancelled",
"trade_no": o.trade_no,
"paid_at": _iso(o.paid_at),
"created_at": _iso(o.created_at),
}
for o, u in recent_data
],
}
@router.get("/payment-orders")
async def get_admin_payment_orders(
method: str | None = None,
status: str | None = None,
page: int = 1,
page_size: int = 20,
async def list_payment_orders(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
payment_method: str | None = Query(None),
status: str | None = Query(None),
start_date: str | None = Query(None),
end_date: str | None = Query(None),
admin: User = Depends(get_admin_user),
db: AsyncSession = Depends(get_db),
):
"""Return paginated payment orders for admin."""
query = select(PaymentOrder)
if method:
query = query.where(PaymentOrder.payment_method == method)
"""Return paginated payment orders for admin dashboard."""
query = select(PaymentOrder, User.username).join(User, PaymentOrder.user_id == User.id)
count_query = select(func.count(PaymentOrder.id))
filters = []
if payment_method:
filters.append(PaymentOrder.payment_method == payment_method)
if status:
query = query.where(PaymentOrder.status == status)
filters.append(PaymentOrder.status == status)
if start_date:
filters.append(PaymentOrder.created_at >= datetime.fromisoformat(start_date).replace(tzinfo=CST))
if end_date:
filters.append(PaymentOrder.created_at < (datetime.fromisoformat(end_date) + timedelta(days=1)).replace(tzinfo=CST))
# Count total
count_result = await db.execute(
select(func.count()).select_from(query.subquery())
)
total = count_result.scalar() or 0
for f in filters:
query = query.where(f)
count_query = count_query.where(f)
# Paginated results
total = (await db.execute(count_query)).scalar() or 0
result = await db.execute(
query.order_by(PaymentOrder.created_at.desc())
.offset((page - 1) * page_size)
.limit(page_size)
query.order_by(PaymentOrder.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
)
orders = result.scalars().all()
rows = result.all()
return {
"total": total,
"page": page,
"page_size": page_size,
"items": [
{
"id": o.id,
"order_no": o.order_no,
"user_id": o.user_id,
"amount": round(o.amount, 2),
"credits": round(o.credits, 2),
"payment_method": o.payment_method,
"status": o.status if o.status in ("pending", "paid", "cancelled", "refunded") else "cancelled",
"trade_no": o.trade_no,
"paid_at": _iso(o.paid_at),
"created_at": _iso(o.created_at),
}
for o in orders
],
}
items = [
{
"id": o.id,
"orderNo": o.order_no,
"order_no": o.order_no,
"userId": o.user_id,
"user_id": o.user_id,
"username": username,
"amount": round(float(o.amount), 2),
"credits": round(float(o.credits), 2),
"paymentMethod": o.payment_method,
"payment_method": o.payment_method,
"status": o.status,
"tradeNo": o.trade_no,
"trade_no": o.trade_no,
"paidAt": o.paid_at.isoformat() if o.paid_at else None,
"paid_at": o.paid_at.isoformat() if o.paid_at else None,
"createdAt": o.created_at.isoformat() if o.created_at else None,
"created_at": o.created_at.isoformat() if o.created_at else None,
}
for o, username in rows
]
return {"items": items, "total": total, "page": page, "page_size": page_size}
@router.put("/payment-configs/{config_id}")
async def update_payment_config(
+3 -4
View File
@@ -15,7 +15,7 @@ from app.services.credits import get_records
router = APIRouter(prefix="/credits", tags=["credits"])
@router.get("", response_model=CreditBalanceOut)
@router.get("")
async def get_credits(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
@@ -23,12 +23,11 @@ async def get_credits(
db: AsyncSession = Depends(get_db),
):
records, total = await get_records(db, current_user.id, page, page_size)
from fastapi.responses import JSONResponse
return JSONResponse(content={
return {
"credits": round(current_user.credits, 2),
"records": [CreditRecordOut.model_validate(r) for r in records],
"total": total,
})
}
@router.get(
+5 -6
View File
@@ -288,7 +288,7 @@ async def alipay_callback(request: Request, db: AsyncSession = Depends(get_db)):
return "success"
@router.get("/orders", response_model=list[PaymentOrderOut])
@router.get("/orders")
async def list_orders(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
@@ -296,10 +296,10 @@ async def list_orders(
db: AsyncSession = Depends(get_db),
):
from app.services.payment import _check_and_expire_order
count_query = select(func.count(PaymentOrder.id)).where(PaymentOrder.user_id == current_user.id)
total = (await db.execute(count_query)).scalar() or 0
result = await db.execute(
select(PaymentOrder)
.where(PaymentOrder.user_id == current_user.id)
@@ -310,9 +310,8 @@ async def list_orders(
orders = result.scalars().all()
for o in orders:
await _check_and_expire_order(db, o)
from fastapi.responses import JSONResponse
return JSONResponse(content={"items": orders, "total": total})
return {"items": [PaymentOrderOut.model_validate(o) for o in orders], "total": total}
@router.get("/orders/{order_no}", response_model=PaymentOrderOut)