mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
100 lines
2.9 KiB
Go
100 lines
2.9 KiB
Go
package admin
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// upstreamBillingRatesResponse is deliberately smaller than the regular
|
|
// account-list response. The frontend uses the returned ID order to detect a
|
|
// page boundary/order change and only applies snapshots when the page is
|
|
// otherwise stable.
|
|
type upstreamBillingRatesResponse struct {
|
|
Items []service.UpstreamBillingRateSnapshotItem `json:"items"`
|
|
Total int64 `json:"total"`
|
|
Page int `json:"page"`
|
|
PageSize int `json:"page_size"`
|
|
}
|
|
|
|
// GetUpstreamBillingRates returns persisted upstream billing snapshots for the
|
|
// current account-list page. It never probes an upstream and never loads
|
|
// today-stats, usage, concurrency, or credentials for the response.
|
|
//
|
|
// GET /api/v1/admin/accounts/upstream-billing-rates
|
|
func (h *AccountHandler) GetUpstreamBillingRates(c *gin.Context) {
|
|
if h.adminService == nil {
|
|
response.Error(c, http.StatusServiceUnavailable, "account service unavailable")
|
|
return
|
|
}
|
|
|
|
page, pageSize := response.ParsePagination(c)
|
|
platform := c.Query("platform")
|
|
accountType := c.Query("type")
|
|
status := c.Query("status")
|
|
search := strings.TrimSpace(c.Query("search"))
|
|
if len(search) > 100 {
|
|
search = search[:100]
|
|
}
|
|
privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
|
|
sortBy := c.DefaultQuery("sort_by", "name")
|
|
sortOrder := c.DefaultQuery("sort_order", "asc")
|
|
|
|
var groupID int64
|
|
if groupQuery := c.Query("group"); groupQuery != "" {
|
|
if groupQuery == accountListGroupUngroupedQueryValue {
|
|
groupID = service.AccountListGroupUngrouped
|
|
} else {
|
|
parsed, err := strconv.ParseInt(groupQuery, 10, 64)
|
|
if err != nil || parsed < 0 {
|
|
response.BadRequest(c, "invalid group filter")
|
|
return
|
|
}
|
|
groupID = parsed
|
|
}
|
|
}
|
|
|
|
accounts, total, err := h.adminService.ListAccounts(
|
|
c.Request.Context(), page, pageSize, platform, accountType, status,
|
|
search, groupID, privacyMode, sortBy, sortOrder,
|
|
)
|
|
if err != nil {
|
|
response.ErrorFrom(c, err)
|
|
return
|
|
}
|
|
|
|
payload := upstreamBillingRatesResponse{
|
|
Items: service.BuildUpstreamBillingRateSnapshotItems(accounts),
|
|
Total: total,
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
}
|
|
c.Header("Cache-Control", "private, no-cache")
|
|
etag := buildUpstreamBillingRatesETag(payload)
|
|
if etag != "" {
|
|
c.Header("ETag", etag)
|
|
if ifNoneMatchMatched(c.GetHeader("If-None-Match"), etag) {
|
|
c.Status(http.StatusNotModified)
|
|
return
|
|
}
|
|
}
|
|
|
|
response.Success(c, payload)
|
|
}
|
|
|
|
func buildUpstreamBillingRatesETag(payload upstreamBillingRatesResponse) string {
|
|
raw, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
sum := sha256.Sum256(raw)
|
|
return "\"" + hex.EncodeToString(sum[:]) + "\""
|
|
}
|