85 lines
2.8 KiB
Go
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})
|
|
}
|