mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-04 21:20:51 +08:00
Merge pull request #1220 from weak-fox/feat/account-privacy-mode-filter
feat: 管理员账号列表支持按 Privacy 状态筛选
This commit is contained in:
@@ -352,7 +352,7 @@ func (h *AccountHandler) listAccountsFiltered(ctx context.Context, platform, acc
|
|||||||
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, 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
accountType := c.Query("type")
|
accountType := c.Query("type")
|
||||||
status := c.Query("status")
|
status := c.Query("status")
|
||||||
search := c.Query("search")
|
search := c.Query("search")
|
||||||
|
privacyMode := strings.TrimSpace(c.Query("privacy_mode"))
|
||||||
// 标准化和验证 search 参数
|
// 标准化和验证 search 参数
|
||||||
search = strings.TrimSpace(search)
|
search = strings.TrimSpace(search)
|
||||||
if len(search) > 100 {
|
if len(search) > 100 {
|
||||||
@@ -244,7 +245,7 @@ func (h *AccountHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID)
|
accounts, total, err := h.adminService.ListAccounts(c.Request.Context(), page, pageSize, platform, accountType, status, search, groupID, privacyMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
@@ -1936,7 +1937,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, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ 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) ([]service.Account, int64, error) {
|
func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, int64, error) {
|
||||||
return s.accounts, int64(len(s.accounts)), nil
|
return s.accounts, int64(len(s.accounts)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2072,7 +2072,7 @@ func (r *stubAccountRepoForHandler) Delete(context.Context, int64) error
|
|||||||
func (r *stubAccountRepoForHandler) List(context.Context, pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *stubAccountRepoForHandler) List(context.Context, pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (r *stubAccountRepoForHandler) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *stubAccountRepoForHandler) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64, string) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (r *stubAccountRepoForHandler) ListByGroup(context.Context, int64) ([]service.Account, error) {
|
func (r *stubAccountRepoForHandler) ListByGroup(context.Context, int64) ([]service.Account, error) {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ func (r *stubAccountRepo) Delete(ctx context.Context, id int64) error
|
|||||||
func (r *stubAccountRepo) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *stubAccountRepo) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (r *stubAccountRepo) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *stubAccountRepo) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (r *stubAccountRepo) ListByGroup(ctx context.Context, groupID int64) ([]service.Account, error) {
|
func (r *stubAccountRepo) ListByGroup(ctx context.Context, groupID int64) ([]service.Account, error) {
|
||||||
|
|||||||
@@ -443,10 +443,10 @@ func (r *accountRepository) Delete(ctx context.Context, id int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *accountRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *accountRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return r.ListWithFilters(ctx, params, "", "", "", "", 0)
|
return r.ListWithFilters(ctx, params, "", "", "", "", 0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]service.Account, *pagination.PaginationResult, error) {
|
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
q := r.client.Account.Query()
|
q := r.client.Account.Query()
|
||||||
|
|
||||||
if platform != "" {
|
if platform != "" {
|
||||||
@@ -479,6 +479,20 @@ func (r *accountRepository) ListWithFilters(ctx context.Context, params paginati
|
|||||||
} else if groupID > 0 {
|
} else if groupID > 0 {
|
||||||
q = q.Where(dbaccount.HasAccountGroupsWith(dbaccountgroup.GroupIDEQ(groupID)))
|
q = q.Where(dbaccount.HasAccountGroupsWith(dbaccountgroup.GroupIDEQ(groupID)))
|
||||||
}
|
}
|
||||||
|
if privacyMode != "" {
|
||||||
|
q = q.Where(dbpredicate.Account(func(s *entsql.Selector) {
|
||||||
|
path := sqljson.Path("privacy_mode")
|
||||||
|
switch privacyMode {
|
||||||
|
case service.AccountPrivacyModeUnsetFilter:
|
||||||
|
s.Where(entsql.Or(
|
||||||
|
entsql.Not(sqljson.HasKey(dbaccount.FieldExtra, path)),
|
||||||
|
sqljson.ValueEQ(dbaccount.FieldExtra, "", path),
|
||||||
|
))
|
||||||
|
default:
|
||||||
|
s.Where(sqljson.ValueEQ(dbaccount.FieldExtra, privacyMode, path))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
total, err := q.Count(ctx)
|
total, err := q.Count(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -208,15 +208,16 @@ func (s *AccountRepoSuite) TestList() {
|
|||||||
|
|
||||||
func (s *AccountRepoSuite) TestListWithFilters() {
|
func (s *AccountRepoSuite) TestListWithFilters() {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(client *dbent.Client)
|
setup func(client *dbent.Client)
|
||||||
platform string
|
platform string
|
||||||
accType string
|
accType string
|
||||||
status string
|
status string
|
||||||
search string
|
search string
|
||||||
groupID int64
|
groupID int64
|
||||||
wantCount int
|
privacyMode string
|
||||||
validate func(accounts []service.Account)
|
wantCount int
|
||||||
|
validate func(accounts []service.Account)
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "filter_by_platform",
|
name: "filter_by_platform",
|
||||||
@@ -281,6 +282,32 @@ func (s *AccountRepoSuite) TestListWithFilters() {
|
|||||||
s.Require().Empty(accounts[0].GroupIDs)
|
s.Require().Empty(accounts[0].GroupIDs)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "filter_by_privacy_mode",
|
||||||
|
setup: func(client *dbent.Client) {
|
||||||
|
mustCreateAccount(s.T(), client, &service.Account{Name: "privacy-ok", Extra: map[string]any{"privacy_mode": service.PrivacyModeTrainingOff}})
|
||||||
|
mustCreateAccount(s.T(), client, &service.Account{Name: "privacy-fail", Extra: map[string]any{"privacy_mode": service.PrivacyModeFailed}})
|
||||||
|
},
|
||||||
|
privacyMode: service.PrivacyModeTrainingOff,
|
||||||
|
wantCount: 1,
|
||||||
|
validate: func(accounts []service.Account) {
|
||||||
|
s.Require().Equal("privacy-ok", accounts[0].Name)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "filter_by_privacy_mode_unset",
|
||||||
|
setup: func(client *dbent.Client) {
|
||||||
|
mustCreateAccount(s.T(), client, &service.Account{Name: "privacy-unset", Extra: nil})
|
||||||
|
mustCreateAccount(s.T(), client, &service.Account{Name: "privacy-empty", Extra: map[string]any{"privacy_mode": ""}})
|
||||||
|
mustCreateAccount(s.T(), client, &service.Account{Name: "privacy-set", Extra: map[string]any{"privacy_mode": service.PrivacyModeTrainingOff}})
|
||||||
|
},
|
||||||
|
privacyMode: service.AccountPrivacyModeUnsetFilter,
|
||||||
|
wantCount: 2,
|
||||||
|
validate: func(accounts []service.Account) {
|
||||||
|
names := []string{accounts[0].Name, accounts[1].Name}
|
||||||
|
s.ElementsMatch([]string{"privacy-unset", "privacy-empty"}, names)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -293,7 +320,7 @@ func (s *AccountRepoSuite) TestListWithFilters() {
|
|||||||
|
|
||||||
tt.setup(client)
|
tt.setup(client)
|
||||||
|
|
||||||
accounts, _, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, tt.groupID)
|
accounts, _, err := repo.ListWithFilters(ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, tt.platform, tt.accType, tt.status, tt.search, tt.groupID, tt.privacyMode)
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
s.Require().Len(accounts, tt.wantCount)
|
s.Require().Len(accounts, tt.wantCount)
|
||||||
if tt.validate != nil {
|
if tt.validate != nil {
|
||||||
@@ -360,7 +387,7 @@ func (s *AccountRepoSuite) TestPreload_And_VirtualFields() {
|
|||||||
s.Require().Len(got.Groups, 1, "expected Groups to be populated")
|
s.Require().Len(got.Groups, 1, "expected Groups to be populated")
|
||||||
s.Require().Equal(group.ID, got.Groups[0].ID)
|
s.Require().Equal(group.ID, got.Groups[0].ID)
|
||||||
|
|
||||||
accounts, page, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", "", "", "acc", 0)
|
accounts, page, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", "", "", "acc", 0, "")
|
||||||
s.Require().NoError(err, "ListWithFilters")
|
s.Require().NoError(err, "ListWithFilters")
|
||||||
s.Require().Equal(int64(1), page.Total)
|
s.Require().Equal(int64(1), page.Total)
|
||||||
s.Require().Len(accounts, 1)
|
s.Require().Len(accounts, 1)
|
||||||
|
|||||||
@@ -990,7 +990,7 @@ func (s *stubAccountRepo) List(ctx context.Context, params pagination.Pagination
|
|||||||
return nil, nil, errors.New("not implemented")
|
return nil, nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubAccountRepo) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]service.Account, *pagination.PaginationResult, error) {
|
func (s *stubAccountRepo) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, errors.New("not implemented")
|
return nil, nil, errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const AccountListGroupUngrouped int64 = -1
|
const AccountListGroupUngrouped int64 = -1
|
||||||
|
const AccountPrivacyModeUnsetFilter = "__unset__"
|
||||||
|
|
||||||
type AccountRepository interface {
|
type AccountRepository interface {
|
||||||
Create(ctx context.Context, account *Account) error
|
Create(ctx context.Context, account *Account) error
|
||||||
@@ -37,7 +38,7 @@ type AccountRepository interface {
|
|||||||
Delete(ctx context.Context, id int64) error
|
Delete(ctx context.Context, id int64) error
|
||||||
|
|
||||||
List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error)
|
List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error)
|
||||||
ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error)
|
ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error)
|
||||||
ListByGroup(ctx context.Context, groupID int64) ([]Account, error)
|
ListByGroup(ctx context.Context, groupID int64) ([]Account, error)
|
||||||
ListActive(ctx context.Context) ([]Account, error)
|
ListActive(ctx context.Context) ([]Account, error)
|
||||||
ListByPlatform(ctx context.Context, platform string) ([]Account, error)
|
ListByPlatform(ctx context.Context, platform string) ([]Account, error)
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (s *accountRepoStub) List(ctx context.Context, params pagination.Pagination
|
|||||||
panic("unexpected List call")
|
panic("unexpected List call")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *accountRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error) {
|
func (s *accountRepoStub) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
panic("unexpected ListWithFilters call")
|
panic("unexpected ListWithFilters call")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type AdminService interface {
|
|||||||
ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*ReplaceUserGroupResult, error)
|
ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*ReplaceUserGroupResult, error)
|
||||||
|
|
||||||
// Account management
|
// Account management
|
||||||
ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64) ([]Account, int64, error)
|
ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error)
|
||||||
GetAccount(ctx context.Context, id int64) (*Account, error)
|
GetAccount(ctx context.Context, id int64) (*Account, error)
|
||||||
GetAccountsByIDs(ctx context.Context, ids []int64) ([]*Account, error)
|
GetAccountsByIDs(ctx context.Context, ids []int64) ([]*Account, error)
|
||||||
CreateAccount(ctx context.Context, input *CreateAccountInput) (*Account, error)
|
CreateAccount(ctx context.Context, input *CreateAccountInput) (*Account, error)
|
||||||
@@ -1451,9 +1451,9 @@ func (s *adminServiceImpl) ReplaceUserGroup(ctx context.Context, userID, oldGrou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Account management implementations
|
// Account management implementations
|
||||||
func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64) ([]Account, int64, error) {
|
func (s *adminServiceImpl) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, int64, error) {
|
||||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
||||||
accounts, result, err := s.accountRepo.ListWithFilters(ctx, params, platform, accountType, status, search, groupID)
|
accounts, result, err := s.accountRepo.ListWithFilters(ctx, params, platform, accountType, status, search, groupID, privacyMode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,18 +19,20 @@ type accountRepoStubForAdminList struct {
|
|||||||
listWithFiltersType string
|
listWithFiltersType string
|
||||||
listWithFiltersStatus string
|
listWithFiltersStatus string
|
||||||
listWithFiltersSearch string
|
listWithFiltersSearch string
|
||||||
|
listWithFiltersPrivacy string
|
||||||
listWithFiltersAccounts []Account
|
listWithFiltersAccounts []Account
|
||||||
listWithFiltersResult *pagination.PaginationResult
|
listWithFiltersResult *pagination.PaginationResult
|
||||||
listWithFiltersErr error
|
listWithFiltersErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *accountRepoStubForAdminList) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error) {
|
func (s *accountRepoStubForAdminList) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
s.listWithFiltersCalls++
|
s.listWithFiltersCalls++
|
||||||
s.listWithFiltersParams = params
|
s.listWithFiltersParams = params
|
||||||
s.listWithFiltersPlatform = platform
|
s.listWithFiltersPlatform = platform
|
||||||
s.listWithFiltersType = accountType
|
s.listWithFiltersType = accountType
|
||||||
s.listWithFiltersStatus = status
|
s.listWithFiltersStatus = status
|
||||||
s.listWithFiltersSearch = search
|
s.listWithFiltersSearch = search
|
||||||
|
s.listWithFiltersPrivacy = privacyMode
|
||||||
|
|
||||||
if s.listWithFiltersErr != nil {
|
if s.listWithFiltersErr != nil {
|
||||||
return nil, nil, s.listWithFiltersErr
|
return nil, nil, s.listWithFiltersErr
|
||||||
@@ -168,7 +170,7 @@ func TestAdminService_ListAccounts_WithSearch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
svc := &adminServiceImpl{accountRepo: repo}
|
svc := &adminServiceImpl{accountRepo: repo}
|
||||||
|
|
||||||
accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0)
|
accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformGemini, AccountTypeOAuth, StatusActive, "acc", 0, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, int64(10), total)
|
require.Equal(t, int64(10), total)
|
||||||
require.Equal(t, []Account{{ID: 1, Name: "acc"}}, accounts)
|
require.Equal(t, []Account{{ID: 1, Name: "acc"}}, accounts)
|
||||||
@@ -182,6 +184,22 @@ func TestAdminService_ListAccounts_WithSearch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdminService_ListAccounts_WithPrivacyMode(t *testing.T) {
|
||||||
|
t.Run("privacy_mode 参数正常传递到 repository 层", func(t *testing.T) {
|
||||||
|
repo := &accountRepoStubForAdminList{
|
||||||
|
listWithFiltersAccounts: []Account{{ID: 2, Name: "acc2"}},
|
||||||
|
listWithFiltersResult: &pagination.PaginationResult{Total: 1},
|
||||||
|
}
|
||||||
|
svc := &adminServiceImpl{accountRepo: repo}
|
||||||
|
|
||||||
|
accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, StatusActive, "acc2", 0, PrivacyModeCFBlocked)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, int64(1), total)
|
||||||
|
require.Equal(t, []Account{{ID: 2, Name: "acc2"}}, accounts)
|
||||||
|
require.Equal(t, PrivacyModeCFBlocked, repo.listWithFiltersPrivacy)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdminService_ListProxies_WithSearch(t *testing.T) {
|
func TestAdminService_ListProxies_WithSearch(t *testing.T) {
|
||||||
t.Run("search 参数正常传递到 repository 层", func(t *testing.T) {
|
t.Run("search 参数正常传递到 repository 层", func(t *testing.T) {
|
||||||
repo := &proxyRepoStubForAdminList{
|
repo := &proxyRepoStubForAdminList{
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (m *mockAccountRepoForPlatform) Delete(ctx context.Context, id int64) error
|
|||||||
func (m *mockAccountRepoForPlatform) List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
func (m *mockAccountRepoForPlatform) List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockAccountRepoForPlatform) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error) {
|
func (m *mockAccountRepoForPlatform) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockAccountRepoForPlatform) ListByGroup(ctx context.Context, groupID int64) ([]Account, error) {
|
func (m *mockAccountRepoForPlatform) ListByGroup(ctx context.Context, groupID int64) ([]Account, error) {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func (m *mockAccountRepoForGemini) Delete(ctx context.Context, id int64) error
|
|||||||
func (m *mockAccountRepoForGemini) List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
func (m *mockAccountRepoForGemini) List(ctx context.Context, params pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockAccountRepoForGemini) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error) {
|
func (m *mockAccountRepoForGemini) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
func (m *mockAccountRepoForGemini) ListByGroup(ctx context.Context, groupID int64) ([]Account, error) {
|
func (m *mockAccountRepoForGemini) ListByGroup(ctx context.Context, groupID int64) ([]Account, error) {
|
||||||
|
|||||||
@@ -73,12 +73,13 @@ func (r *openAICodexExtraListRepo) SetRateLimited(_ context.Context, _ int64, re
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *openAICodexExtraListRepo) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64) ([]Account, *pagination.PaginationResult, error) {
|
func (r *openAICodexExtraListRepo) ListWithFilters(_ context.Context, params pagination.PaginationParams, platform, accountType, status, search string, groupID int64, privacyMode string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
_ = platform
|
_ = platform
|
||||||
_ = accountType
|
_ = accountType
|
||||||
_ = status
|
_ = status
|
||||||
_ = search
|
_ = search
|
||||||
_ = groupID
|
_ = groupID
|
||||||
|
_ = privacyMode
|
||||||
return r.accounts, &pagination.PaginationResult{Total: int64(len(r.accounts)), Page: params.Page, PageSize: params.PageSize}, nil
|
return r.accounts, &pagination.PaginationResult{Total: int64(len(r.accounts)), Page: params.Page, PageSize: params.PageSize}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +492,7 @@ func TestAdminService_ListAccounts_ExhaustedCodexExtraReturnsRateLimitedAccount(
|
|||||||
}
|
}
|
||||||
svc := &adminServiceImpl{accountRepo: repo}
|
svc := &adminServiceImpl{accountRepo: repo}
|
||||||
|
|
||||||
accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0)
|
accounts, total, err := svc.ListAccounts(context.Background(), 1, 20, PlatformOpenAI, AccountTypeOAuth, "", "", 0, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, int64(1), total)
|
require.Equal(t, int64(1), total)
|
||||||
require.Len(t, accounts, 1)
|
require.Len(t, accounts, 1)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func (s *OpsService) listAllAccountsForOps(ctx context.Context, platformFilter s
|
|||||||
accounts, pageInfo, err := s.accountRepo.ListWithFilters(ctx, pagination.PaginationParams{
|
accounts, pageInfo, err := s.accountRepo.ListWithFilters(ctx, pagination.PaginationParams{
|
||||||
Page: page,
|
Page: page,
|
||||||
PageSize: opsAccountsPageSize,
|
PageSize: opsAccountsPageSize,
|
||||||
}, platformFilter, "", "", "", 0)
|
}, platformFilter, "", "", "", 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (m *sessionWindowMockRepo) Delete(context.Context, int64) error { panic(
|
|||||||
func (m *sessionWindowMockRepo) List(context.Context, pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
func (m *sessionWindowMockRepo) List(context.Context, pagination.PaginationParams) ([]Account, *pagination.PaginationResult, error) {
|
||||||
panic("unexpected")
|
panic("unexpected")
|
||||||
}
|
}
|
||||||
func (m *sessionWindowMockRepo) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64) ([]Account, *pagination.PaginationResult, error) {
|
func (m *sessionWindowMockRepo) ListWithFilters(context.Context, pagination.PaginationParams, string, string, string, string, int64, string) ([]Account, *pagination.PaginationResult, error) {
|
||||||
panic("unexpected")
|
panic("unexpected")
|
||||||
}
|
}
|
||||||
func (m *sessionWindowMockRepo) ListByGroup(context.Context, int64) ([]Account, error) {
|
func (m *sessionWindowMockRepo) ListByGroup(context.Context, int64) ([]Account, error) {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ export async function list(
|
|||||||
status?: string
|
status?: string
|
||||||
group?: string
|
group?: string
|
||||||
search?: string
|
search?: string
|
||||||
|
privacy_mode?: string
|
||||||
lite?: string
|
lite?: string
|
||||||
},
|
},
|
||||||
options?: {
|
options?: {
|
||||||
@@ -68,6 +69,7 @@ export async function listWithEtag(
|
|||||||
status?: string
|
status?: string
|
||||||
group?: string
|
group?: string
|
||||||
search?: string
|
search?: string
|
||||||
|
privacy_mode?: string
|
||||||
lite?: string
|
lite?: string
|
||||||
},
|
},
|
||||||
options?: {
|
options?: {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<Select :model-value="filters.platform" class="w-40" :options="pOpts" @update:model-value="updatePlatform" @change="$emit('change')" />
|
<Select :model-value="filters.platform" class="w-40" :options="pOpts" @update:model-value="updatePlatform" @change="$emit('change')" />
|
||||||
<Select :model-value="filters.type" class="w-40" :options="tOpts" @update:model-value="updateType" @change="$emit('change')" />
|
<Select :model-value="filters.type" class="w-40" :options="tOpts" @update:model-value="updateType" @change="$emit('change')" />
|
||||||
<Select :model-value="filters.status" class="w-40" :options="sOpts" @update:model-value="updateStatus" @change="$emit('change')" />
|
<Select :model-value="filters.status" class="w-40" :options="sOpts" @update:model-value="updateStatus" @change="$emit('change')" />
|
||||||
|
<Select :model-value="filters.privacy_mode" class="w-40" :options="privacyOpts" @update:model-value="updatePrivacyMode" @change="$emit('change')" />
|
||||||
<Select :model-value="filters.group" class="w-40" :options="gOpts" @update:model-value="updateGroup" @change="$emit('change')" />
|
<Select :model-value="filters.group" class="w-40" :options="gOpts" @update:model-value="updateGroup" @change="$emit('change')" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,10 +23,18 @@ const emit = defineEmits(['update:searchQuery', 'update:filters', 'change']); co
|
|||||||
const updatePlatform = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, platform: value }) }
|
const updatePlatform = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, platform: value }) }
|
||||||
const updateType = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, type: value }) }
|
const updateType = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, type: value }) }
|
||||||
const updateStatus = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, status: value }) }
|
const updateStatus = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, status: value }) }
|
||||||
|
const updatePrivacyMode = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, privacy_mode: value }) }
|
||||||
const updateGroup = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, group: value }) }
|
const updateGroup = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, group: value }) }
|
||||||
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }, { value: 'sora', label: 'Sora' }])
|
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }, { value: 'sora', label: 'Sora' }])
|
||||||
const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }])
|
const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }])
|
||||||
const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }])
|
const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }])
|
||||||
|
const privacyOpts = computed(() => [
|
||||||
|
{ value: '', label: t('admin.accounts.allPrivacyModes') },
|
||||||
|
{ value: '__unset__', label: t('admin.accounts.privacyUnset') },
|
||||||
|
{ value: 'training_off', label: 'Privacy' },
|
||||||
|
{ value: 'training_set_cf_blocked', label: 'CF' },
|
||||||
|
{ value: 'training_set_failed', label: 'Fail' }
|
||||||
|
])
|
||||||
const gOpts = computed(() => [
|
const gOpts = computed(() => [
|
||||||
{ value: '', label: t('admin.accounts.allGroups') },
|
{ value: '', label: t('admin.accounts.allGroups') },
|
||||||
{ value: 'ungrouped', label: t('admin.accounts.ungroupedGroup') },
|
{ value: 'ungrouped', label: t('admin.accounts.ungroupedGroup') },
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
|
||||||
|
import AccountTableFilters from '../AccountTableFilters.vue'
|
||||||
|
|
||||||
|
vi.mock('vue-i18n', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useI18n: () => ({
|
||||||
|
t: (key: string) => key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('AccountTableFilters', () => {
|
||||||
|
it('renders privacy mode options and emits privacy_mode updates', async () => {
|
||||||
|
const wrapper = mount(AccountTableFilters, {
|
||||||
|
props: {
|
||||||
|
searchQuery: '',
|
||||||
|
filters: {
|
||||||
|
platform: '',
|
||||||
|
type: '',
|
||||||
|
status: '',
|
||||||
|
group: '',
|
||||||
|
privacy_mode: ''
|
||||||
|
},
|
||||||
|
groups: []
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
SearchInput: {
|
||||||
|
template: '<div />'
|
||||||
|
},
|
||||||
|
Select: {
|
||||||
|
props: ['modelValue', 'options'],
|
||||||
|
emits: ['update:modelValue', 'change'],
|
||||||
|
template: '<div class="select-stub" :data-options="JSON.stringify(options)" />'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const selects = wrapper.findAll('.select-stub')
|
||||||
|
expect(selects).toHaveLength(5)
|
||||||
|
|
||||||
|
const privacyOptions = JSON.parse(selects[3].attributes('data-options'))
|
||||||
|
expect(privacyOptions).toEqual([
|
||||||
|
{ value: '', label: 'admin.accounts.allPrivacyModes' },
|
||||||
|
{ value: '__unset__', label: 'admin.accounts.privacyUnset' },
|
||||||
|
{ value: 'training_off', label: 'Privacy' },
|
||||||
|
{ value: 'training_set_cf_blocked', label: 'CF' },
|
||||||
|
{ value: 'training_set_failed', label: 'Fail' }
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1979,6 +1979,8 @@ export default {
|
|||||||
expiresAt: 'Expires At',
|
expiresAt: 'Expires At',
|
||||||
actions: 'Actions'
|
actions: 'Actions'
|
||||||
},
|
},
|
||||||
|
allPrivacyModes: 'All Privacy States',
|
||||||
|
privacyUnset: 'Unset',
|
||||||
privacyTrainingOff: 'Training data sharing disabled',
|
privacyTrainingOff: 'Training data sharing disabled',
|
||||||
privacyCfBlocked: 'Blocked by Cloudflare, training may still be on',
|
privacyCfBlocked: 'Blocked by Cloudflare, training may still be on',
|
||||||
privacyFailed: 'Failed to disable training',
|
privacyFailed: 'Failed to disable training',
|
||||||
|
|||||||
@@ -2017,6 +2017,8 @@ export default {
|
|||||||
expiresAt: '过期时间',
|
expiresAt: '过期时间',
|
||||||
actions: '操作'
|
actions: '操作'
|
||||||
},
|
},
|
||||||
|
allPrivacyModes: '全部Privacy状态',
|
||||||
|
privacyUnset: '未设置',
|
||||||
privacyTrainingOff: '已关闭训练数据共享',
|
privacyTrainingOff: '已关闭训练数据共享',
|
||||||
privacyCfBlocked: '被 Cloudflare 拦截,训练可能仍开启',
|
privacyCfBlocked: '被 Cloudflare 拦截,训练可能仍开启',
|
||||||
privacyFailed: '关闭训练数据共享失败',
|
privacyFailed: '关闭训练数据共享失败',
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ const {
|
|||||||
handlePageSizeChange: baseHandlePageSizeChange
|
handlePageSizeChange: baseHandlePageSizeChange
|
||||||
} = useTableLoader<Account, any>({
|
} = useTableLoader<Account, any>({
|
||||||
fetchFn: adminAPI.accounts.list,
|
fetchFn: adminAPI.accounts.list,
|
||||||
initialParams: { platform: '', type: '', status: '', group: '', search: '' }
|
initialParams: { platform: '', type: '', status: '', privacy_mode: '', group: '', search: '' }
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -758,6 +758,7 @@ const refreshAccountsIncrementally = async () => {
|
|||||||
platform?: string
|
platform?: string
|
||||||
type?: string
|
type?: string
|
||||||
status?: string
|
status?: string
|
||||||
|
privacy_mode?: string
|
||||||
group?: string
|
group?: string
|
||||||
search?: string
|
search?: string
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user