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 (
"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/response"
"github.com/Wei-Shaw/sub2api/internal/service"
...
...
@@ -359,7 +360,7 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
pageSize
:=
dataPageCap
var
out
[]
service
.
Proxy
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
{
return
nil
,
err
}
...
...
@@ -372,12 +373,12 @@ func (h *AccountHandler) listAllProxies(ctx context.Context) ([]service.Proxy, e
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
pageSize
:=
dataPageCap
var
out
[]
service
.
Account
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
{
return
nil
,
err
}
...
...
@@ -409,11 +410,28 @@ func (h *AccountHandler) resolveExportAccounts(ctx context.Context, ids []int64,
platform
:=
c
.
Query
(
"platform"
)
accountType
:=
c
.
Query
(
"type"
)
status
:=
c
.
Query
(
"status"
)
privacyMode
:=
strings
.
TrimSpace
(
c
.
Query
(
"privacy_mode"
))
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"name"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"asc"
)
if
len
(
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
)
{
...
...
backend/internal/handler/admin/account_data_handler_test.go
View file @
1ef3782d
...
...
@@ -172,6 +172,51 @@ func TestExportDataWithoutProxies(t *testing.T) {
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
)
{
router
,
adminSvc
:=
setupAccountDataRouter
()
...
...
backend/internal/handler/admin/account_handler.go
View file @
1ef3782d
...
...
@@ -221,6 +221,8 @@ func (h *AccountHandler) List(c *gin.Context) {
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
privacyMode
:=
strings
.
TrimSpace
(
c
.
Query
(
"privacy_mode"
))
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"name"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"asc"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
search
)
>
100
{
...
...
@@ -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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
@@ -2029,7 +2031,7 @@ func (h *AccountHandler) BatchRefreshTier(c *gin.Context) {
accounts
:=
make
([]
*
service
.
Account
,
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/admin_service_stub_test.go
View file @
1ef3782d
...
...
@@ -31,6 +31,33 @@ type stubAdminService struct {
platform
string
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
}
...
...
@@ -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
}
...
...
@@ -132,7 +159,7 @@ func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64,
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
}
...
...
@@ -140,7 +167,7 @@ func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64,
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
}
...
...
@@ -187,7 +214,16 @@ func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int
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
}
...
...
@@ -261,7 +297,13 @@ func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAcc
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
))
filtered
:=
make
([]
service
.
Proxy
,
0
,
len
(
s
.
proxies
))
for
_
,
proxy
:=
range
s
.
proxies
{
...
...
@@ -283,7 +325,7 @@ func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int,
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
}
...
...
@@ -384,7 +426,13 @@ func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*se
},
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
}
...
...
backend/internal/handler/admin/announcement_handler.go
View file @
1ef3782d
...
...
@@ -52,13 +52,17 @@ func (h *AnnouncementHandler) List(c *gin.Context) {
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
status
:=
strings
.
TrimSpace
(
c
.
Query
(
"status"
))
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
if
len
(
search
)
>
200
{
search
=
search
[
:
200
]
}
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
sortBy
,
SortOrder
:
sortOrder
,
}
items
,
paginationResult
,
err
:=
h
.
announcementService
.
List
(
...
...
@@ -227,8 +231,10 @@ func (h *AnnouncementHandler) ListReadStatus(c *gin.Context) {
page
,
pageSize
:=
response
.
ParsePagination
(
c
)
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
Page
:
page
,
PageSize
:
pageSize
,
SortBy
:
c
.
DefaultQuery
(
"sort_by"
,
"email"
),
SortOrder
:
c
.
DefaultQuery
(
"sort_order"
,
"asc"
),
}
search
:=
strings
.
TrimSpace
(
c
.
Query
(
"search"
))
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) {
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/group_handler.go
View file @
1ef3782d
...
...
@@ -162,6 +162,8 @@ func (h *GroupHandler) List(c *gin.Context) {
search
=
search
[
:
100
]
}
isExclusiveStr
:=
c
.
Query
(
"is_exclusive"
)
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"sort_order"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"asc"
)
var
isExclusive
*
bool
if
isExclusiveStr
!=
""
{
...
...
@@ -169,7 +171,7 @@ func (h *GroupHandler) List(c *gin.Context) {
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/promo_handler.go
View file @
1ef3782d
...
...
@@ -55,8 +55,10 @@ func (h *PromoHandler) List(c *gin.Context) {
}
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
,
Page
:
page
,
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
)
...
...
backend/internal/handler/admin/proxy_data.go
View file @
1ef3782d
...
...
@@ -33,11 +33,13 @@ func (h *ProxyHandler) ExportData(c *gin.Context) {
protocol
:=
c
.
Query
(
"protocol"
)
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
]
}
proxies
,
err
=
h
.
listProxiesFiltered
(
ctx
,
protocol
,
status
,
search
)
proxies
,
err
=
h
.
listProxiesFiltered
(
ctx
,
protocol
,
status
,
search
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
@@ -89,7 +91,7 @@ func (h *ProxyHandler) ImportData(c *gin.Context) {
ctx
:=
c
.
Request
.
Context
()
result
:=
DataImportResult
{}
existingProxies
,
err
:=
h
.
listProxiesFiltered
(
ctx
,
""
,
""
,
""
)
existingProxies
,
err
:=
h
.
listProxiesFiltered
(
ctx
,
""
,
""
,
""
,
"id"
,
"desc"
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
@@ -220,18 +222,33 @@ func parseProxyIDs(c *gin.Context) ([]int64, error) {
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
pageSize
:=
dataPageCap
var
out
[]
service
.
Proxy
sortBy
=
strings
.
TrimSpace
(
sortBy
)
useAccountCountSort
:=
strings
.
EqualFold
(
sortBy
,
"account_count"
)
for
{
items
,
total
,
err
:=
h
.
adminService
.
ListProxies
(
ctx
,
page
,
pageSize
,
protocol
,
status
,
search
)
if
err
!=
nil
{
return
nil
,
err
}
out
=
append
(
out
,
items
...
)
if
len
(
out
)
>=
int
(
total
)
||
len
(
items
)
==
0
{
break
if
useAccountCountSort
{
items
,
total
,
err
:=
h
.
adminService
.
ListProxiesWithAccountCount
(
ctx
,
page
,
pageSize
,
protocol
,
status
,
search
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
return
nil
,
err
}
for
i
:=
range
items
{
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
++
}
...
...
backend/internal/handler/admin/proxy_data_handler_test.go
View file @
1ef3782d
...
...
@@ -74,6 +74,10 @@ func TestProxyExportDataRespectsFilters(t *testing.T) {
require
.
Len
(
t
,
resp
.
Data
.
Proxies
,
1
)
require
.
Len
(
t
,
resp
.
Data
.
Accounts
,
0
)
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
)
{
...
...
@@ -113,6 +117,96 @@ func TestProxyExportDataWithSelectedIDs(t *testing.T) {
require
.
Len
(
t
,
resp
.
Data
.
Proxies
,
1
)
require
.
Equal
(
t
,
"https"
,
resp
.
Data
.
Proxies
[
0
]
.
Protocol
)
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
)
{
...
...
backend/internal/handler/admin/proxy_handler.go
View file @
1ef3782d
...
...
@@ -52,13 +52,15 @@ func (h *ProxyHandler) List(c *gin.Context) {
protocol
:=
c
.
Query
(
"protocol"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"id"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
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
{
response
.
ErrorFrom
(
c
,
err
)
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) {
codeType
:=
c
.
Query
(
"type"
)
status
:=
c
.
Query
(
"status"
)
search
:=
c
.
Query
(
"search"
)
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"id"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
// 标准化和验证 search 参数
search
=
strings
.
TrimSpace
(
search
)
if
len
(
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
@@ -300,9 +302,15 @@ func (h *RedeemHandler) GetStats(c *gin.Context) {
func
(
h
*
RedeemHandler
)
Export
(
c
*
gin
.
Context
)
{
codeType
:=
c
.
Query
(
"type"
)
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)
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/setting_handler.go
View file @
1ef3782d
...
...
@@ -137,6 +137,8 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
HideCcsImportButton
:
settings
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
settings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionURL
:
settings
.
PurchaseSubscriptionURL
,
TableDefaultPageSize
:
settings
.
TableDefaultPageSize
,
TablePageSizeOptions
:
settings
.
TablePageSizeOptions
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
settings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
settings
.
CustomEndpoints
),
DefaultConcurrency
:
settings
.
DefaultConcurrency
,
...
...
@@ -230,6 +232,8 @@ type UpdateSettingsRequest struct {
HideCcsImportButton
bool
`json:"hide_ccs_import_button"`
PurchaseSubscriptionEnabled
*
bool
`json:"purchase_subscription_enabled"`
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"`
CustomEndpoints
*
[]
dto
.
CustomEndpoint
`json:"custom_endpoints"`
...
...
@@ -292,6 +296,13 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if
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
.
SMTPUsername
=
strings
.
TrimSpace
(
req
.
SMTPUsername
)
req
.
SMTPPassword
=
strings
.
TrimSpace
(
req
.
SMTPPassword
)
...
...
@@ -757,6 +768,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton
:
req
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
purchaseEnabled
,
PurchaseSubscriptionURL
:
purchaseURL
,
TableDefaultPageSize
:
req
.
TableDefaultPageSize
,
TablePageSizeOptions
:
req
.
TablePageSizeOptions
,
CustomMenuItems
:
customMenuJSON
,
CustomEndpoints
:
customEndpointsJSON
,
DefaultConcurrency
:
req
.
DefaultConcurrency
,
...
...
@@ -894,6 +907,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
HideCcsImportButton
:
updatedSettings
.
HideCcsImportButton
,
PurchaseSubscriptionEnabled
:
updatedSettings
.
PurchaseSubscriptionEnabled
,
PurchaseSubscriptionURL
:
updatedSettings
.
PurchaseSubscriptionURL
,
TableDefaultPageSize
:
updatedSettings
.
TableDefaultPageSize
,
TablePageSizeOptions
:
updatedSettings
.
TablePageSizeOptions
,
CustomMenuItems
:
dto
.
ParseCustomMenuItems
(
updatedSettings
.
CustomMenuItems
),
CustomEndpoints
:
dto
.
ParseCustomEndpoints
(
updatedSettings
.
CustomEndpoints
),
DefaultConcurrency
:
updatedSettings
.
DefaultConcurrency
,
...
...
@@ -1152,6 +1167,12 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if
before
.
PurchaseSubscriptionURL
!=
after
.
PurchaseSubscriptionURL
{
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
{
changed
=
append
(
changed
,
"custom_menu_items"
)
}
...
...
@@ -1208,6 +1229,18 @@ func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
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连接请求
type
TestSMTPRequest
struct
{
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) {
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
{
UserID
:
userID
,
APIKeyID
:
apiKeyID
,
...
...
@@ -339,7 +344,7 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
}
// 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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/admin/usage_handler_request_type_test.go
View file @
1ef3782d
...
...
@@ -15,11 +15,13 @@ import (
type
adminUsageRepoCapture
struct
{
service
.
UsageLogRepository
listParams
pagination
.
PaginationParams
listFilters
usagestats
.
UsageLogFilters
statsFilters
usagestats
.
UsageLogFilters
}
func
(
s
*
adminUsageRepoCapture
)
ListWithFilters
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
filters
usagestats
.
UsageLogFilters
)
([]
service
.
UsageLog
,
*
pagination
.
PaginationResult
,
error
)
{
s
.
listParams
=
params
s
.
listFilters
=
filters
return
[]
service
.
UsageLog
{},
&
pagination
.
PaginationResult
{
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) {
GroupName
:
strings
.
TrimSpace
(
c
.
Query
(
"group_name"
)),
Attributes
:
parseAttributeFilters
(
c
),
}
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
if
raw
,
ok
:=
c
.
GetQuery
(
"include_subscriptions"
);
ok
{
includeSubscriptions
:=
parseBoolQueryWithDefault
(
raw
,
true
)
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
@@ -290,8 +292,10 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
}
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
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/handler/api_key_handler.go
View file @
1ef3782d
...
...
@@ -72,7 +72,12 @@ func (h *APIKeyHandler) List(c *gin.Context) {
}
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
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