143 lines
5.7 KiB
Python
143 lines
5.7 KiB
Python
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import case, func, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.dependencies import get_current_user, get_db
|
|
from app.models.credit.balance import UserCreditBalance
|
|
from app.models.credit_record import CreditRecord
|
|
from app.models.credit_ratio import CreditRatio
|
|
from app.models.image_engine import ImageEngine
|
|
from app.models.user import User
|
|
from app.models.video_engine import VideoEngine
|
|
from app.schemas.credit import CreditRecordOut
|
|
from app.schemas.credit_balance import CreditBalanceItemOut
|
|
from app.schemas.credit_ratio import CreditRatioOut
|
|
from app.services.credit.query_service import (
|
|
apply_balance_status_filter,
|
|
effective_balance_status,
|
|
get_balance_summary,
|
|
)
|
|
from app.services.credit.utils import utc_now
|
|
from app.services.credit.time_policy import last_usable_at
|
|
from app.services.credit_ratio_service import list_all_credit_ratios
|
|
from app.services.credits import get_records
|
|
|
|
router = APIRouter(prefix="/credits", tags=["credits"])
|
|
|
|
|
|
def _balance_to_out(item: UserCreditBalance, *, checked_at) -> CreditBalanceItemOut:
|
|
return CreditBalanceItemOut(
|
|
id=item.id,
|
|
credit_level=item.credit_level,
|
|
source_type=item.source_type,
|
|
source_id=item.source_id,
|
|
product_id=item.product_id,
|
|
payment_order_id=item.payment_order_id,
|
|
subscription_id=item.subscription_id,
|
|
subscription_period_id=item.subscription_period_id,
|
|
grant_amount=float(item.grant_amount),
|
|
unspent_amount=float(item.unspent_amount),
|
|
consumed_amount=float(item.consumed_amount),
|
|
expired_amount=float(item.expired_amount),
|
|
revoked_amount=float(item.revoked_amount),
|
|
valid_from=item.valid_from,
|
|
expires_at=item.expires_at,
|
|
last_usable_at=last_usable_at(item.expires_at),
|
|
status=effective_balance_status(item, request_time=checked_at),
|
|
created_at=item.created_at,
|
|
)
|
|
|
|
|
|
@router.get("")
|
|
async def get_credits(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
records, total = await get_records(db, current_user.id, page, page_size)
|
|
summary = await get_balance_summary(db, current_user.id)
|
|
totals_result = await db.execute(
|
|
select(
|
|
func.coalesce(func.sum(case((CreditRecord.type == "recharge", CreditRecord.amount), else_=0)), 0),
|
|
func.coalesce(func.sum(case((CreditRecord.type == "consume", func.abs(CreditRecord.amount)), else_=0)), 0),
|
|
func.coalesce(func.sum(case((CreditRecord.type == "refund", CreditRecord.amount), else_=0)), 0),
|
|
func.coalesce(func.sum(case((CreditRecord.type == "expire", CreditRecord.expired_amount), else_=0)), 0),
|
|
).where(CreditRecord.user_id == current_user.id)
|
|
)
|
|
total_granted, total_consumed, total_refunded, total_expired = totals_result.one()
|
|
return {
|
|
**summary.to_dict(),
|
|
"records": [CreditRecordOut.model_validate(r) for r in records],
|
|
"total": total,
|
|
"total_granted": float(total_granted or 0),
|
|
"total_consumed": float(total_consumed or 0),
|
|
"total_refunded": float(total_refunded or 0),
|
|
"total_expired": float(total_expired or 0),
|
|
}
|
|
|
|
|
|
@router.get("/balances")
|
|
async def list_credit_balances(
|
|
status: str | None = Query(default=None),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
checked_at = utc_now()
|
|
stmt = select(UserCreditBalance).where(UserCreditBalance.user_id == current_user.id)
|
|
stmt = apply_balance_status_filter(stmt, status, request_time=checked_at)
|
|
stmt = stmt.order_by(UserCreditBalance.expires_at.asc(), UserCreditBalance.id.asc())
|
|
result = await db.execute(stmt.offset((page - 1) * page_size).limit(page_size))
|
|
return [_balance_to_out(item, checked_at=checked_at) for item in result.scalars().all()]
|
|
|
|
|
|
@router.get(
|
|
"/credit-ratios",
|
|
response_model=list[CreditRatioOut],
|
|
summary="获取积分比例列表",
|
|
)
|
|
async def list_client_credit_ratios(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
return await list_all_credit_ratios(db)
|
|
|
|
|
|
@router.get("/ratios", response_model=dict)
|
|
async def get_credit_ratios(
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
async def get_ratios_for_engine_type(gen_type: str, engine_ids: list):
|
|
for engine_id in engine_ids:
|
|
result = await db.execute(
|
|
select(CreditRatio)
|
|
.where(CreditRatio.gen_type == gen_type)
|
|
.where(CreditRatio.model_config_id == engine_id)
|
|
)
|
|
ratios = result.scalars().all()
|
|
if ratios:
|
|
return [CreditRatioOut.model_validate(r) for r in ratios]
|
|
return []
|
|
|
|
video_result = await db.execute(
|
|
select(VideoEngine.id)
|
|
.where(VideoEngine.is_active.is_(True), VideoEngine.deleted_at.is_(None))
|
|
.order_by(VideoEngine.priority.desc())
|
|
)
|
|
image_result = await db.execute(
|
|
select(ImageEngine.id)
|
|
.where(ImageEngine.is_active.is_(True), ImageEngine.deleted_at.is_(None))
|
|
.order_by(ImageEngine.priority.desc())
|
|
)
|
|
grouped = {}
|
|
video_ratios = await get_ratios_for_engine_type("video", list(video_result.scalars().all()))
|
|
image_ratios = await get_ratios_for_engine_type("image", list(image_result.scalars().all()))
|
|
if video_ratios:
|
|
grouped["video"] = video_ratios
|
|
if image_ratios:
|
|
grouped["image"] = image_ratios
|
|
return grouped
|