Files
new-api-custom/controller/bot_balance.go
T

85 lines
2.8 KiB
Go

package controller
import (
"crypto/sha256"
"crypto/subtle"
"math"
"net/http"
"net/mail"
"os"
"strings"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
)
// BotBalanceQuery is a dedicated read-only API; its key grants no dashboard access.
func BotBalanceQuery(c *gin.Context) {
c.Header("Cache-Control", "no-store")
key := os.Getenv("BOT_BALANCE_KEY")
raw, bearer := strings.CutPrefix(c.GetHeader("Authorization"), "Bearer ")
expected, supplied := sha256.Sum256([]byte(key)), sha256.Sum256([]byte(raw))
if len(key) < 32 || !bearer || subtle.ConstantTimeCompare(expected[:], supplied[:]) != 1 {
c.JSON(http.StatusUnauthorized, gin.H{"success": false, "code": "unauthorized"})
return
}
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 8192)
var request struct {
Email string `json:"email"`
IDs []int `json:"ids"`
}
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"})
return
}
email := model.NormalizeEmail(request.Email)
if (email == "") == (len(request.IDs) == 0) || len(request.IDs) > 100 {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"})
return
}
if email != "" {
parsed, err := mail.ParseAddress(email)
if err != nil || parsed.Address != email || len(email) > 50 {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_email"})
return
}
}
for _, id := range request.IDs {
if id <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "code": "invalid_request"})
return
}
}
accounts, err := model.FindBotBalanceAccounts(email, request.IDs)
if err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false, "code": "query_failed"})
return
}
if email != "" && len(accounts) != 1 {
code := "not_found"
if len(accounts) > 1 {
code = "ambiguous_email"
}
c.JSON(http.StatusConflict, gin.H{"success": false, "code": code})
return
}
unit, rate := common.QuotaPerUnit, operation_setting.USDExchangeRate
if unit <= 0 || rate <= 0 || math.IsNaN(unit) || math.IsNaN(rate) || math.IsInf(unit, 0) || math.IsInf(rate, 0) {
c.JSON(http.StatusServiceUnavailable, gin.H{"success": false, "code": "invalid_currency"})
return
}
items := make([]gin.H, 0, len(accounts))
for _, account := range accounts {
active := account.Status == common.UserStatusEnabled
item := gin.H{"id": account.ID, "active": active}
if active {
item["balance_cny"] = decimal.NewFromInt(int64(account.Quota)).Mul(decimal.NewFromFloat(rate)).DivRound(decimal.NewFromFloat(unit), 18).String()
}
items = append(items, item)
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": items})
}