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
b08767a4
Commit
b08767a4
authored
Mar 10, 2026
by
ischanx
Browse files
fix: avoid admin subscription binding regressions
parent
767a41e2
Changes
2
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/admin_service.go
View file @
b08767a4
...
@@ -1282,6 +1282,9 @@ func (s *adminServiceImpl) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID i
...
@@ -1282,6 +1282,9 @@ func (s *adminServiceImpl) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID i
}
}
// 订阅类型分组:用户须持有该分组的有效订阅才可绑定
// 订阅类型分组:用户须持有该分组的有效订阅才可绑定
if
group
.
IsSubscriptionType
()
{
if
group
.
IsSubscriptionType
()
{
if
s
.
userSubRepo
==
nil
{
return
nil
,
infraerrors
.
InternalServer
(
"SUBSCRIPTION_REPOSITORY_UNAVAILABLE"
,
"subscription repository is not configured"
)
}
if
_
,
err
:=
s
.
userSubRepo
.
GetActiveByUserIDAndGroupID
(
ctx
,
apiKey
.
UserID
,
*
groupID
);
err
!=
nil
{
if
_
,
err
:=
s
.
userSubRepo
.
GetActiveByUserIDAndGroupID
(
ctx
,
apiKey
.
UserID
,
*
groupID
);
err
!=
nil
{
if
errors
.
Is
(
err
,
ErrSubscriptionNotFound
)
{
if
errors
.
Is
(
err
,
ErrSubscriptionNotFound
)
{
return
nil
,
infraerrors
.
BadRequest
(
"SUBSCRIPTION_REQUIRED"
,
"user does not have an active subscription for this group"
)
return
nil
,
infraerrors
.
BadRequest
(
"SUBSCRIPTION_REQUIRED"
,
"user does not have an active subscription for this group"
)
...
@@ -1295,7 +1298,7 @@ func (s *adminServiceImpl) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID i
...
@@ -1295,7 +1298,7 @@ func (s *adminServiceImpl) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID i
apiKey
.
Group
=
group
apiKey
.
Group
=
group
// 专属标准分组:使用事务保证「添加分组权限」与「更新 API Key」的原子性
// 专属标准分组:使用事务保证「添加分组权限」与「更新 API Key」的原子性
if
group
.
IsExclusive
{
if
group
.
IsExclusive
&&
!
group
.
IsSubscriptionType
()
{
opCtx
:=
ctx
opCtx
:=
ctx
var
tx
*
dbent
.
Tx
var
tx
*
dbent
.
Tx
if
s
.
entClient
==
nil
{
if
s
.
entClient
==
nil
{
...
...
backend/internal/service/admin_service_apikey_test.go
View file @
b08767a4
...
@@ -32,28 +32,44 @@ func (s *userRepoStubForGroupUpdate) AddGroupToAllowedGroups(_ context.Context,
...
@@ -32,28 +32,44 @@ func (s *userRepoStubForGroupUpdate) AddGroupToAllowedGroups(_ context.Context,
return
s
.
addGroupErr
return
s
.
addGroupErr
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
Create
(
context
.
Context
,
*
User
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
Create
(
context
.
Context
,
*
User
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
GetByID
(
context
.
Context
,
int64
)
(
*
User
,
error
)
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
GetByID
(
context
.
Context
,
int64
)
(
*
User
,
error
)
{
func
(
s
*
userRepoStubForGroupUpdate
)
GetByEmail
(
context
.
Context
,
string
)
(
*
User
,
error
)
{
panic
(
"unexpected"
)
}
panic
(
"unexpected"
)
func
(
s
*
userRepoStubForGroupUpdate
)
GetFirstAdmin
(
context
.
Context
)
(
*
User
,
error
)
{
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
Update
(
context
.
Context
,
*
User
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
GetByEmail
(
context
.
Context
,
string
)
(
*
User
,
error
)
{
func
(
s
*
userRepoStubForGroupUpdate
)
Delete
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
GetFirstAdmin
(
context
.
Context
)
(
*
User
,
error
)
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
Update
(
context
.
Context
,
*
User
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
Delete
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
List
(
context
.
Context
,
pagination
.
PaginationParams
)
([]
User
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
s
*
userRepoStubForGroupUpdate
)
List
(
context
.
Context
,
pagination
.
PaginationParams
)
([]
User
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
ListWithFilters
(
context
.
Context
,
pagination
.
PaginationParams
,
UserListFilters
)
([]
User
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
s
*
userRepoStubForGroupUpdate
)
ListWithFilters
(
context
.
Context
,
pagination
.
PaginationParams
,
UserListFilters
)
([]
User
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateBalance
(
context
.
Context
,
int64
,
float64
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateBalance
(
context
.
Context
,
int64
,
float64
)
error
{
func
(
s
*
userRepoStubForGroupUpdate
)
DeductBalance
(
context
.
Context
,
int64
,
float64
)
error
{
panic
(
"unexpected"
)
}
panic
(
"unexpected"
)
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateConcurrency
(
context
.
Context
,
int64
,
int
)
error
{
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
ExistsByEmail
(
context
.
Context
,
string
)
(
bool
,
error
)
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
DeductBalance
(
context
.
Context
,
int64
,
float64
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateConcurrency
(
context
.
Context
,
int64
,
int
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
ExistsByEmail
(
context
.
Context
,
string
)
(
bool
,
error
)
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
RemoveGroupFromAllowedGroups
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
func
(
s
*
userRepoStubForGroupUpdate
)
RemoveGroupFromAllowedGroups
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateTotpSecret
(
context
.
Context
,
int64
,
*
string
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
UpdateTotpSecret
(
context
.
Context
,
int64
,
*
string
)
error
{
func
(
s
*
userRepoStubForGroupUpdate
)
EnableTotp
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
panic
(
"unexpected"
)
func
(
s
*
userRepoStubForGroupUpdate
)
DisableTotp
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
}
func
(
s
*
userRepoStubForGroupUpdate
)
EnableTotp
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
func
(
s
*
userRepoStubForGroupUpdate
)
DisableTotp
(
context
.
Context
,
int64
)
error
{
panic
(
"unexpected"
)
}
// apiKeyRepoStubForGroupUpdate implements APIKeyRepository for AdminUpdateAPIKeyGroupID tests.
// apiKeyRepoStubForGroupUpdate implements APIKeyRepository for AdminUpdateAPIKeyGroupID tests.
type
apiKeyRepoStubForGroupUpdate
struct
{
type
apiKeyRepoStubForGroupUpdate
struct
{
...
@@ -194,6 +210,29 @@ func (s *groupRepoStubForGroupUpdate) UpdateSortOrders(context.Context, []GroupS
...
@@ -194,6 +210,29 @@ func (s *groupRepoStubForGroupUpdate) UpdateSortOrders(context.Context, []GroupS
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
type
userSubRepoStubForGroupUpdate
struct
{
userSubRepoNoop
getActiveSub
*
UserSubscription
getActiveErr
error
called
bool
calledUserID
int64
calledGroupID
int64
}
func
(
s
*
userSubRepoStubForGroupUpdate
)
GetActiveByUserIDAndGroupID
(
_
context
.
Context
,
userID
,
groupID
int64
)
(
*
UserSubscription
,
error
)
{
s
.
called
=
true
s
.
calledUserID
=
userID
s
.
calledGroupID
=
groupID
if
s
.
getActiveErr
!=
nil
{
return
nil
,
s
.
getActiveErr
}
if
s
.
getActiveSub
==
nil
{
return
nil
,
ErrSubscriptionNotFound
}
clone
:=
*
s
.
getActiveSub
return
&
clone
,
nil
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// Tests
// Tests
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
...
@@ -386,14 +425,49 @@ func TestAdminService_AdminUpdateAPIKeyGroupID_NonExclusiveGroup_NoAllowedGroupU
...
@@ -386,14 +425,49 @@ func TestAdminService_AdminUpdateAPIKeyGroupID_NonExclusiveGroup_NoAllowedGroupU
func
TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_Blocked
(
t
*
testing
.
T
)
{
func
TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_Blocked
(
t
*
testing
.
T
)
{
existing
:=
&
APIKey
{
ID
:
1
,
UserID
:
42
,
Key
:
"sk-test"
,
GroupID
:
nil
}
existing
:=
&
APIKey
{
ID
:
1
,
UserID
:
42
,
Key
:
"sk-test"
,
GroupID
:
nil
}
apiKeyRepo
:=
&
apiKeyRepoStubForGroupUpdate
{
key
:
existing
}
apiKeyRepo
:=
&
apiKeyRepoStubForGroupUpdate
{
key
:
existing
}
groupRepo
:=
&
groupRepoStubForGroupUpdate
{
group
:
&
Group
{
ID
:
10
,
Name
:
"Sub"
,
Status
:
StatusActive
,
IsExclusive
:
true
,
SubscriptionType
:
SubscriptionTypeSubscription
}}
groupRepo
:=
&
groupRepoStubForGroupUpdate
{
group
:
&
Group
{
ID
:
10
,
Name
:
"Sub"
,
Status
:
StatusActive
,
IsExclusive
:
false
,
SubscriptionType
:
SubscriptionTypeSubscription
}}
userRepo
:=
&
userRepoStubForGroupUpdate
{}
userSubRepo
:=
&
userSubRepoStubForGroupUpdate
{
getActiveErr
:
ErrSubscriptionNotFound
}
svc
:=
&
adminServiceImpl
{
apiKeyRepo
:
apiKeyRepo
,
groupRepo
:
groupRepo
,
userRepo
:
userRepo
,
userSubRepo
:
userSubRepo
}
// 无有效订阅时应拒绝绑定
_
,
err
:=
svc
.
AdminUpdateAPIKeyGroupID
(
context
.
Background
(),
1
,
int64Ptr
(
10
))
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
"SUBSCRIPTION_REQUIRED"
,
infraerrors
.
Reason
(
err
))
require
.
True
(
t
,
userSubRepo
.
called
)
require
.
Equal
(
t
,
int64
(
42
),
userSubRepo
.
calledUserID
)
require
.
Equal
(
t
,
int64
(
10
),
userSubRepo
.
calledGroupID
)
require
.
False
(
t
,
userRepo
.
addGroupCalled
)
}
func
TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_RequiresRepo
(
t
*
testing
.
T
)
{
existing
:=
&
APIKey
{
ID
:
1
,
UserID
:
42
,
Key
:
"sk-test"
,
GroupID
:
nil
}
apiKeyRepo
:=
&
apiKeyRepoStubForGroupUpdate
{
key
:
existing
}
groupRepo
:=
&
groupRepoStubForGroupUpdate
{
group
:
&
Group
{
ID
:
10
,
Name
:
"Sub"
,
Status
:
StatusActive
,
IsExclusive
:
false
,
SubscriptionType
:
SubscriptionTypeSubscription
}}
userRepo
:=
&
userRepoStubForGroupUpdate
{}
userRepo
:=
&
userRepoStubForGroupUpdate
{}
svc
:=
&
adminServiceImpl
{
apiKeyRepo
:
apiKeyRepo
,
groupRepo
:
groupRepo
,
userRepo
:
userRepo
}
svc
:=
&
adminServiceImpl
{
apiKeyRepo
:
apiKeyRepo
,
groupRepo
:
groupRepo
,
userRepo
:
userRepo
}
// 订阅类型分组应被阻止绑定
_
,
err
:=
svc
.
AdminUpdateAPIKeyGroupID
(
context
.
Background
(),
1
,
int64Ptr
(
10
))
_
,
err
:=
svc
.
AdminUpdateAPIKeyGroupID
(
context
.
Background
(),
1
,
int64Ptr
(
10
))
require
.
Error
(
t
,
err
)
require
.
Error
(
t
,
err
)
require
.
Equal
(
t
,
"SUBSCRIPTION_GROUP_NOT_ALLOWED"
,
infraerrors
.
Reason
(
err
))
require
.
Equal
(
t
,
"SUBSCRIPTION_REPOSITORY_UNAVAILABLE"
,
infraerrors
.
Reason
(
err
))
require
.
False
(
t
,
userRepo
.
addGroupCalled
)
}
func
TestAdminService_AdminUpdateAPIKeyGroupID_SubscriptionGroup_AllowsActiveSubscription
(
t
*
testing
.
T
)
{
existing
:=
&
APIKey
{
ID
:
1
,
UserID
:
42
,
Key
:
"sk-test"
,
GroupID
:
nil
}
apiKeyRepo
:=
&
apiKeyRepoStubForGroupUpdate
{
key
:
existing
}
groupRepo
:=
&
groupRepoStubForGroupUpdate
{
group
:
&
Group
{
ID
:
10
,
Name
:
"Sub"
,
Status
:
StatusActive
,
IsExclusive
:
true
,
SubscriptionType
:
SubscriptionTypeSubscription
}}
userRepo
:=
&
userRepoStubForGroupUpdate
{}
userSubRepo
:=
&
userSubRepoStubForGroupUpdate
{
getActiveSub
:
&
UserSubscription
{
ID
:
99
,
UserID
:
42
,
GroupID
:
10
},
}
svc
:=
&
adminServiceImpl
{
apiKeyRepo
:
apiKeyRepo
,
groupRepo
:
groupRepo
,
userRepo
:
userRepo
,
userSubRepo
:
userSubRepo
}
got
,
err
:=
svc
.
AdminUpdateAPIKeyGroupID
(
context
.
Background
(),
1
,
int64Ptr
(
10
))
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
userSubRepo
.
called
)
require
.
NotNil
(
t
,
got
.
APIKey
.
GroupID
)
require
.
Equal
(
t
,
int64
(
10
),
*
got
.
APIKey
.
GroupID
)
require
.
False
(
t
,
userRepo
.
addGroupCalled
)
require
.
False
(
t
,
userRepo
.
addGroupCalled
)
}
}
...
...
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