Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
1ef3782d
Unverified
Commit
1ef3782d
authored
Apr 11, 2026
by
Wesley Liddick
Committed by
GitHub
Apr 11, 2026
Browse files
Merge pull request #1538 from IanShaw027/fix/bug-cleanup-main
fix: 修复多个 UI 和功能问题 - 表格排序搜索、导出逻辑、分页配置和状态筛选
parents
00c08c57
f480e573
Changes
117
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/account_data.go
View file @
1ef3782d
...
@@ -10,6 +10,7 @@ import (
...
@@ -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
...
@@ -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
...
@@ -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,
...
@@ -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
)
{
...
...
backend/internal/handler/admin/account_data_handler_test.go
View file @
1ef3782d
...
@@ -172,6 +172,51 @@ func TestExportDataWithoutProxies(t *testing.T) {
...
@@ -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
()
...
...
backend/internal/handler/admin/account_handler.go
View file @
1ef3782d
...
@@ -221,6 +221,8 @@ func (h *AccountHandler) List(c *gin.Context) {
...
@@ -221,6 +221,8 @@ func (h *AccountHandler) List(c *gin.Context) {
status
:=
c
.
Query
(
"status"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
search
:=
c
.
Query
(
"search"
)
privacyMode
:=
strings
.
TrimSpace
(
c
.
Query
(
"privacy_mode"
))
privacyMode
:=
strings
.
TrimSpace
(
c
.
Query
(
"privacy_mode"
))
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"name"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"asc"
)
// 标准化和验证 search 参数
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
if
len
(
search
)
>
100
{
...
@@ -246,7 +248,7 @@ func (h *AccountHandler) List(c *gin.Context) {
...
@@ -246,7 +248,7 @@ func (h *AccountHandler) List(c *gin.Context) {
}
}
}
}
accounts
,
total
,
err
:=
h
.
adminService
.
ListAccounts
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
accountType
,
status
,
search
,
groupID
,
privacyMode
)
accounts
,
total
,
err
:=
h
.
adminService
.
ListAccounts
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
accountType
,
status
,
search
,
groupID
,
privacyMode
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
@@ -2029,7 +2031,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
...
@@ -2029,7 +2031,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
accounts
:=
make
([]
*
service
.
Account
,
0
)
accounts
:=
make
([]
*
service
.
Account
,
0
)
if
len
(
req
.
AccountIDs
)
==
0
{
if
len
(
req
.
AccountIDs
)
==
0
{
allAccounts
,
_
,
err
:=
h
.
adminService
.
ListAccounts
(
ctx
,
1
,
10000
,
"gemini"
,
"oauth"
,
""
,
""
,
0
,
""
)
allAccounts
,
_
,
err
:=
h
.
adminService
.
ListAccounts
(
ctx
,
1
,
10000
,
"gemini"
,
"oauth"
,
""
,
""
,
0
,
""
,
"name"
,
"asc"
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/admin/admin_service_stub_test.go
View file @
1ef3782d
...
@@ -31,6 +31,33 @@ type stubAdminService struct {
...
@@ -31,6 +31,33 @@ type stubAdminService struct {
platform
string
platform
string
groupIDs
[]
int64
groupIDs
[]
int64
}
}
lastListAccounts
struct
{
platform
string
accountType
string
status
string
search
string
groupID
int64
privacyMode
string
sortBy
string
sortOrder
string
calls
int
}
lastListProxies
struct
{
protocol
string
status
string
search
string
sortBy
string
sortOrder
string
calls
int
}
lastListRedeemCodes
struct
{
codeType
string
status
string
search
string
sortBy
string
sortOrder
string
calls
int
}
mu
sync
.
Mutex
mu
sync
.
Mutex
}
}
...
@@ -99,7 +126,7 @@ func newStubAdminService() *stubAdminService {
...
@@ -99,7 +126,7 @@ func newStubAdminService() *stubAdminService {
}
}
}
}
func
(
s
*
stubAdminService
)
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
service
.
UserListFilters
)
([]
service
.
User
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListUsers
(
ctx
context
.
Context
,
page
,
pageSize
int
,
filters
service
.
UserListFilters
,
sortBy
,
sortOrder
string
)
([]
service
.
User
,
int64
,
error
)
{
return
s
.
users
,
int64
(
len
(
s
.
users
)),
nil
return
s
.
users
,
int64
(
len
(
s
.
users
)),
nil
}
}
...
@@ -132,7 +159,7 @@ func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64,
...
@@ -132,7 +159,7 @@ func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64,
return
&
user
,
nil
return
&
user
,
nil
}
}
func
(
s
*
stubAdminService
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
)
([]
service
.
APIKey
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
GetUserAPIKeys
(
ctx
context
.
Context
,
userID
int64
,
page
,
pageSize
int
,
sortBy
,
sortOrder
string
)
([]
service
.
APIKey
,
int64
,
error
)
{
return
s
.
apiKeys
,
int64
(
len
(
s
.
apiKeys
)),
nil
return
s
.
apiKeys
,
int64
(
len
(
s
.
apiKeys
)),
nil
}
}
...
@@ -140,7 +167,7 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64,
...
@@ -140,7 +167,7 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64,
return
map
[
string
]
any
{
"user_id"
:
userID
},
nil
return
map
[
string
]
any
{
"user_id"
:
userID
},
nil
}
}
func
(
s
*
stubAdminService
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
)
([]
service
.
Group
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListGroups
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
status
,
search
string
,
isExclusive
*
bool
,
sortBy
,
sortOrder
string
)
([]
service
.
Group
,
int64
,
error
)
{
return
s
.
groups
,
int64
(
len
(
s
.
groups
)),
nil
return
s
.
groups
,
int64
(
len
(
s
.
groups
)),
nil
}
}
...
@@ -187,7 +214,16 @@ func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int
...
@@ -187,7 +214,16 @@ func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int
return
nil
return
nil
}
}
func
(
s
*
stubAdminService
)
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
)
([]
service
.
Account
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListAccounts
(
ctx
context
.
Context
,
page
,
pageSize
int
,
platform
,
accountType
,
status
,
search
string
,
groupID
int64
,
privacyMode
string
,
sortBy
,
sortOrder
string
)
([]
service
.
Account
,
int64
,
error
)
{
s
.
lastListAccounts
.
platform
=
platform
s
.
lastListAccounts
.
accountType
=
accountType
s
.
lastListAccounts
.
status
=
status
s
.
lastListAccounts
.
search
=
search
s
.
lastListAccounts
.
groupID
=
groupID
s
.
lastListAccounts
.
privacyMode
=
privacyMode
s
.
lastListAccounts
.
sortBy
=
sortBy
s
.
lastListAccounts
.
sortOrder
=
sortOrder
s
.
lastListAccounts
.
calls
++
return
s
.
accounts
,
int64
(
len
(
s
.
accounts
)),
nil
return
s
.
accounts
,
int64
(
len
(
s
.
accounts
)),
nil
}
}
...
@@ -261,7 +297,13 @@ func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAcc
...
@@ -261,7 +297,13 @@ func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAcc
return
s
.
checkMixedErr
return
s
.
checkMixedErr
}
}
func
(
s
*
stubAdminService
)
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
service
.
Proxy
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListProxies
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
service
.
Proxy
,
int64
,
error
)
{
s
.
lastListProxies
.
protocol
=
protocol
s
.
lastListProxies
.
status
=
status
s
.
lastListProxies
.
search
=
search
s
.
lastListProxies
.
sortBy
=
sortBy
s
.
lastListProxies
.
sortOrder
=
sortOrder
s
.
lastListProxies
.
calls
++
search
=
strings
.
TrimSpace
(
strings
.
ToLower
(
search
))
search
=
strings
.
TrimSpace
(
strings
.
ToLower
(
search
))
filtered
:=
make
([]
service
.
Proxy
,
0
,
len
(
s
.
proxies
))
filtered
:=
make
([]
service
.
Proxy
,
0
,
len
(
s
.
proxies
))
for
_
,
proxy
:=
range
s
.
proxies
{
for
_
,
proxy
:=
range
s
.
proxies
{
...
@@ -283,7 +325,7 @@ func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int,
...
@@ -283,7 +325,7 @@ func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int,
return
filtered
,
int64
(
len
(
filtered
)),
nil
return
filtered
,
int64
(
len
(
filtered
)),
nil
}
}
func
(
s
*
stubAdminService
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
)
([]
service
.
ProxyWithAccountCount
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListProxiesWithAccountCount
(
ctx
context
.
Context
,
page
,
pageSize
int
,
protocol
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
service
.
ProxyWithAccountCount
,
int64
,
error
)
{
return
s
.
proxyCounts
,
int64
(
len
(
s
.
proxyCounts
)),
nil
return
s
.
proxyCounts
,
int64
(
len
(
s
.
proxyCounts
)),
nil
}
}
...
@@ -384,7 +426,13 @@ func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*se
...
@@ -384,7 +426,13 @@ func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*se
},
nil
},
nil
}
}
func
(
s
*
stubAdminService
)
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
)
([]
service
.
RedeemCode
,
int64
,
error
)
{
func
(
s
*
stubAdminService
)
ListRedeemCodes
(
ctx
context
.
Context
,
page
,
pageSize
int
,
codeType
,
status
,
search
string
,
sortBy
,
sortOrder
string
)
([]
service
.
RedeemCode
,
int64
,
error
)
{
s
.
lastListRedeemCodes
.
codeType
=
codeType
s
.
lastListRedeemCodes
.
status
=
status
s
.
lastListRedeemCodes
.
search
=
search
s
.
lastListRedeemCodes
.
sortBy
=
sortBy
s
.
lastListRedeemCodes
.
sortOrder
=
sortOrder
s
.
lastListRedeemCodes
.
calls
++
return
s
.
redeems
,
int64
(
len
(
s
.
redeems
)),
nil
return
s
.
redeems
,
int64
(
len
(
s
.
redeems
)),
nil
}
}
...
...
backend/internal/handler/admin/announcement_handler.go
View file @
1ef3782d
...
@@ -52,13 +52,17 @@ func (h *AnnouncementHandler) List(c *gin.Context) {
...
@@ -52,13 +52,17 @@ func (h *AnnouncementHandler) List(c *gin.Context) {
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
status
:=
strings
.
TrimSpace
(
c
.
Query
(
"status"
))
status
:=
strings
.
TrimSpace
(
c
.
Query
(
"status"
))
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
if
len
(
search
)
>
200
{
if
len
(
search
)
>
200
{
search
=
search
[
:
200
]
search
=
search
[
:
200
]
}
}
params
:=
pagination
.
PaginationParams
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
Page
:
page
,
PageSize
:
pageSize
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
,
}
}
items
,
paginationResult
,
err
:=
h
.
announcementService
.
List
(
items
,
paginationResult
,
err
:=
h
.
announcementService
.
List
(
...
@@ -227,8 +231,10 @@ func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) {
...
@@ -227,8 +231,10 @@ func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) {
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
params
:=
pagination
.
PaginationParams
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
Page
:
page
,
PageSize
:
pageSize
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"email"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"asc"
),
}
}
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
if
len
(
search
)
>
200
{
if
len
(
search
)
>
200
{
...
...
backend/internal/handler/admin/announcement_handler_sort_test.go
0 → 100644
View file @
1ef3782d
package
admin
import
(
"context"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
type
announcementRepoCapture
struct
{
service
.
AnnouncementRepository
listParams
pagination
.
PaginationParams
}
func
(
r
*
announcementRepoCapture
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
service
.
AnnouncementListFilters
)
([]
service
.
Announcement
,
*
pagination
.
PaginationResult
,
error
)
{
r
.
listParams
=
params
return
[]
service
.
Announcement
{},
&
pagination
.
PaginationResult
{
Total
:
0
,
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
Pages
:
0
,
},
nil
}
func
(
r
*
announcementRepoCapture
)
GetByID
(
ctx
context
.
Context
,
id
int64
)
(
*
service
.
Announcement
,
error
)
{
return
&
service
.
Announcement
{
ID
:
id
,
Title
:
"announcement"
,
Content
:
"content"
,
Status
:
service
.
AnnouncementStatusActive
,
CreatedAt
:
time
.
Now
(),
UpdatedAt
:
time
.
Now
(),
},
nil
}
type
announcementUserRepoCapture
struct
{
service
.
UserRepository
listParams
pagination
.
PaginationParams
}
func
(
r
*
announcementUserRepoCapture
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
service
.
UserListFilters
)
([]
service
.
User
,
*
pagination
.
PaginationResult
,
error
)
{
r
.
listParams
=
params
return
[]
service
.
User
{},
&
pagination
.
PaginationResult
{
Total
:
0
,
Page
:
params
.
Page
,
PageSize
:
params
.
PageSize
,
Pages
:
0
,
},
nil
}
type
announcementReadRepoCapture
struct
{
service
.
AnnouncementReadRepository
}
func
(
r
*
announcementReadRepoCapture
)
GetReadMapByUsers
(
ctx
context
.
Context
,
announcementID
int64
,
userIDs
[]
int64
)
(
map
[
int64
]
time
.
Time
,
error
)
{
return
map
[
int64
]
time
.
Time
{},
nil
}
type
announcementUserSubRepoCapture
struct
{
service
.
UserSubscriptionRepository
}
func
newAnnouncementSortTestRouter
(
announcementRepo
*
announcementRepoCapture
,
userRepo
*
announcementUserRepoCapture
)
*
gin
.
Engine
{
gin
.
SetMode
(
gin
.
TestMode
)
svc
:=
service
.
NewAnnouncementService
(
announcementRepo
,
&
announcementReadRepoCapture
{},
userRepo
,
&
announcementUserSubRepoCapture
{},
)
handler
:=
NewAnnouncementHandler
(
svc
)
router
:=
gin
.
New
()
router
.
GET
(
"/admin/announcements"
,
handler
.
List
)
router
.
GET
(
"/admin/announcements/:id/read-status"
,
handler
.
ListReadStatus
)
return
router
}
func
TestAdminAnnouncementListSortParams
(
t
*
testing
.
T
)
{
announcementRepo
:=
&
announcementRepoCapture
{}
userRepo
:=
&
announcementUserRepoCapture
{}
router
:=
newAnnouncementSortTestRouter
(
announcementRepo
,
userRepo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/announcements?sort_by=title&sort_order=ASC"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"title"
,
announcementRepo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"ASC"
,
announcementRepo
.
listParams
.
SortOrder
)
}
func
TestAdminAnnouncementListSortDefaults
(
t
*
testing
.
T
)
{
announcementRepo
:=
&
announcementRepoCapture
{}
userRepo
:=
&
announcementUserRepoCapture
{}
router
:=
newAnnouncementSortTestRouter
(
announcementRepo
,
userRepo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/announcements"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"created_at"
,
announcementRepo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"desc"
,
announcementRepo
.
listParams
.
SortOrder
)
}
func
TestAdminAnnouncementReadStatusSortParams
(
t
*
testing
.
T
)
{
announcementRepo
:=
&
announcementRepoCapture
{}
userRepo
:=
&
announcementUserRepoCapture
{}
router
:=
newAnnouncementSortTestRouter
(
announcementRepo
,
userRepo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/announcements/1/read-status?sort_by=balance&sort_order=DESC"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"balance"
,
userRepo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"DESC"
,
userRepo
.
listParams
.
SortOrder
)
}
func
TestAdminAnnouncementReadStatusSortDefaults
(
t
*
testing
.
T
)
{
announcementRepo
:=
&
announcementRepoCapture
{}
userRepo
:=
&
announcementUserRepoCapture
{}
router
:=
newAnnouncementSortTestRouter
(
announcementRepo
,
userRepo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/announcements/1/read-status"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"email"
,
userRepo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"asc"
,
userRepo
.
listParams
.
SortOrder
)
}
backend/internal/handler/admin/channel_handler.go
View file @
1ef3782d
...
@@ -245,7 +245,12 @@ func (h *ChannelHandler) List(c *gin.Context) {
...
@@ -245,7 +245,12 @@ func (h *ChannelHandler) List(c *gin.Context) {
search
=
search
[
:
100
]
search
=
search
[
:
100
]
}
}
channels
,
pag
,
err
:=
h
.
channelService
.
List
(
c
.
Request
.
Context
(),
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
},
status
,
search
)
channels
,
pag
,
err
:=
h
.
channelService
.
List
(
c
.
Request
.
Context
(),
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"desc"
),
},
status
,
search
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/admin/group_handler.go
View file @
1ef3782d
...
@@ -162,6 +162,8 @@ func (h *GroupHandler) List(c *gin.Context) {
...
@@ -162,6 +162,8 @@ func (h *GroupHandler) List(c *gin.Context) {
search
=
search
[
:
100
]
search
=
search
[
:
100
]
}
}
isExclusiveStr
:=
c
.
Query
(
"is_exclusive"
)
isExclusiveStr
:=
c
.
Query
(
"is_exclusive"
)
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"sort_order"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"asc"
)
var
isExclusive
*
bool
var
isExclusive
*
bool
if
isExclusiveStr
!=
""
{
if
isExclusiveStr
!=
""
{
...
@@ -169,7 +171,7 @@ func (h *GroupHandler) List(c *gin.Context) {
...
@@ -169,7 +171,7 @@ func (h *GroupHandler) List(c *gin.Context) {
isExclusive
=
&
val
isExclusive
=
&
val
}
}
groups
,
total
,
err
:=
h
.
adminService
.
ListGroups
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
status
,
search
,
isExclusive
)
groups
,
total
,
err
:=
h
.
adminService
.
ListGroups
(
c
.
Request
.
Context
(),
page
,
pageSize
,
platform
,
status
,
search
,
isExclusive
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/admin/promo_handler.go
View file @
1ef3782d
...
@@ -55,8 +55,10 @@ func (h *PromoHandler) List(c *gin.Context) {
...
@@ -55,8 +55,10 @@ func (h *PromoHandler) List(c *gin.Context) {
}
}
params
:=
pagination
.
PaginationParams
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
Page
:
page
,
PageSize
:
pageSize
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"desc"
),
}
}
codes
,
paginationResult
,
err
:=
h
.
promoService
.
List
(
c
.
Request
.
Context
(),
params
,
status
,
search
)
codes
,
paginationResult
,
err
:=
h
.
promoService
.
List
(
c
.
Request
.
Context
(),
params
,
status
,
search
)
...
...
backend/internal/handler/admin/proxy_data.go
View file @
1ef3782d
...
@@ -33,11 +33,13 @@ func (h *ProxyHandler) ExportData(c *gin.Context) {
...
@@ -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) {
...
@@ -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) {
...
@@ -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
++
}
}
...
...
backend/internal/handler/admin/proxy_data_handler_test.go
View file @
1ef3782d
...
@@ -74,6 +74,10 @@ func TestProxyExportDataRespectsFilters(t *testing.T) {
...
@@ -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) {
...
@@ -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
)
{
...
...
backend/internal/handler/admin/proxy_handler.go
View file @
1ef3782d
...
@@ -52,13 +52,15 @@ func (h *ProxyHandler) List(c *gin.Context) {
...
@@ -52,13 +52,15 @@ func (h *ProxyHandler) List(c *gin.Context) {
protocol
:=
c
.
Query
(
"protocol"
)
protocol
:=
c
.
Query
(
"protocol"
)
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
]
}
}
proxies
,
total
,
err
:=
h
.
adminService
.
ListProxiesWithAccountCount
(
c
.
Request
.
Context
(),
page
,
pageSize
,
protocol
,
status
,
search
)
proxies
,
total
,
err
:=
h
.
adminService
.
ListProxiesWithAccountCount
(
c
.
Request
.
Context
(),
page
,
pageSize
,
protocol
,
status
,
search
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/admin/redeem_export_handler_test.go
0 → 100644
View file @
1ef3782d
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
)
}
backend/internal/handler/admin/redeem_handler.go
View file @
1ef3782d
...
@@ -59,13 +59,15 @@ func (h *RedeemHandler) List(c *gin.Context) {
...
@@ -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) {
...
@@ -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
...
...
backend/internal/handler/admin/setting_handler.go
View file @
1ef3782d
...
@@ -137,6 +137,8 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
...
@@ -137,6 +137,8 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
HideCcsImportButton
:
settings
.
HideCcsImportButton
,
HideCcsImportButton
:
settings
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
settings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionEnabled
:
settings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionURL
:
settings
.
PurchaseSubscriptionURL
,
PurchaseSubscriptionURL
:
settings
.
PurchaseSubscriptionURL
,
TableDefaultPageSize
:
settings
.
TableDefaultPageSize
,
TablePageSizeOptions
:
settings
.
TablePageSizeOptions
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
settings
.
CustomMenuItems
),
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
settings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
settings
.
CustomEndpoints
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
settings
.
CustomEndpoints
),
DefaultConcurrency
:
settings
.
DefaultConcurrency
,
DefaultConcurrency
:
settings
.
DefaultConcurrency
,
...
@@ -230,6 +232,8 @@ type UpdateSettingsRequest struct {
...
@@ -230,6 +232,8 @@ type UpdateSettingsRequest struct {
HideCcsImportButton
bool
`json:"hide_ccs_import_button"`
HideCcsImportButton
bool
`json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled
*
bool
`json:"purchase_subscription_enabled"`
PurchaseSubscriptionEnabled
*
bool
`json:"purchase_subscription_enabled"`
PurchaseSubscriptionURL
*
string
`json:"purchase_subscription_url"`
PurchaseSubscriptionURL
*
string
`json:"purchase_subscription_url"`
TableDefaultPageSize
int
`json:"table_default_page_size"`
TablePageSizeOptions
[]
int
`json:"table_page_size_options"`
CustomMenuItems
*
[]
dto
.
CustomMenuItem
`json:"custom_menu_items"`
CustomMenuItems
*
[]
dto
.
CustomMenuItem
`json:"custom_menu_items"`
CustomEndpoints
*
[]
dto
.
CustomEndpoint
`json:"custom_endpoints"`
CustomEndpoints
*
[]
dto
.
CustomEndpoint
`json:"custom_endpoints"`
...
@@ -292,6 +296,13 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -292,6 +296,13 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if
req
.
DefaultBalance
<
0
{
if
req
.
DefaultBalance
<
0
{
req
.
DefaultBalance
=
0
req
.
DefaultBalance
=
0
}
}
// 通用表格配置:兼容旧客户端未传字段时保留当前值。
if
req
.
TableDefaultPageSize
<=
0
{
req
.
TableDefaultPageSize
=
previousSettings
.
TableDefaultPageSize
}
if
req
.
TablePageSizeOptions
==
nil
{
req
.
TablePageSizeOptions
=
previousSettings
.
TablePageSizeOptions
}
req
.
SMTPHost
=
strings
.
TrimSpace
(
req
.
SMTPHost
)
req
.
SMTPHost
=
strings
.
TrimSpace
(
req
.
SMTPHost
)
req
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
req
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
req
.
SMTPPassword
=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
req
.
SMTPPassword
=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
...
@@ -757,6 +768,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -757,6 +768,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton
:
req
.
HideCcsImportButton
,
HideCcsImportButton
:
req
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
purchaseEnabled
,
PurchaseSubscriptionEnabled
:
purchaseEnabled
,
PurchaseSubscriptionURL
:
purchaseURL
,
PurchaseSubscriptionURL
:
purchaseURL
,
TableDefaultPageSize
:
req
.
TableDefaultPageSize
,
TablePageSizeOptions
:
req
.
TablePageSizeOptions
,
CustomMenuItems
:
customMenuJSON
,
CustomMenuItems
:
customMenuJSON
,
CustomEndpoints
:
customEndpointsJSON
,
CustomEndpoints
:
customEndpointsJSON
,
DefaultConcurrency
:
req
.
DefaultConcurrency
,
DefaultConcurrency
:
req
.
DefaultConcurrency
,
...
@@ -894,6 +907,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -894,6 +907,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton
:
updatedSettings
.
HideCcsImportButton
,
HideCcsImportButton
:
updatedSettings
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
updatedSettings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionEnabled
:
updatedSettings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionURL
:
updatedSettings
.
PurchaseSubscriptionURL
,
PurchaseSubscriptionURL
:
updatedSettings
.
PurchaseSubscriptionURL
,
TableDefaultPageSize
:
updatedSettings
.
TableDefaultPageSize
,
TablePageSizeOptions
:
updatedSettings
.
TablePageSizeOptions
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
updatedSettings
.
CustomMenuItems
),
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
updatedSettings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
updatedSettings
.
CustomEndpoints
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
updatedSettings
.
CustomEndpoints
),
DefaultConcurrency
:
updatedSettings
.
DefaultConcurrency
,
DefaultConcurrency
:
updatedSettings
.
DefaultConcurrency
,
...
@@ -1152,6 +1167,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
...
@@ -1152,6 +1167,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if
before
.
PurchaseSubscriptionURL
!=
after
.
PurchaseSubscriptionURL
{
if
before
.
PurchaseSubscriptionURL
!=
after
.
PurchaseSubscriptionURL
{
changed
=
append
(
changed
,
"purchase_subscription_url"
)
changed
=
append
(
changed
,
"purchase_subscription_url"
)
}
}
if
before
.
TableDefaultPageSize
!=
after
.
TableDefaultPageSize
{
changed
=
append
(
changed
,
"table_default_page_size"
)
}
if
!
equalIntSlice
(
before
.
TablePageSizeOptions
,
after
.
TablePageSizeOptions
)
{
changed
=
append
(
changed
,
"table_page_size_options"
)
}
if
before
.
CustomMenuItems
!=
after
.
CustomMenuItems
{
if
before
.
CustomMenuItems
!=
after
.
CustomMenuItems
{
changed
=
append
(
changed
,
"custom_menu_items"
)
changed
=
append
(
changed
,
"custom_menu_items"
)
}
}
...
@@ -1208,6 +1229,18 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
...
@@ -1208,6 +1229,18 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
return
true
return
true
}
}
func
equalIntSlice
(
a
,
b
[]
int
)
bool
{
if
len
(
a
)
!=
len
(
b
)
{
return
false
}
for
i
:=
range
a
{
if
a
[
i
]
!=
b
[
i
]
{
return
false
}
}
return
true
}
// TestSMTPRequest 测试SMTP连接请求
// TestSMTPRequest 测试SMTP连接请求
type
TestSMTPRequest
struct
{
type
TestSMTPRequest
struct
{
SMTPHost
string
`json:"smtp_host"`
SMTPHost
string
`json:"smtp_host"`
...
...
backend/internal/handler/admin/usage_handler.go
View file @
1ef3782d
...
@@ -165,7 +165,12 @@ func (h *UsageHandler) List(c *gin.Context) {
...
@@ -165,7 +165,12 @@ func (h *UsageHandler) List(c *gin.Context) {
endTime
=
&
t
endTime
=
&
t
}
}
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"desc"
),
}
filters
:=
usagestats
.
UsageLogFilters
{
filters
:=
usagestats
.
UsageLogFilters
{
UserID
:
userID
,
UserID
:
userID
,
APIKeyID
:
apiKeyID
,
APIKeyID
:
apiKeyID
,
...
@@ -339,7 +344,7 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
...
@@ -339,7 +344,7 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
}
}
// Limit to 30 results
// Limit to 30 results
users
,
_
,
err
:=
h
.
adminService
.
ListUsers
(
c
.
Request
.
Context
(),
1
,
30
,
service
.
UserListFilters
{
Search
:
keyword
})
users
,
_
,
err
:=
h
.
adminService
.
ListUsers
(
c
.
Request
.
Context
(),
1
,
30
,
service
.
UserListFilters
{
Search
:
keyword
}
,
"email"
,
"asc"
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/admin/usage_handler_request_type_test.go
View file @
1ef3782d
...
@@ -15,11 +15,13 @@ import (
...
@@ -15,11 +15,13 @@ import (
type
adminUsageRepoCapture
struct
{
type
adminUsageRepoCapture
struct
{
service
.
UsageLogRepository
service
.
UsageLogRepository
listParams
pagination
.
PaginationParams
listFilters
usagestats
.
UsageLogFilters
listFilters
usagestats
.
UsageLogFilters
statsFilters
usagestats
.
UsageLogFilters
statsFilters
usagestats
.
UsageLogFilters
}
}
func
(
s
*
adminUsageRepoCapture
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
usagestats
.
UsageLogFilters
)
([]
service
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
s
*
adminUsageRepoCapture
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
usagestats
.
UsageLogFilters
)
([]
service
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listParams
=
params
s
.
listFilters
=
filters
s
.
listFilters
=
filters
return
[]
service
.
UsageLog
{},
&
pagination
.
PaginationResult
{
return
[]
service
.
UsageLog
{},
&
pagination
.
PaginationResult
{
Total
:
0
,
Total
:
0
,
...
...
backend/internal/handler/admin/usage_handler_sort_test.go
0 → 100644
View file @
1ef3782d
package
admin
import
(
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func
TestAdminUsageListSortParams
(
t
*
testing
.
T
)
{
repo
:=
&
adminUsageRepoCapture
{}
router
:=
newAdminUsageRequestTypeTestRouter
(
repo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/usage?sort_by=model&sort_order=ASC"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"model"
,
repo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"ASC"
,
repo
.
listParams
.
SortOrder
)
}
func
TestAdminUsageListSortDefaults
(
t
*
testing
.
T
)
{
repo
:=
&
adminUsageRepoCapture
{}
router
:=
newAdminUsageRequestTypeTestRouter
(
repo
)
req
:=
httptest
.
NewRequest
(
http
.
MethodGet
,
"/admin/usage"
,
nil
)
rec
:=
httptest
.
NewRecorder
()
router
.
ServeHTTP
(
rec
,
req
)
require
.
Equal
(
t
,
http
.
StatusOK
,
rec
.
Code
)
require
.
Equal
(
t
,
"created_at"
,
repo
.
listParams
.
SortBy
)
require
.
Equal
(
t
,
"desc"
,
repo
.
listParams
.
SortOrder
)
}
backend/internal/handler/admin/user_handler.go
View file @
1ef3782d
...
@@ -91,12 +91,14 @@ func (h *UserHandler) List(c *gin.Context) {
...
@@ -91,12 +91,14 @@ func (h *UserHandler) List(c *gin.Context) {
GroupName
:
strings
.
TrimSpace
(
c
.
Query
(
"group_name"
)),
GroupName
:
strings
.
TrimSpace
(
c
.
Query
(
"group_name"
)),
Attributes
:
parseAttributeFilters
(
c
),
Attributes
:
parseAttributeFilters
(
c
),
}
}
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
if
raw
,
ok
:=
c
.
GetQuery
(
"include_subscriptions"
);
ok
{
if
raw
,
ok
:=
c
.
GetQuery
(
"include_subscriptions"
);
ok
{
includeSubscriptions
:=
parseBoolQueryWithDefault
(
raw
,
true
)
includeSubscriptions
:=
parseBoolQueryWithDefault
(
raw
,
true
)
filters
.
IncludeSubscriptions
=
&
includeSubscriptions
filters
.
IncludeSubscriptions
=
&
includeSubscriptions
}
}
users
,
total
,
err
:=
h
.
adminService
.
ListUsers
(
c
.
Request
.
Context
(),
page
,
pageSize
,
filters
)
users
,
total
,
err
:=
h
.
adminService
.
ListUsers
(
c
.
Request
.
Context
(),
page
,
pageSize
,
filters
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
@@ -290,8 +292,10 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
...
@@ -290,8 +292,10 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
}
}
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
keys
,
total
,
err
:=
h
.
adminService
.
GetUserAPIKeys
(
c
.
Request
.
Context
(),
userID
,
page
,
pageSize
)
keys
,
total
,
err
:=
h
.
adminService
.
GetUserAPIKeys
(
c
.
Request
.
Context
(),
userID
,
page
,
pageSize
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
response
.
ErrorFrom
(
c
,
err
)
return
return
...
...
backend/internal/handler/api_key_handler.go
View file @
1ef3782d
...
@@ -72,7 +72,12 @@ func (h *APIKeyHandler) List(c *gin.Context) {
...
@@ -72,7 +72,12 @@ func (h *APIKeyHandler) List(c *gin.Context) {
}
}
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"desc"
),
}
// Parse filter parameters
// Parse filter parameters
var
filters
service
.
APIKeyListFilters
var
filters
service
.
APIKeyListFilters
...
...
Prev
1
2
3
4
5
6
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment