| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 |
- package admin
- import (
- "context"
- "strings"
- "sync"
- "time"
- "github.com/Wei-Shaw/sub2api/internal/service"
- )
- type stubAdminService struct {
- users []service.User
- apiKeys []service.APIKey
- groups []service.Group
- accounts []service.Account
- accountSchedulerScoreFilterAccounts []service.Account
- openAISchedulerScorePoolAccounts []service.Account
- schedulerScoreFilterCalls int
- openAISchedulerScorePoolCalls int
- proxies []service.Proxy
- proxyCounts []service.ProxyWithAccountCount
- redeems []service.RedeemCode
- boundAuthIdentity *service.AdminBindAuthIdentityInput
- boundAuthIdentityFor int64
- createdAccounts []*service.CreateAccountInput
- createdGroups []*service.CreateGroupInput
- updatedGroups []*service.UpdateGroupInput
- deletedGroupIDs []int64
- guardedDeletedGroupIDs []int64
- deleteGroupIfEmptyErr error
- advancedGroupOperationCalls int
- lastListGroupsIsExclusive *bool
- createdProxies []*service.CreateProxyInput
- updatedProxyIDs []int64
- updatedProxies []*service.UpdateProxyInput
- testedProxyIDs []int64
- getUserErr error
- createAccountErr error
- createSparkShadowErr error
- updateAccountErr error
- lastUpdateAccountInput *service.UpdateAccountInput
- bulkUpdateAccountErr error
- lastBulkUpdateAccountInput *service.BulkUpdateAccountsInput
- getAccountResult *service.Account
- updateAccountCalls int
- updateAccountExtraCalls int
- checkMixedErr error
- lastMixedCheck struct {
- accountID int64
- platform string
- groupIDs []int64
- }
- lastListAccounts struct {
- platform string
- accountType string
- status string
- search string
- groupID int64
- privacyMode string
- sortBy string
- sortOrder string
- calls int
- }
- lastListUsers struct {
- page int
- pageSize int
- filters service.UserListFilters
- 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
- }
- func newStubAdminService() *stubAdminService {
- now := time.Now().UTC()
- user := service.User{
- ID: 1,
- Email: "user@example.com",
- Role: service.RoleUser,
- Status: service.StatusActive,
- CreatedAt: now,
- UpdatedAt: now,
- }
- apiKey := service.APIKey{
- ID: 10,
- UserID: user.ID,
- Key: "sk-test",
- Name: "test",
- Status: service.StatusActive,
- CreatedAt: now,
- UpdatedAt: now,
- }
- group := service.Group{
- ID: 2,
- Name: "group",
- Platform: service.PlatformAnthropic,
- Status: service.StatusActive,
- CreatedAt: now,
- UpdatedAt: now,
- }
- account := service.Account{
- ID: 3,
- Name: "account",
- Platform: service.PlatformAnthropic,
- Type: service.AccountTypeOAuth,
- Status: service.StatusActive,
- CreatedAt: now,
- UpdatedAt: now,
- }
- proxy := service.Proxy{
- ID: 4,
- Name: "proxy",
- Protocol: "http",
- Host: "127.0.0.1",
- Port: 8080,
- Status: service.StatusActive,
- CreatedAt: now,
- UpdatedAt: now,
- }
- redeem := service.RedeemCode{
- ID: 5,
- Code: "R-TEST",
- Type: service.RedeemTypeBalance,
- Value: 10,
- Status: service.StatusUnused,
- CreatedAt: now,
- }
- return &stubAdminService{
- users: []service.User{user},
- apiKeys: []service.APIKey{apiKey},
- groups: []service.Group{group},
- accounts: []service.Account{account},
- proxies: []service.Proxy{proxy},
- proxyCounts: []service.ProxyWithAccountCount{{Proxy: proxy, AccountCount: 1}},
- redeems: []service.RedeemCode{redeem},
- }
- }
- func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters, sortBy, sortOrder string) ([]service.User, int64, error) {
- s.lastListUsers.page = page
- s.lastListUsers.pageSize = pageSize
- s.lastListUsers.filters = filters
- s.lastListUsers.sortBy = sortBy
- s.lastListUsers.sortOrder = sortOrder
- s.lastListUsers.calls++
- return s.users, int64(len(s.users)), nil
- }
- func (s *stubAdminService) GetUser(ctx context.Context, id int64) (*service.User, error) {
- if s.getUserErr != nil {
- return nil, s.getUserErr
- }
- for i := range s.users {
- if s.users[i].ID == id {
- return &s.users[i], nil
- }
- }
- user := service.User{ID: id, Email: "user@example.com", Status: service.StatusActive}
- return &user, nil
- }
- func (s *stubAdminService) GetUserIncludeDeleted(ctx context.Context, id int64) (*service.User, error) {
- return s.GetUser(ctx, id)
- }
- func (s *stubAdminService) CreateUser(ctx context.Context, input *service.CreateUserInput) (*service.User, error) {
- user := service.User{ID: 100, Email: input.Email, Status: service.StatusActive}
- return &user, nil
- }
- func (s *stubAdminService) UpdateUser(ctx context.Context, id int64, input *service.UpdateUserInput) (*service.User, error) {
- user := service.User{ID: id, Email: "updated@example.com", Status: service.StatusActive}
- return &user, nil
- }
- func (s *stubAdminService) DeleteUser(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*service.User, error) {
- user := service.User{ID: userID, Balance: balance, Status: service.StatusActive}
- return &user, nil
- }
- func (s *stubAdminService) BatchUpdateConcurrency(ctx context.Context, userIDs []int64, value int, mode string) (int, error) {
- return len(userIDs), nil
- }
- func (s *stubAdminService) BatchUpdateLimits(ctx context.Context, userIDs []int64, concurrency, rpmLimit *int) (int, error) {
- return len(userIDs), nil
- }
- 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
- }
- func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error) {
- return map[string]any{"user_id": userID}, nil
- }
- func (s *stubAdminService) GetUserRPMStatus(ctx context.Context, userID int64) (*service.UserRPMStatus, error) {
- user, err := s.GetUser(ctx, userID)
- if err != nil {
- return nil, err
- }
- return &service.UserRPMStatus{
- UserRPMUsed: 0,
- UserRPMLimit: user.RPMLimit,
- }, nil
- }
- func (s *stubAdminService) BindUserAuthIdentity(ctx context.Context, userID int64, input service.AdminBindAuthIdentityInput) (*service.AdminBoundAuthIdentity, error) {
- s.boundAuthIdentityFor = userID
- copied := input
- if input.Metadata != nil {
- copied.Metadata = map[string]any{}
- for key, value := range input.Metadata {
- copied.Metadata[key] = value
- }
- }
- if input.Channel != nil {
- channel := *input.Channel
- if input.Channel.Metadata != nil {
- channel.Metadata = map[string]any{}
- for key, value := range input.Channel.Metadata {
- channel.Metadata[key] = value
- }
- }
- copied.Channel = &channel
- }
- s.boundAuthIdentity = &copied
- now := time.Now().UTC()
- result := &service.AdminBoundAuthIdentity{
- UserID: userID,
- ProviderType: input.ProviderType,
- ProviderKey: input.ProviderKey,
- ProviderSubject: input.ProviderSubject,
- VerifiedAt: &now,
- Issuer: input.Issuer,
- Metadata: input.Metadata,
- CreatedAt: now,
- UpdatedAt: now,
- }
- if input.Channel != nil {
- result.Channel = &service.AdminBoundAuthIdentityChannel{
- Channel: input.Channel.Channel,
- ChannelAppID: input.Channel.ChannelAppID,
- ChannelSubject: input.Channel.ChannelSubject,
- Metadata: input.Channel.Metadata,
- CreatedAt: now,
- UpdatedAt: now,
- }
- }
- return result, nil
- }
- func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
- s.lastListGroupsIsExclusive = isExclusive
- return s.groups, int64(len(s.groups)), nil
- }
- func (s *stubAdminService) GetAllGroups(ctx context.Context) ([]service.Group, error) {
- return s.groups, nil
- }
- func (s *stubAdminService) GetAllGroupsByPlatform(ctx context.Context, platform string) ([]service.Group, error) {
- return s.groups, nil
- }
- func (s *stubAdminService) GetAllGroupsIncludingInactive(ctx context.Context) ([]service.Group, error) {
- return s.groups, nil
- }
- func (s *stubAdminService) GetGroup(ctx context.Context, id int64) (*service.Group, error) {
- group := service.Group{ID: id, Name: "group", Status: service.StatusActive}
- return &group, nil
- }
- func (s *stubAdminService) GetGroupModelsListCandidates(ctx context.Context, id int64, platform string) ([]string, error) {
- if platform == service.PlatformOpenAI {
- return []string{"gpt-5.5", "gpt-5.4"}, nil
- }
- return []string{"claude-sonnet-4-6"}, nil
- }
- func (s *stubAdminService) ListCompositeRoutes(ctx context.Context, groupID int64) ([]service.CompositeModelRoute, error) {
- s.advancedGroupOperationCalls++
- return []service.CompositeModelRoute{
- {
- ID: 1,
- GroupID: groupID,
- PublicModel: "openrouter/gpt-5",
- MatchType: service.CompositeRouteMatchExact,
- TargetPlatform: service.PlatformOpenAI,
- UpstreamModel: "gpt-5",
- Endpoint: service.CompositeRouteEndpointAny,
- Priority: 100,
- Enabled: true,
- },
- }, nil
- }
- func (s *stubAdminService) CreateCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
- s.advancedGroupOperationCalls++
- return &service.CompositeModelRoute{
- ID: 1,
- GroupID: groupID,
- PublicModel: input.PublicModel,
- MatchType: input.MatchType,
- TargetPlatform: input.TargetPlatform,
- UpstreamModel: input.UpstreamModel,
- Endpoint: input.Endpoint,
- Priority: input.Priority,
- Enabled: input.Enabled,
- Notes: input.Notes,
- }, nil
- }
- func (s *stubAdminService) UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
- s.advancedGroupOperationCalls++
- return &service.CompositeModelRoute{
- ID: routeID,
- GroupID: groupID,
- PublicModel: input.PublicModel,
- MatchType: input.MatchType,
- TargetPlatform: input.TargetPlatform,
- UpstreamModel: input.UpstreamModel,
- Endpoint: input.Endpoint,
- Priority: input.Priority,
- Enabled: input.Enabled,
- Notes: input.Notes,
- }, nil
- }
- func (s *stubAdminService) DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error {
- s.advancedGroupOperationCalls++
- return nil
- }
- func (s *stubAdminService) PreviewCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRoutePreviewRequest) (*service.CompositeRouteDecision, error) {
- s.advancedGroupOperationCalls++
- decision, err := service.NewCompositeRouteResolver(nil).Resolve(ctx, groupID, input.Model, input.Endpoint)
- if err != nil {
- return nil, err
- }
- return &decision, nil
- }
- func (s *stubAdminService) CreateGroup(ctx context.Context, input *service.CreateGroupInput) (*service.Group, error) {
- s.createdGroups = append(s.createdGroups, input)
- group := service.Group{ID: 200, Name: input.Name, Status: service.StatusActive}
- return &group, nil
- }
- func (s *stubAdminService) DuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*service.Group, error) {
- s.advancedGroupOperationCalls++
- group := service.Group{ID: 201, Name: "group (Copy)", Status: "inactive"}
- return &group, nil
- }
- func (s *stubAdminService) RecoverDuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*service.Group, error) {
- return nil, nil
- }
- func (s *stubAdminService) UpdateGroup(ctx context.Context, id int64, input *service.UpdateGroupInput) (*service.Group, error) {
- s.updatedGroups = append(s.updatedGroups, input)
- group := service.Group{ID: id, Name: input.Name, Status: service.StatusActive}
- return &group, nil
- }
- func (s *stubAdminService) DeleteGroup(ctx context.Context, id int64) error {
- s.deletedGroupIDs = append(s.deletedGroupIDs, id)
- return nil
- }
- func (s *stubAdminService) DeleteGroupIfEmpty(ctx context.Context, id int64) error {
- s.guardedDeletedGroupIDs = append(s.guardedDeletedGroupIDs, id)
- return s.deleteGroupIfEmptyErr
- }
- func (s *stubAdminService) GetGroupAPIKeys(ctx context.Context, groupID int64, page, pageSize int) ([]service.APIKey, int64, error) {
- return s.apiKeys, int64(len(s.apiKeys)), nil
- }
- func (s *stubAdminService) GetGroupRateMultipliers(_ context.Context, _ int64) ([]service.UserGroupRateEntry, error) {
- return nil, nil
- }
- func (s *stubAdminService) ClearGroupRateMultipliers(_ context.Context, _ int64) error {
- s.advancedGroupOperationCalls++
- return nil
- }
- func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int64, _ []service.GroupRateMultiplierInput) error {
- s.advancedGroupOperationCalls++
- return nil
- }
- func (s *stubAdminService) ClearGroupRPMOverrides(_ context.Context, _ int64) error {
- return nil
- }
- func (s *stubAdminService) BatchSetGroupRPMOverrides(_ context.Context, _ int64, _ []service.GroupRPMOverrideInput) error {
- s.advancedGroupOperationCalls++
- return nil
- }
- 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++
- accounts := s.accounts
- total := len(accounts)
- if page < 1 {
- page = 1
- }
- if pageSize < 1 {
- pageSize = total
- }
- start := (page - 1) * pageSize
- if start >= total {
- return []service.Account{}, int64(total), nil
- }
- end := start + pageSize
- if end > total {
- end = total
- }
- return accounts[start:end], int64(total), nil
- }
- func (s *stubAdminService) ListAccountsForSchedulerScoreFilter(_ context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, error) {
- s.schedulerScoreFilterCalls++
- if s.accountSchedulerScoreFilterAccounts != nil {
- return s.accountSchedulerScoreFilterAccounts, nil
- }
- return s.accounts, nil
- }
- func (s *stubAdminService) ListOpenAISchedulableAccountsForSchedulerScore(_ context.Context, groupID *int64) ([]service.Account, error) {
- s.openAISchedulerScorePoolCalls++
- accounts := s.openAISchedulerScorePoolAccounts
- if accounts == nil {
- accounts = s.accounts
- }
- out := make([]service.Account, 0, len(accounts))
- for _, account := range accounts {
- if account.Platform != service.PlatformOpenAI || !account.IsSchedulable() {
- continue
- }
- if groupID == nil {
- if len(account.AccountGroups) == 0 && len(account.GroupIDs) == 0 {
- out = append(out, account)
- }
- continue
- }
- for _, accountGroup := range account.AccountGroups {
- if accountGroup.GroupID == *groupID {
- out = append(out, account)
- break
- }
- }
- }
- return out, nil
- }
- func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
- if s.getAccountResult != nil {
- return s.getAccountResult, nil
- }
- account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
- return &account, nil
- }
- func (s *stubAdminService) GetAccountsByIDs(ctx context.Context, ids []int64) ([]*service.Account, error) {
- out := make([]*service.Account, 0, len(ids))
- for _, id := range ids {
- account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
- out = append(out, &account)
- }
- return out, nil
- }
- func (s *stubAdminService) CreateAccount(ctx context.Context, input *service.CreateAccountInput) (*service.Account, error) {
- s.mu.Lock()
- s.createdAccounts = append(s.createdAccounts, input)
- s.mu.Unlock()
- if s.createAccountErr != nil {
- return nil, s.createAccountErr
- }
- account := service.Account{ID: 300, Name: input.Name, Status: service.StatusActive}
- return &account, nil
- }
- func (s *stubAdminService) ValidateAccountGroupBindings(context.Context, []int64) error { return nil }
- func (s *stubAdminService) DuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*service.Account, error) {
- account := service.Account{ID: 301, Name: "account (Copy)", Status: service.StatusActive, Schedulable: false}
- return &account, nil
- }
- func (s *stubAdminService) RecoverDuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*service.Account, error) {
- return nil, nil
- }
- func (s *stubAdminService) UpdateAccount(ctx context.Context, id int64, input *service.UpdateAccountInput) (*service.Account, error) {
- s.updateAccountCalls++
- s.lastUpdateAccountInput = input
- if s.updateAccountErr != nil {
- return nil, s.updateAccountErr
- }
- account := service.Account{ID: id, Name: input.Name, Status: service.StatusActive}
- return &account, nil
- }
- func (s *stubAdminService) UpdateAccountExtra(ctx context.Context, id int64, updates map[string]any) error {
- s.updateAccountExtraCalls++
- return nil
- }
- func (s *stubAdminService) DeleteAccount(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) RefreshAccountCredentials(ctx context.Context, id int64) (*service.Account, error) {
- account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
- return &account, nil
- }
- func (s *stubAdminService) ClearAccountError(ctx context.Context, id int64) (*service.Account, error) {
- account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
- return &account, nil
- }
- func (s *stubAdminService) SetAccountError(ctx context.Context, id int64, errorMsg string) error {
- return nil
- }
- func (s *stubAdminService) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*service.Account, error) {
- account := service.Account{ID: id, Name: "account", Status: service.StatusActive, Schedulable: schedulable}
- return &account, nil
- }
- func (s *stubAdminService) BulkUpdateAccounts(ctx context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
- s.lastBulkUpdateAccountInput = input
- if s.bulkUpdateAccountErr != nil {
- return nil, s.bulkUpdateAccountErr
- }
- return &service.BulkUpdateAccountsResult{Success: len(input.AccountIDs), Failed: 0, SuccessIDs: input.AccountIDs}, nil
- }
- func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error {
- s.lastMixedCheck.accountID = currentAccountID
- s.lastMixedCheck.platform = currentAccountPlatform
- s.lastMixedCheck.groupIDs = append([]int64(nil), groupIDs...)
- return s.checkMixedErr
- }
- 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 {
- if protocol != "" && proxy.Protocol != protocol {
- continue
- }
- if status != "" && proxy.Status != status {
- continue
- }
- if search != "" {
- name := strings.ToLower(proxy.Name)
- host := strings.ToLower(proxy.Host)
- if !strings.Contains(name, search) && !strings.Contains(host, search) {
- continue
- }
- }
- filtered = append(filtered, proxy)
- }
- return filtered, int64(len(filtered)), nil
- }
- 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
- }
- func (s *stubAdminService) GetAllProxies(ctx context.Context) ([]service.Proxy, error) {
- return s.proxies, nil
- }
- func (s *stubAdminService) GetAllProxiesWithAccountCount(ctx context.Context) ([]service.ProxyWithAccountCount, error) {
- return s.proxyCounts, nil
- }
- func (s *stubAdminService) GetProxy(ctx context.Context, id int64) (*service.Proxy, error) {
- for i := range s.proxies {
- proxy := s.proxies[i]
- if proxy.ID == id {
- return &proxy, nil
- }
- }
- proxy := service.Proxy{ID: id, Name: "proxy", Status: service.StatusActive}
- return &proxy, nil
- }
- func (s *stubAdminService) GetProxiesByIDs(ctx context.Context, ids []int64) ([]service.Proxy, error) {
- if len(ids) == 0 {
- return []service.Proxy{}, nil
- }
- out := make([]service.Proxy, 0, len(ids))
- seen := make(map[int64]struct{}, len(ids))
- for _, id := range ids {
- seen[id] = struct{}{}
- }
- for i := range s.proxies {
- proxy := s.proxies[i]
- if _, ok := seen[proxy.ID]; ok {
- out = append(out, proxy)
- }
- }
- return out, nil
- }
- func (s *stubAdminService) CreateProxy(ctx context.Context, input *service.CreateProxyInput) (*service.Proxy, error) {
- s.mu.Lock()
- s.createdProxies = append(s.createdProxies, input)
- s.mu.Unlock()
- proxy := service.Proxy{ID: 400, Name: input.Name, Status: service.StatusActive}
- return &proxy, nil
- }
- func (s *stubAdminService) UpdateProxy(ctx context.Context, id int64, input *service.UpdateProxyInput) (*service.Proxy, error) {
- s.mu.Lock()
- s.updatedProxyIDs = append(s.updatedProxyIDs, id)
- s.updatedProxies = append(s.updatedProxies, input)
- s.mu.Unlock()
- proxy := service.Proxy{ID: id, Name: input.Name, Status: service.StatusActive}
- return &proxy, nil
- }
- func (s *stubAdminService) DeleteProxy(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) BatchDeleteProxies(ctx context.Context, ids []int64) (*service.ProxyBatchDeleteResult, error) {
- return &service.ProxyBatchDeleteResult{DeletedIDs: ids}, nil
- }
- func (s *stubAdminService) GetProxyAccounts(ctx context.Context, proxyID int64) ([]service.ProxyAccountSummary, error) {
- return []service.ProxyAccountSummary{{ID: 1, Name: "account"}}, nil
- }
- func (s *stubAdminService) CheckProxyExists(ctx context.Context, host string, port int, username, password string) (bool, error) {
- return false, nil
- }
- func (s *stubAdminService) TestProxy(ctx context.Context, id int64) (*service.ProxyTestResult, error) {
- s.mu.Lock()
- s.testedProxyIDs = append(s.testedProxyIDs, id)
- s.mu.Unlock()
- return &service.ProxyTestResult{Success: true, Message: "ok"}, nil
- }
- func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*service.ProxyQualityCheckResult, error) {
- return &service.ProxyQualityCheckResult{
- ProxyID: id,
- Score: 95,
- Grade: "A",
- Summary: "通过 5 项,告警 0 项,失败 0 项,挑战 0 项",
- PassedCount: 5,
- WarnCount: 0,
- FailedCount: 0,
- ChallengeCount: 0,
- CheckedAt: time.Now().Unix(),
- Items: []service.ProxyQualityCheckItem{
- {Target: "base_connectivity", Status: "pass", Message: "ok"},
- {Target: "openai", Status: "pass", HTTPStatus: 401},
- {Target: "anthropic", Status: "pass", HTTPStatus: 401},
- {Target: "gemini", Status: "pass", HTTPStatus: 200},
- },
- }, nil
- }
- 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
- }
- func (s *stubAdminService) GetRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
- code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUnused}
- return &code, nil
- }
- func (s *stubAdminService) GenerateRedeemCodes(ctx context.Context, input *service.GenerateRedeemCodesInput) ([]service.RedeemCode, error) {
- return s.redeems, nil
- }
- func (s *stubAdminService) DeleteRedeemCode(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) BatchDeleteRedeemCodes(ctx context.Context, ids []int64) (int64, error) {
- return int64(len(ids)), nil
- }
- func (s *stubAdminService) ExpireRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
- code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUsed}
- return &code, nil
- }
- func (s *stubAdminService) GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]service.RedeemCode, int64, float64, error) {
- return s.redeems, int64(len(s.redeems)), 100.0, nil
- }
- func (s *stubAdminService) UpdateGroupSortOrders(ctx context.Context, updates []service.GroupSortOrderUpdate) error {
- return nil
- }
- func (s *stubAdminService) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID int64, groupID *int64) (*service.AdminUpdateAPIKeyGroupIDResult, error) {
- for i := range s.apiKeys {
- if s.apiKeys[i].ID == keyID {
- k := s.apiKeys[i]
- if groupID != nil {
- if *groupID == 0 {
- k.GroupID = nil
- } else {
- gid := *groupID
- k.GroupID = &gid
- }
- }
- return &service.AdminUpdateAPIKeyGroupIDResult{APIKey: &k}, nil
- }
- }
- return nil, service.ErrAPIKeyNotFound
- }
- func (s *stubAdminService) AdminResetAPIKeyRateLimitUsage(ctx context.Context, keyID int64) (*service.APIKey, error) {
- for i := range s.apiKeys {
- if s.apiKeys[i].ID == keyID {
- s.apiKeys[i].Usage5h = 0
- s.apiKeys[i].Usage1d = 0
- s.apiKeys[i].Usage7d = 0
- s.apiKeys[i].Window5hStart = nil
- s.apiKeys[i].Window1dStart = nil
- s.apiKeys[i].Window7dStart = nil
- k := s.apiKeys[i]
- return &k, nil
- }
- }
- return nil, service.ErrAPIKeyNotFound
- }
- func (s *stubAdminService) ResetAccountQuota(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) EnsureOpenAIPrivacy(ctx context.Context, account *service.Account) string {
- return ""
- }
- func (s *stubAdminService) EnsureAntigravityPrivacy(ctx context.Context, account *service.Account) string {
- return ""
- }
- func (s *stubAdminService) ForceOpenAIPrivacy(ctx context.Context, account *service.Account) string {
- return ""
- }
- func (s *stubAdminService) ForceAntigravityPrivacy(ctx context.Context, account *service.Account) string {
- return ""
- }
- func (s *stubAdminService) ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*service.ReplaceUserGroupResult, error) {
- return &service.ReplaceUserGroupResult{MigratedKeys: 0}, nil
- }
- func (s *stubAdminService) RevertAccountProxyFallback(ctx context.Context, id int64) error {
- return nil
- }
- func (s *stubAdminService) CreateShadow(ctx context.Context, parentID int64, opts service.ShadowOptions) (*service.Account, error) {
- if s.createSparkShadowErr != nil {
- return nil, s.createSparkShadowErr
- }
- pid := parentID
- return &service.Account{
- ID: 9001,
- Name: opts.Name,
- Platform: service.PlatformOpenAI,
- Type: service.AccountTypeOAuth,
- Priority: opts.Priority,
- Concurrency: opts.Concurrency,
- GroupIDs: opts.GroupIDs,
- ParentAccountID: &pid,
- QuotaDimension: service.QuotaDimensionSpark,
- Credentials: map[string]any{},
- Extra: map[string]any{},
- }, nil
- }
- // Ensure stub implements interface.
- var _ service.AdminService = (*stubAdminService)(nil)
|