Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
690 lines
19 KiB
Go
690 lines
19 KiB
Go
package controller
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"github.com/QuantumNous/new-api/i18n"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/service"
|
|
"github.com/QuantumNous/new-api/setting"
|
|
"github.com/QuantumNous/new-api/setting/operation_setting"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type tokenAutoGroupsInput struct {
|
|
Set bool
|
|
Groups []string
|
|
}
|
|
|
|
func (input *tokenAutoGroupsInput) UnmarshalJSON(data []byte) error {
|
|
input.Set = true
|
|
if strings.TrimSpace(string(data)) == "null" {
|
|
input.Groups = nil
|
|
return nil
|
|
}
|
|
return common.Unmarshal(data, &input.Groups)
|
|
}
|
|
|
|
type tokenGroupPoolInput struct {
|
|
Set bool
|
|
Entries []model.TokenGroupEntry
|
|
}
|
|
|
|
func (input *tokenGroupPoolInput) UnmarshalJSON(data []byte) error {
|
|
input.Set = true
|
|
if strings.TrimSpace(string(data)) == "null" {
|
|
input.Entries = nil
|
|
return nil
|
|
}
|
|
return common.Unmarshal(data, &input.Entries)
|
|
}
|
|
|
|
type tokenRequest struct {
|
|
model.Token
|
|
AstraDetectionEnabled *bool `json:"astra_detection_enabled"`
|
|
AstraAutoSwitch *bool `json:"astra_auto_switch"`
|
|
RoutingPolicy *model.TokenRoutingPolicy `json:"routing_policy"`
|
|
AutoGroups tokenAutoGroupsInput `json:"auto_groups"`
|
|
GroupPool tokenGroupPoolInput `json:"group_pool"`
|
|
}
|
|
|
|
type tokenResponse struct {
|
|
*model.Token
|
|
RoutingPolicy *model.TokenRoutingPolicy `json:"routing_policy,omitempty"`
|
|
AutoGroups []string `json:"auto_groups"`
|
|
GroupPool []model.TokenGroupEntry `json:"group_pool,omitempty"`
|
|
}
|
|
|
|
func maxTokenQuota() int {
|
|
quota, err := common.WalletQuotaFromDecimalStrict(
|
|
decimal.NewFromInt(1_000_000_000).Mul(decimal.NewFromFloat(common.QuotaPerUnit)),
|
|
)
|
|
if err != nil {
|
|
return common.MaxWalletQuota
|
|
}
|
|
return quota
|
|
}
|
|
|
|
func buildMaskedTokenResponse(token *model.Token) *tokenResponse {
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
maskedToken := *token
|
|
maskedToken.Key = token.GetMaskedKey()
|
|
groupPool, err := token.GetGroupPool()
|
|
if err != nil {
|
|
common.SysError(fmt.Sprintf("failed to parse auto groups for token %d: %v", token.Id, err))
|
|
groupPool = nil
|
|
}
|
|
autoGroups := make([]string, 0, len(groupPool))
|
|
for _, entry := range groupPool {
|
|
autoGroups = append(autoGroups, entry.Group)
|
|
}
|
|
if len(autoGroups) == 0 {
|
|
autoGroups = nil
|
|
}
|
|
policy, _ := token.GetRoutingPolicy()
|
|
return &tokenResponse{Token: &maskedToken, AutoGroups: autoGroups, GroupPool: groupPool, RoutingPolicy: policy}
|
|
}
|
|
|
|
func buildMaskedTokenResponses(tokens []*model.Token) []*tokenResponse {
|
|
maskedTokens := make([]*tokenResponse, 0, len(tokens))
|
|
for _, token := range tokens {
|
|
maskedTokens = append(maskedTokens, buildMaskedTokenResponse(token))
|
|
}
|
|
return maskedTokens
|
|
}
|
|
|
|
func getTokenRequestUserGroup(c *gin.Context) (string, error) {
|
|
if userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup); userGroup != "" {
|
|
return userGroup, nil
|
|
}
|
|
if userGroup := c.GetString("group"); userGroup != "" {
|
|
return userGroup, nil
|
|
}
|
|
return model.GetUserGroup(c.GetInt("id"), false)
|
|
}
|
|
|
|
func setTokenAutoGroups(c *gin.Context, token *model.Token, groups []string) bool {
|
|
if len(groups) == 0 {
|
|
if err := token.SetAutoGroups(nil); err != nil {
|
|
common.ApiError(c, err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
maxCount := setting.GetMaxTokenAutoGroups()
|
|
if len(groups) > maxCount {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsTooMany, map[string]any{"Max": maxCount})
|
|
return false
|
|
}
|
|
|
|
userGroup, err := getTokenRequestUserGroup(c)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return false
|
|
}
|
|
seen := make(map[string]struct{}, len(groups))
|
|
for _, group := range groups {
|
|
if _, ok := seen[group]; ok {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsDuplicate, map[string]any{"Group": group})
|
|
return false
|
|
}
|
|
seen[group] = struct{}{}
|
|
if !service.IsUserSelectableGroup(userGroup, group) {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsInvalid, map[string]any{"Group": group})
|
|
return false
|
|
}
|
|
}
|
|
|
|
if err := token.SetAutoGroups(groups); err != nil {
|
|
common.ApiError(c, err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func setTokenBackupGroup(c *gin.Context, token *model.Token, groups []string) bool {
|
|
maxCount := setting.GetMaxTokenAutoGroups()
|
|
if len(groups) > maxCount {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsTooMany, map[string]any{"Max": maxCount})
|
|
return false
|
|
}
|
|
for _, group := range groups {
|
|
if group == token.Group {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsDuplicate, map[string]any{"Group": group})
|
|
return false
|
|
}
|
|
}
|
|
if !setTokenAutoGroups(c, token, groups) {
|
|
return false
|
|
}
|
|
token.CrossGroupRetry = len(groups) > 0
|
|
return true
|
|
}
|
|
|
|
func setTokenGroupPool(c *gin.Context, token *model.Token, entries []model.TokenGroupEntry) bool {
|
|
maxCount := setting.GetMaxTokenAutoGroups()
|
|
if len(entries) > maxCount {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsTooMany, map[string]any{"Max": maxCount})
|
|
return false
|
|
}
|
|
userGroup, err := getTokenRequestUserGroup(c)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return false
|
|
}
|
|
seen := make(map[string]struct{}, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.Group == token.Group {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsDuplicate, map[string]any{"Group": entry.Group})
|
|
return false
|
|
}
|
|
if _, ok := seen[entry.Group]; ok {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsDuplicate, map[string]any{"Group": entry.Group})
|
|
return false
|
|
}
|
|
seen[entry.Group] = struct{}{}
|
|
if !service.IsUserSelectableGroup(userGroup, entry.Group) {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenAutoGroupsInvalid, map[string]any{"Group": entry.Group})
|
|
return false
|
|
}
|
|
}
|
|
if err := token.SetGroupPool(entries); err != nil {
|
|
common.ApiError(c, err)
|
|
return false
|
|
}
|
|
token.CrossGroupRetry = len(entries) > 0
|
|
return true
|
|
}
|
|
|
|
func GetAllTokens(c *gin.Context) {
|
|
userId := c.GetInt("id")
|
|
pageInfo := common.GetPageQuery(c)
|
|
tokens, err := model.GetAllUserTokens(userId, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
total, _ := model.CountUserTokens(userId)
|
|
pageInfo.SetTotal(int(total))
|
|
pageInfo.SetItems(buildMaskedTokenResponses(tokens))
|
|
common.ApiSuccess(c, pageInfo)
|
|
}
|
|
|
|
func SearchTokens(c *gin.Context) {
|
|
userId := c.GetInt("id")
|
|
keyword := c.Query("keyword")
|
|
token := c.Query("token")
|
|
|
|
pageInfo := common.GetPageQuery(c)
|
|
|
|
tokens, total, err := model.SearchUserTokens(userId, keyword, token, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
pageInfo.SetTotal(int(total))
|
|
pageInfo.SetItems(buildMaskedTokenResponses(tokens))
|
|
common.ApiSuccess(c, pageInfo)
|
|
}
|
|
|
|
func GetToken(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
userId := c.GetInt("id")
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
token, err := model.GetTokenByIds(id, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
common.ApiSuccess(c, buildMaskedTokenResponse(token))
|
|
}
|
|
|
|
func GetTokenAutoGroups(c *gin.Context) {
|
|
userGroup, err := getTokenRequestUserGroup(c)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
common.ApiSuccess(c, gin.H{
|
|
"groups": service.GetUserAutoGroup(userGroup),
|
|
"max_count": setting.GetMaxTokenAutoGroups(),
|
|
})
|
|
}
|
|
|
|
func GetTokenKey(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
userId := c.GetInt("id")
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
token, err := model.GetTokenByIds(id, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params := tokenAuditParams(c)
|
|
params["id"], params["name"] = token.Id, token.Name
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
common.ApiSuccess(c, gin.H{
|
|
"key": token.GetFullKey(),
|
|
})
|
|
}
|
|
|
|
func GetTokenStatus(c *gin.Context) {
|
|
tokenId := c.GetInt("token_id")
|
|
userId := c.GetInt("id")
|
|
token, err := model.GetTokenByIds(tokenId, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
expiredAt := token.ExpiredTime
|
|
if expiredAt == -1 {
|
|
expiredAt = 0
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"object": "credit_summary",
|
|
"total_granted": token.RemainQuota,
|
|
"total_used": 0, // not supported currently
|
|
"total_available": token.RemainQuota,
|
|
"expires_at": expiredAt * 1000,
|
|
})
|
|
}
|
|
|
|
func GetTokenUsage(c *gin.Context) {
|
|
authHeader := c.GetHeader("Authorization")
|
|
if authHeader == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"message": "No Authorization header",
|
|
})
|
|
return
|
|
}
|
|
|
|
parts := strings.Split(authHeader, " ")
|
|
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"message": "Invalid Bearer token",
|
|
})
|
|
return
|
|
}
|
|
tokenKey := parts[1]
|
|
|
|
token, err := model.GetTokenByKey(strings.TrimPrefix(tokenKey, "sk-"), false)
|
|
if err != nil {
|
|
common.SysError("failed to get token by key: " + err.Error())
|
|
common.ApiErrorI18n(c, i18n.MsgTokenGetInfoFailed)
|
|
return
|
|
}
|
|
|
|
expiredAt := token.ExpiredTime
|
|
if expiredAt == -1 {
|
|
expiredAt = 0
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"code": true,
|
|
"message": "ok",
|
|
"data": gin.H{
|
|
"object": "token_usage",
|
|
"name": token.Name,
|
|
"total_granted": token.RemainQuota + token.UsedQuota,
|
|
"total_used": token.UsedQuota,
|
|
"total_available": token.RemainQuota,
|
|
"unlimited_quota": token.UnlimitedQuota,
|
|
"model_limits": token.GetModelLimitsMap(),
|
|
"model_limits_enabled": token.ModelLimitsEnabled,
|
|
"expires_at": expiredAt,
|
|
},
|
|
})
|
|
}
|
|
|
|
func AddToken(c *gin.Context) {
|
|
request := tokenRequest{}
|
|
err := c.ShouldBindJSON(&request)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
token := request.Token
|
|
token.AstraDetectionEnabled = true
|
|
if request.AstraDetectionEnabled != nil {
|
|
token.AstraDetectionEnabled = *request.AstraDetectionEnabled
|
|
}
|
|
if request.AstraAutoSwitch != nil {
|
|
token.AstraAutoSwitch = *request.AstraAutoSwitch
|
|
}
|
|
if !token.AstraDetectionEnabled {
|
|
token.AstraAutoSwitch = false
|
|
}
|
|
if len(token.Name) > 50 {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenNameTooLong)
|
|
return
|
|
}
|
|
params := tokenAuditParams(c)
|
|
params["name"] = token.Name
|
|
// 非无限额度时,检查额度值是否超出有效范围
|
|
if !token.UnlimitedQuota {
|
|
if token.RemainQuota < 0 {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenQuotaNegative)
|
|
return
|
|
}
|
|
maxQuotaValue := maxTokenQuota()
|
|
if token.RemainQuota > maxQuotaValue {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenQuotaExceedMax, map[string]any{"Max": maxQuotaValue})
|
|
return
|
|
}
|
|
}
|
|
// 检查用户令牌数量是否已达上限
|
|
maxTokens := operation_setting.GetMaxUserTokens()
|
|
count, err := model.CountUserTokens(c.GetInt("id"))
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
if int(count) >= maxTokens {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": fmt.Sprintf("已达到最大令牌数量限制 (%d)", maxTokens),
|
|
})
|
|
return
|
|
}
|
|
if request.RoutingPolicy != nil {
|
|
if !setTokenRoutingPolicy(c, &token, request.RoutingPolicy) {
|
|
return
|
|
}
|
|
} else if token.Group == "auto" {
|
|
if !setTokenAutoGroups(c, &token, request.AutoGroups.Groups) {
|
|
return
|
|
}
|
|
} else if request.GroupPool.Set {
|
|
if !setTokenGroupPool(c, &token, request.GroupPool.Entries) {
|
|
return
|
|
}
|
|
} else {
|
|
if !setTokenBackupGroup(c, &token, request.AutoGroups.Groups) {
|
|
return
|
|
}
|
|
}
|
|
if token.RoutingPolicy == "" {
|
|
policy, err := token.LegacyRoutingPolicy()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
body, err := common.Marshal(policy)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
token.RoutingPolicy = string(body)
|
|
}
|
|
key, err := common.GenerateKey()
|
|
if err != nil {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenGenerateFailed)
|
|
common.SysLog("failed to generate token key: " + err.Error())
|
|
return
|
|
}
|
|
cleanToken := model.Token{
|
|
UserId: c.GetInt("id"),
|
|
Name: token.Name,
|
|
Key: key,
|
|
CreatedTime: common.GetTimestamp(),
|
|
AccessedTime: common.GetTimestamp(),
|
|
ExpiredTime: token.ExpiredTime,
|
|
RemainQuota: token.RemainQuota,
|
|
UnlimitedQuota: token.UnlimitedQuota,
|
|
ModelLimitsEnabled: token.ModelLimitsEnabled,
|
|
ModelLimits: token.ModelLimits,
|
|
AllowIps: token.AllowIps,
|
|
Group: token.Group,
|
|
CrossGroupRetry: token.CrossGroupRetry,
|
|
AstraAutoSwitch: token.AstraAutoSwitch,
|
|
AstraDetectionEnabled: token.AstraDetectionEnabled,
|
|
AutoGroups: token.AutoGroups,
|
|
RoutingPolicy: token.RoutingPolicy,
|
|
}
|
|
err = cleanToken.Insert()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params["id"] = cleanToken.Id
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
}
|
|
|
|
func DeleteToken(c *gin.Context) {
|
|
id, _ := strconv.Atoi(c.Param("id"))
|
|
userId := c.GetInt("id")
|
|
token, err := model.GetTokenByIds(id, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params := tokenAuditParams(c)
|
|
params["id"], params["name"] = token.Id, token.Name
|
|
err = token.Delete()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
})
|
|
}
|
|
|
|
func UpdateToken(c *gin.Context) {
|
|
userId := c.GetInt("id")
|
|
statusOnly := c.Query("status_only")
|
|
request := tokenRequest{}
|
|
err := c.ShouldBindJSON(&request)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
token := request.Token
|
|
params := tokenAuditParams(c)
|
|
if token.Id > 0 {
|
|
params["id"] = token.Id
|
|
}
|
|
if len(token.Name) > 50 {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenNameTooLong)
|
|
return
|
|
}
|
|
if !token.UnlimitedQuota {
|
|
if token.RemainQuota < 0 {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenQuotaNegative)
|
|
return
|
|
}
|
|
maxQuotaValue := maxTokenQuota()
|
|
if token.RemainQuota > maxQuotaValue {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenQuotaExceedMax, map[string]any{"Max": maxQuotaValue})
|
|
return
|
|
}
|
|
}
|
|
cleanToken, err := model.GetTokenByIds(token.Id, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params["name"] = cleanToken.Name
|
|
previous := *cleanToken
|
|
if token.Status == common.TokenStatusEnabled {
|
|
if cleanToken.Status == common.TokenStatusExpired && cleanToken.ExpiredTime <= common.GetTimestamp() && cleanToken.ExpiredTime != -1 {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenExpiredCannotEnable)
|
|
return
|
|
}
|
|
if cleanToken.Status == common.TokenStatusExhausted && cleanToken.RemainQuota <= 0 && !cleanToken.UnlimitedQuota {
|
|
common.ApiErrorI18n(c, i18n.MsgTokenExhaustedCannotEable)
|
|
return
|
|
}
|
|
}
|
|
if statusOnly != "" {
|
|
cleanToken.Status = token.Status
|
|
} else {
|
|
// If you add more fields, please also update token.Update()
|
|
cleanToken.Name = token.Name
|
|
cleanToken.ExpiredTime = token.ExpiredTime
|
|
cleanToken.RemainQuota = token.RemainQuota
|
|
cleanToken.UnlimitedQuota = token.UnlimitedQuota
|
|
cleanToken.ModelLimitsEnabled = token.ModelLimitsEnabled
|
|
cleanToken.ModelLimits = token.ModelLimits
|
|
cleanToken.AllowIps = token.AllowIps
|
|
if request.AstraDetectionEnabled != nil {
|
|
cleanToken.AstraDetectionEnabled = *request.AstraDetectionEnabled
|
|
}
|
|
if request.AstraAutoSwitch != nil {
|
|
cleanToken.AstraAutoSwitch = *request.AstraAutoSwitch
|
|
}
|
|
if !cleanToken.AstraDetectionEnabled {
|
|
cleanToken.AstraAutoSwitch = false
|
|
}
|
|
if request.RoutingPolicy != nil {
|
|
if !setTokenRoutingPolicy(c, cleanToken, request.RoutingPolicy) {
|
|
return
|
|
}
|
|
} else if cleanToken.RoutingPolicy == "" {
|
|
cleanToken.Group = token.Group
|
|
cleanToken.CrossGroupRetry = token.CrossGroupRetry
|
|
if token.Group != "auto" {
|
|
if request.GroupPool.Set {
|
|
if !setTokenGroupPool(c, cleanToken, request.GroupPool.Entries) {
|
|
return
|
|
}
|
|
} else if !setTokenBackupGroup(c, cleanToken, request.AutoGroups.Groups) {
|
|
return
|
|
}
|
|
} else if request.AutoGroups.Set {
|
|
if !setTokenAutoGroups(c, cleanToken, request.AutoGroups.Groups) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
err = cleanToken.Update()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params["name"] = cleanToken.Name
|
|
if statusOnly != "" {
|
|
params["from"], params["to"] = previous.Status, cleanToken.Status
|
|
} else {
|
|
changedFields := []string{}
|
|
for _, field := range []struct {
|
|
name string
|
|
changed bool
|
|
}{
|
|
{"name", previous.Name != cleanToken.Name},
|
|
{"expired_time", previous.ExpiredTime != cleanToken.ExpiredTime},
|
|
{"remain_quota", previous.RemainQuota != cleanToken.RemainQuota},
|
|
{"unlimited_quota", previous.UnlimitedQuota != cleanToken.UnlimitedQuota},
|
|
{"model_limits_enabled", previous.ModelLimitsEnabled != cleanToken.ModelLimitsEnabled},
|
|
{"model_limits", previous.ModelLimits != cleanToken.ModelLimits},
|
|
{"allow_ips", (previous.AllowIps == nil) != (cleanToken.AllowIps == nil) ||
|
|
(previous.AllowIps != nil && cleanToken.AllowIps != nil && *previous.AllowIps != *cleanToken.AllowIps)},
|
|
{"group", previous.Group != cleanToken.Group},
|
|
{"cross_group_retry", previous.CrossGroupRetry != cleanToken.CrossGroupRetry},
|
|
{"astra_auto_switch", previous.AstraAutoSwitch != cleanToken.AstraAutoSwitch},
|
|
{"astra_detection_enabled", previous.AstraDetectionEnabled != cleanToken.AstraDetectionEnabled},
|
|
{"auto_groups", previous.AutoGroups != cleanToken.AutoGroups},
|
|
{"routing_policy", previous.RoutingPolicy != cleanToken.RoutingPolicy},
|
|
} {
|
|
if field.changed {
|
|
changedFields = append(changedFields, field.name)
|
|
}
|
|
}
|
|
params["changed_fields"] = changedFields
|
|
}
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": buildMaskedTokenResponse(cleanToken),
|
|
})
|
|
}
|
|
|
|
type TokenBatch struct {
|
|
Ids []int `json:"ids"`
|
|
}
|
|
|
|
func DeleteTokenBatch(c *gin.Context) {
|
|
tokenBatch := TokenBatch{}
|
|
if err := c.ShouldBindJSON(&tokenBatch); err != nil {
|
|
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
|
return
|
|
}
|
|
params := tokenBatchAuditParams(c, tokenBatch.Ids)
|
|
if len(tokenBatch.Ids) == 0 {
|
|
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
|
return
|
|
}
|
|
userId := c.GetInt("id")
|
|
count, err := model.BatchDeleteTokens(tokenBatch.Ids, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
params["count"] = count
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": count,
|
|
})
|
|
}
|
|
|
|
func GetTokenKeysBatch(c *gin.Context) {
|
|
tokenBatch := TokenBatch{}
|
|
if err := c.ShouldBindJSON(&tokenBatch); err != nil {
|
|
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
|
return
|
|
}
|
|
params := tokenBatchAuditParams(c, tokenBatch.Ids)
|
|
if len(tokenBatch.Ids) == 0 {
|
|
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
|
return
|
|
}
|
|
if len(tokenBatch.Ids) > 100 {
|
|
common.ApiErrorI18n(c, i18n.MsgBatchTooMany, map[string]any{"Max": 100})
|
|
return
|
|
}
|
|
userId := c.GetInt("id")
|
|
tokens, err := model.GetTokenKeysByIds(tokenBatch.Ids, userId)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
keysMap := make(map[int]string)
|
|
returnedIDs := make([]int, 0, len(tokens))
|
|
for _, t := range tokens {
|
|
keysMap[t.Id] = t.GetFullKey()
|
|
returnedIDs = append(returnedIDs, t.Id)
|
|
}
|
|
params["count"] = len(tokens)
|
|
params["returned_ids"] = returnedIDs
|
|
common.SetContextKey(c, constant.ContextKeyTokenAuditSucceeded, true)
|
|
common.ApiSuccess(c, gin.H{"keys": keysMap})
|
|
}
|