fix(export): 导出逻辑与当前筛选条件对齐
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
@@ -359,7 +360,7 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
|
|||||||
pageSize := dataPageCap
|
pageSize := dataPageCap
|
||||||
var out []service.Proxy
|
var out []service.Proxy
|
||||||
for {
|
for {
|
||||||
items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "")
|
items, total, err := h.adminService.ListProxies(ctx, page, pageSize, "", "", "", "created_at", "desc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -372,12 +373,12 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string) ([]service.Account, error) {
|
func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, accountType, status, search string, groupID int64, privacyMode, sortBy, sortOrder string) ([]service.Account, error) {
|
||||||
page := 1
|
page := 1
|
||||||
pageSize := dataPageCap
|
pageSize := dataPageCap
|
||||||
var out []service.Account
|
var out []service.Account
|
||||||
for {
|
for {
|
||||||
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, 0, "")
|
items, total, err := h.adminService.ListAccounts(ctx, page, pageSize, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -409,11 +410,28 @@ func (h *AccountHandler) resolveExportAccounts(ctx context.Context, ids []int64,
|
|||||||
platform := c.Query("platform")
|
platform := c.Query("platform")
|
||||||
accountType := c.Query("type")
|
accountType := c.Query("type")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
|
privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
|
||||||
search := strings.TrimSpace(c.Query("search"))
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "name")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "asc")
|
||||||
if len(search) > 100 {
|
if len(search) > 100 {
|
||||||
search = search[:100]
|
search = search[:100]
|
||||||
}
|
}
|
||||||
return h.listAccountsFiltered(ctx, platform, accountType, status, search)
|
|
||||||
|
groupID := int64(0)
|
||||||
|
if groupIDStr := c.Query("group"); groupIDStr != "" {
|
||||||
|
if groupIDStr == accountListGroupUngroupedQueryValue {
|
||||||
|
groupID = service.AccountListGroupUngrouped
|
||||||
|
} else {
|
||||||
|
parsedGroupID, parseErr := strconv.ParseInt(groupIDStr, 10, 64)
|
||||||
|
if parseErr != nil || parsedGroupID <= 0 {
|
||||||
|
return nil, infraerrors.BadRequest("INVALID_GROUP_FILTER", "invalid group filter")
|
||||||
|
}
|
||||||
|
groupID = parsedGroupID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h.listAccountsFiltered(ctx, platform, accountType, status, search, groupID, privacyMode, sortBy, sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AccountHandler) resolveExportProxies(ctx context.Context, accounts []service.Account) ([]service.Proxy, error) {
|
func (h *AccountHandler) resolveExportProxies(ctx context.Context, accounts []service.Account) ([]service.Proxy, error) {
|
||||||
|
|||||||
@@ -172,6 +172,51 @@ func TestExportDataWithoutProxies(t *testing.T) {
|
|||||||
require.Nil(t, resp.Data.Accounts[0].ProxyKey)
|
require.Nil(t, resp.Data.Accounts[0].ProxyKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExportDataPassesAccountFiltersAndSort(t *testing.T) {
|
||||||
|
router, adminSvc := setupAccountDataRouter()
|
||||||
|
adminSvc.accounts = []service.Account{
|
||||||
|
{ID: 1, Name: "acc-1", Status: service.StatusActive},
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(
|
||||||
|
http.MethodGet,
|
||||||
|
"/api/v1/admin/accounts/data?platform=openai&type=oauth&status=active&group=12&privacy_mode=blocked&search=keyword&sort_by=priority&sort_order=desc",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
require.Equal(t, 1, adminSvc.lastListAccounts.calls)
|
||||||
|
require.Equal(t, "openai", adminSvc.lastListAccounts.platform)
|
||||||
|
require.Equal(t, "oauth", adminSvc.lastListAccounts.accountType)
|
||||||
|
require.Equal(t, "active", adminSvc.lastListAccounts.status)
|
||||||
|
require.Equal(t, int64(12), adminSvc.lastListAccounts.groupID)
|
||||||
|
require.Equal(t, "blocked", adminSvc.lastListAccounts.privacyMode)
|
||||||
|
require.Equal(t, "keyword", adminSvc.lastListAccounts.search)
|
||||||
|
require.Equal(t, "priority", adminSvc.lastListAccounts.sortBy)
|
||||||
|
require.Equal(t, "desc", adminSvc.lastListAccounts.sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportDataSelectedIDsOverrideFilters(t *testing.T) {
|
||||||
|
router, adminSvc := setupAccountDataRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(
|
||||||
|
http.MethodGet,
|
||||||
|
"/api/v1/admin/accounts/data?ids=1,2&platform=openai&search=keyword&sort_by=priority&sort_order=desc",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
var resp dataResponse
|
||||||
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||||
|
require.Equal(t, 0, resp.Code)
|
||||||
|
require.Len(t, resp.Data.Accounts, 2)
|
||||||
|
require.Equal(t, 0, adminSvc.lastListAccounts.calls)
|
||||||
|
}
|
||||||
|
|
||||||
func TestImportDataReusesProxyAndSkipsDefaultGroup(t *testing.T) {
|
func TestImportDataReusesProxyAndSkipsDefaultGroup(t *testing.T) {
|
||||||
router, adminSvc := setupAccountDataRouter()
|
router, adminSvc := setupAccountDataRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,13 @@ func (h *ProxyHandler) ExportData(c *gin.Context) {
|
|||||||
protocol := c.Query("protocol")
|
protocol := c.Query("protocol")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
search := strings.TrimSpace(c.Query("search"))
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "id")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
if len(search) > 100 {
|
if len(search) > 100 {
|
||||||
search = search[:100]
|
search = search[:100]
|
||||||
}
|
}
|
||||||
|
|
||||||
proxies, err = h.listProxiesFiltered(ctx, protocol, status, search)
|
proxies, err = h.listProxiesFiltered(ctx, protocol, status, search, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -89,7 +91,7 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
result := DataImportResult{}
|
result := DataImportResult{}
|
||||||
|
|
||||||
existingProxies, err := h.listProxiesFiltered(ctx, "", "", "")
|
existingProxies, err := h.listProxiesFiltered(ctx, "", "", "", "id", "desc")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -220,18 +222,33 @@ func parseProxyIDs(c *gin.Context) ([]int64, error) {
|
|||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search string) ([]service.Proxy, error) {
|
func (h *ProxyHandler) listProxiesFiltered(ctx context.Context, protocol, status, search, sortBy, sortOrder string) ([]service.Proxy, error) {
|
||||||
page := 1
|
page := 1
|
||||||
pageSize := dataPageCap
|
pageSize := dataPageCap
|
||||||
var out []service.Proxy
|
var out []service.Proxy
|
||||||
|
sortBy = strings.TrimSpace(sortBy)
|
||||||
|
useAccountCountSort := strings.EqualFold(sortBy, "account_count")
|
||||||
for {
|
for {
|
||||||
items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search)
|
if useAccountCountSort {
|
||||||
if err != nil {
|
items, total, err := h.adminService.ListProxiesWithAccountCount(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
out = append(out, items...)
|
}
|
||||||
if len(out) >= int(total) || len(items) == 0 {
|
for i := range items {
|
||||||
break
|
out = append(out, items[i].Proxy)
|
||||||
|
}
|
||||||
|
if len(out) >= int(total) || len(items) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items, total, err := h.adminService.ListProxies(ctx, page, pageSize, protocol, status, search, sortBy, sortOrder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, items...)
|
||||||
|
if len(out) >= int(total) || len(items) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
page++
|
page++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ func TestProxyExportDataRespectsFilters(t *testing.T) {
|
|||||||
require.Len(t, resp.Data.Proxies, 1)
|
require.Len(t, resp.Data.Proxies, 1)
|
||||||
require.Len(t, resp.Data.Accounts, 0)
|
require.Len(t, resp.Data.Accounts, 0)
|
||||||
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
|
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
|
||||||
|
require.Equal(t, 1, adminSvc.lastListProxies.calls)
|
||||||
|
require.Equal(t, "https", adminSvc.lastListProxies.protocol)
|
||||||
|
require.Equal(t, "id", adminSvc.lastListProxies.sortBy)
|
||||||
|
require.Equal(t, "desc", adminSvc.lastListProxies.sortOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyExportDataWithSelectedIDs(t *testing.T) {
|
func TestProxyExportDataWithSelectedIDs(t *testing.T) {
|
||||||
@@ -113,6 +117,96 @@ func TestProxyExportDataWithSelectedIDs(t *testing.T) {
|
|||||||
require.Len(t, resp.Data.Proxies, 1)
|
require.Len(t, resp.Data.Proxies, 1)
|
||||||
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
|
require.Equal(t, "https", resp.Data.Proxies[0].Protocol)
|
||||||
require.Equal(t, "10.0.0.2", resp.Data.Proxies[0].Host)
|
require.Equal(t, "10.0.0.2", resp.Data.Proxies[0].Host)
|
||||||
|
require.Equal(t, 0, adminSvc.lastListProxies.calls)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyExportDataPassesSortParams(t *testing.T) {
|
||||||
|
router, adminSvc := setupProxyDataRouter()
|
||||||
|
|
||||||
|
adminSvc.proxies = []service.Proxy{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Name: "proxy-a",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
Username: "user",
|
||||||
|
Password: "pass",
|
||||||
|
Status: service.StatusActive,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?protocol=http&status=active&search=proxy&sort_by=name&sort_order=asc", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
require.Equal(t, 1, adminSvc.lastListProxies.calls)
|
||||||
|
require.Equal(t, "http", adminSvc.lastListProxies.protocol)
|
||||||
|
require.Equal(t, "active", adminSvc.lastListProxies.status)
|
||||||
|
require.Equal(t, "proxy", adminSvc.lastListProxies.search)
|
||||||
|
require.Equal(t, "name", adminSvc.lastListProxies.sortBy)
|
||||||
|
require.Equal(t, "asc", adminSvc.lastListProxies.sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProxyExportDataSortByAccountCountUsesAccountCountListing(t *testing.T) {
|
||||||
|
router, adminSvc := setupProxyDataRouter()
|
||||||
|
|
||||||
|
adminSvc.proxies = []service.Proxy{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Name: "proxy-id-1",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
Name: "proxy-id-2",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.2",
|
||||||
|
Port: 8081,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
adminSvc.proxyCounts = []service.ProxyWithAccountCount{
|
||||||
|
{
|
||||||
|
Proxy: service.Proxy{
|
||||||
|
ID: 2,
|
||||||
|
Name: "proxy-count-high",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.2",
|
||||||
|
Port: 8081,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
},
|
||||||
|
AccountCount: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Proxy: service.Proxy{
|
||||||
|
ID: 1,
|
||||||
|
Name: "proxy-count-low",
|
||||||
|
Protocol: "http",
|
||||||
|
Host: "127.0.0.1",
|
||||||
|
Port: 8080,
|
||||||
|
Status: service.StatusActive,
|
||||||
|
},
|
||||||
|
AccountCount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/proxies/data?sort_by=account_count&sort_order=desc", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
var resp proxyDataResponse
|
||||||
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp))
|
||||||
|
require.Equal(t, 0, resp.Code)
|
||||||
|
require.Len(t, resp.Data.Proxies, 2)
|
||||||
|
require.Equal(t, "proxy-count-high", resp.Data.Proxies[0].Name)
|
||||||
|
require.Equal(t, "proxy-count-low", resp.Data.Proxies[1].Name)
|
||||||
|
require.Equal(t, 0, adminSvc.lastListProxies.calls)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyImportDataReusesAndTriggersLatencyProbe(t *testing.T) {
|
func TestProxyImportDataReusesAndTriggersLatencyProbe(t *testing.T) {
|
||||||
|
|||||||
49
backend/internal/handler/admin/redeem_export_handler_test.go
Normal file
49
backend/internal/handler/admin/redeem_export_handler_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package admin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupRedeemExportRouter() (*gin.Engine, *stubAdminService) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
adminSvc := newStubAdminService()
|
||||||
|
|
||||||
|
h := NewRedeemHandler(adminSvc, nil)
|
||||||
|
router.GET("/api/v1/admin/redeem-codes/export", h.Export)
|
||||||
|
return router, adminSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedeemExportPassesSearchAndSort(t *testing.T) {
|
||||||
|
router, adminSvc := setupRedeemExportRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export?type=balance&status=unused&search=ABC&sort_by=value&sort_order=asc", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls)
|
||||||
|
require.Equal(t, "balance", adminSvc.lastListRedeemCodes.codeType)
|
||||||
|
require.Equal(t, "unused", adminSvc.lastListRedeemCodes.status)
|
||||||
|
require.Equal(t, "ABC", adminSvc.lastListRedeemCodes.search)
|
||||||
|
require.Equal(t, "value", adminSvc.lastListRedeemCodes.sortBy)
|
||||||
|
require.Equal(t, "asc", adminSvc.lastListRedeemCodes.sortOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedeemExportSortDefaults(t *testing.T) {
|
||||||
|
router, adminSvc := setupRedeemExportRouter()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/redeem-codes/export", nil)
|
||||||
|
router.ServeHTTP(rec, req)
|
||||||
|
require.Equal(t, http.StatusOK, rec.Code)
|
||||||
|
|
||||||
|
require.Equal(t, 1, adminSvc.lastListRedeemCodes.calls)
|
||||||
|
require.Equal(t, "id", adminSvc.lastListRedeemCodes.sortBy)
|
||||||
|
require.Equal(t, "desc", adminSvc.lastListRedeemCodes.sortOrder)
|
||||||
|
}
|
||||||
@@ -59,13 +59,15 @@ func (h *RedeemHandler) List(c *gin.Context) {
|
|||||||
codeType := c.Query("type")
|
codeType := c.Query("type")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
search := c.Query("search")
|
search := c.Query("search")
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "id")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
// 标准化和验证 search 参数
|
// 标准化和验证 search 参数
|
||||||
search = strings.TrimSpace(search)
|
search = strings.TrimSpace(search)
|
||||||
if len(search) > 100 {
|
if len(search) > 100 {
|
||||||
search = search[:100]
|
search = search[:100]
|
||||||
}
|
}
|
||||||
|
|
||||||
codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search)
|
codes, total, err := h.adminService.ListRedeemCodes(c.Request.Context(), page, pageSize, codeType, status, search, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -300,9 +302,15 @@ func (h *RedeemHandler) GetStats(c *gin.Context) {
|
|||||||
func (h *RedeemHandler) Export(c *gin.Context) {
|
func (h *RedeemHandler) Export(c *gin.Context) {
|
||||||
codeType := c.Query("type")
|
codeType := c.Query("type")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
|
search := strings.TrimSpace(c.Query("search"))
|
||||||
|
sortBy := c.DefaultQuery("sort_by", "id")
|
||||||
|
sortOrder := c.DefaultQuery("sort_order", "desc")
|
||||||
|
if len(search) > 100 {
|
||||||
|
search = search[:100]
|
||||||
|
}
|
||||||
|
|
||||||
// Get all codes without pagination (use large page size)
|
// Get all codes without pagination (use large page size)
|
||||||
codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, "")
|
codes, _, err := h.adminService.ListRedeemCodes(c.Request.Context(), 1, 10000, codeType, status, search, sortBy, sortOrder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export async function list(
|
|||||||
type?: RedeemCodeType
|
type?: RedeemCodeType
|
||||||
status?: 'active' | 'used' | 'expired' | 'unused'
|
status?: 'active' | 'used' | 'expired' | 'unused'
|
||||||
search?: string
|
search?: string
|
||||||
|
sort_by?: string
|
||||||
|
sort_order?: 'asc' | 'desc'
|
||||||
},
|
},
|
||||||
options?: {
|
options?: {
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
@@ -151,7 +153,10 @@ export async function getStats(): Promise<{
|
|||||||
*/
|
*/
|
||||||
export async function exportCodes(filters?: {
|
export async function exportCodes(filters?: {
|
||||||
type?: RedeemCodeType
|
type?: RedeemCodeType
|
||||||
status?: 'active' | 'used' | 'expired'
|
status?: 'used' | 'expired' | 'unused'
|
||||||
|
search?: string
|
||||||
|
sort_by?: string
|
||||||
|
sort_order?: 'asc' | 'desc'
|
||||||
}): Promise<Blob> {
|
}): Promise<Blob> {
|
||||||
const response = await apiClient.get('/admin/redeem-codes/export', {
|
const response = await apiClient.get('/admin/redeem-codes/export', {
|
||||||
params: filters,
|
params: filters,
|
||||||
|
|||||||
Reference in New Issue
Block a user