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
6c020763
Unverified
Commit
6c020763
authored
Mar 18, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 18, 2026
Browse files
Merge pull request #1106 from geminiwen/feat/subscription-platform-filter
feat: add platform type filter to subscription management
parents
7414bdf0
1ac7219a
Changes
13
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/subscription_handler.go
View file @
6c020763
...
...
@@ -77,12 +77,13 @@ func (h *SubscriptionHandler) List(c *gin.Context) {
}
}
status
:=
c
.
Query
(
"status"
)
platform
:=
c
.
Query
(
"platform"
)
// Parse sorting parameters
sortBy
:=
c
.
DefaultQuery
(
"sort_by"
,
"created_at"
)
sortOrder
:=
c
.
DefaultQuery
(
"sort_order"
,
"desc"
)
subscriptions
,
pagination
,
err
:=
h
.
subscriptionService
.
List
(
c
.
Request
.
Context
(),
page
,
pageSize
,
userID
,
groupID
,
status
,
sortBy
,
sortOrder
)
subscriptions
,
pagination
,
err
:=
h
.
subscriptionService
.
List
(
c
.
Request
.
Context
(),
page
,
pageSize
,
userID
,
groupID
,
status
,
platform
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
response
.
ErrorFrom
(
c
,
err
)
return
...
...
backend/internal/repository/user_subscription_repo.go
View file @
6c020763
...
...
@@ -5,6 +5,7 @@ import (
"time"
dbent
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/group"
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
"github.com/Wei-Shaw/sub2api/internal/service"
...
...
@@ -190,7 +191,7 @@ func (r *userSubscriptionRepository) ListByGroupID(ctx context.Context, groupID
return
userSubscriptionEntitiesToService
(
subs
),
paginationResultFromTotal
(
int64
(
total
),
params
),
nil
}
func
(
r
*
userSubscriptionRepository
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
r
*
userSubscriptionRepository
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
client
:=
clientFromContext
(
ctx
,
r
.
client
)
q
:=
client
.
UserSubscription
.
Query
()
if
userID
!=
nil
{
...
...
@@ -199,6 +200,9 @@ func (r *userSubscriptionRepository) List(ctx context.Context, params pagination
if
groupID
!=
nil
{
q
=
q
.
Where
(
usersubscription
.
GroupIDEQ
(
*
groupID
))
}
if
platform
!=
""
{
q
=
q
.
Where
(
usersubscription
.
HasGroupWith
(
group
.
PlatformEQ
(
platform
)))
}
// Status filtering with real-time expiration check
now
:=
time
.
Now
()
...
...
backend/internal/repository/user_subscription_repo_integration_test.go
View file @
6c020763
...
...
@@ -271,7 +271,7 @@ func (s *UserSubscriptionRepoSuite) TestList_NoFilters() {
group
:=
s
.
mustCreateGroup
(
"g-list"
)
s
.
mustCreateSubscription
(
user
.
ID
,
group
.
ID
,
nil
)
subs
,
page
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
nil
,
""
,
""
,
""
)
subs
,
page
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
nil
,
""
,
""
,
""
,
""
)
s
.
Require
()
.
NoError
(
err
,
"List"
)
s
.
Require
()
.
Len
(
subs
,
1
)
s
.
Require
()
.
Equal
(
int64
(
1
),
page
.
Total
)
...
...
@@ -285,7 +285,7 @@ func (s *UserSubscriptionRepoSuite) TestList_FilterByUserID() {
s
.
mustCreateSubscription
(
user1
.
ID
,
group
.
ID
,
nil
)
s
.
mustCreateSubscription
(
user2
.
ID
,
group
.
ID
,
nil
)
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
&
user1
.
ID
,
nil
,
""
,
""
,
""
)
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
&
user1
.
ID
,
nil
,
""
,
""
,
""
,
""
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
subs
,
1
)
s
.
Require
()
.
Equal
(
user1
.
ID
,
subs
[
0
]
.
UserID
)
...
...
@@ -299,7 +299,7 @@ func (s *UserSubscriptionRepoSuite) TestList_FilterByGroupID() {
s
.
mustCreateSubscription
(
user
.
ID
,
g1
.
ID
,
nil
)
s
.
mustCreateSubscription
(
user
.
ID
,
g2
.
ID
,
nil
)
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
&
g1
.
ID
,
""
,
""
,
""
)
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
&
g1
.
ID
,
""
,
""
,
""
,
""
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
subs
,
1
)
s
.
Require
()
.
Equal
(
g1
.
ID
,
subs
[
0
]
.
GroupID
)
...
...
@@ -320,7 +320,7 @@ func (s *UserSubscriptionRepoSuite) TestList_FilterByStatus() {
c
.
SetExpiresAt
(
time
.
Now
()
.
Add
(
-
24
*
time
.
Hour
))
})
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
nil
,
service
.
SubscriptionStatusExpired
,
""
,
""
)
subs
,
_
,
err
:=
s
.
repo
.
List
(
s
.
ctx
,
pagination
.
PaginationParams
{
Page
:
1
,
PageSize
:
10
},
nil
,
nil
,
service
.
SubscriptionStatusExpired
,
""
,
""
,
""
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Len
(
subs
,
1
)
s
.
Require
()
.
Equal
(
service
.
SubscriptionStatusExpired
,
subs
[
0
]
.
Status
)
...
...
backend/internal/server/api_contract_test.go
View file @
6c020763
...
...
@@ -1289,7 +1289,7 @@ func (r *stubUserSubscriptionRepo) ListActiveByUserID(ctx context.Context, userI
func
(
stubUserSubscriptionRepo
)
ListByGroupID
(
ctx
context
.
Context
,
groupID
int64
,
params
pagination
.
PaginationParams
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
stubUserSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
stubUserSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
stubUserSubscriptionRepo
)
ExistsByUserIDAndGroupID
(
ctx
context
.
Context
,
userID
,
groupID
int64
)
(
bool
,
error
)
{
...
...
backend/internal/server/middleware/api_key_auth_google_test.go
View file @
6c020763
...
...
@@ -135,7 +135,7 @@ func (f fakeGoogleSubscriptionRepo) ListActiveByUserID(ctx context.Context, user
func
(
f
fakeGoogleSubscriptionRepo
)
ListByGroupID
(
ctx
context
.
Context
,
groupID
int64
,
params
pagination
.
PaginationParams
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
f
fakeGoogleSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
f
fakeGoogleSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
f
fakeGoogleSubscriptionRepo
)
ExistsByUserIDAndGroupID
(
ctx
context
.
Context
,
userID
,
groupID
int64
)
(
bool
,
error
)
{
...
...
backend/internal/server/middleware/api_key_auth_test.go
View file @
6c020763
...
...
@@ -646,7 +646,7 @@ func (r *stubUserSubscriptionRepo) ListByGroupID(ctx context.Context, groupID in
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
func
(
r
*
stubUserSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
r
*
stubUserSubscriptionRepo
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
service
.
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
return
nil
,
nil
,
errors
.
New
(
"not implemented"
)
}
...
...
backend/internal/service/subscription_assign_idempotency_test.go
View file @
6c020763
...
...
@@ -92,7 +92,7 @@ func (userSubRepoNoop) ListActiveByUserID(context.Context, int64) ([]UserSubscri
func
(
userSubRepoNoop
)
ListByGroupID
(
context
.
Context
,
int64
,
pagination
.
PaginationParams
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected ListByGroupID call"
)
}
func
(
userSubRepoNoop
)
List
(
context
.
Context
,
pagination
.
PaginationParams
,
*
int64
,
*
int64
,
string
,
string
,
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
userSubRepoNoop
)
List
(
context
.
Context
,
pagination
.
PaginationParams
,
*
int64
,
*
int64
,
string
,
string
,
string
,
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
panic
(
"unexpected List call"
)
}
func
(
userSubRepoNoop
)
ExistsByUserIDAndGroupID
(
context
.
Context
,
int64
,
int64
)
(
bool
,
error
)
{
...
...
backend/internal/service/subscription_service.go
View file @
6c020763
...
...
@@ -634,9 +634,9 @@ func (s *SubscriptionService) ListGroupSubscriptions(ctx context.Context, groupI
}
// List 获取所有订阅(分页,支持筛选和排序)
func
(
s
*
SubscriptionService
)
List
(
ctx
context
.
Context
,
page
,
pageSize
int
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
func
(
s
*
SubscriptionService
)
List
(
ctx
context
.
Context
,
page
,
pageSize
int
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
{
params
:=
pagination
.
PaginationParams
{
Page
:
page
,
PageSize
:
pageSize
}
subs
,
pag
,
err
:=
s
.
userSubRepo
.
List
(
ctx
,
params
,
userID
,
groupID
,
status
,
sortBy
,
sortOrder
)
subs
,
pag
,
err
:=
s
.
userSubRepo
.
List
(
ctx
,
params
,
userID
,
groupID
,
status
,
platform
,
sortBy
,
sortOrder
)
if
err
!=
nil
{
return
nil
,
nil
,
err
}
...
...
backend/internal/service/user_subscription_port.go
View file @
6c020763
...
...
@@ -18,7 +18,7 @@ type UserSubscriptionRepository interface {
ListByUserID
(
ctx
context
.
Context
,
userID
int64
)
([]
UserSubscription
,
error
)
ListActiveByUserID
(
ctx
context
.
Context
,
userID
int64
)
([]
UserSubscription
,
error
)
ListByGroupID
(
ctx
context
.
Context
,
groupID
int64
,
params
pagination
.
PaginationParams
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
sortBy
,
sortOrder
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
List
(
ctx
context
.
Context
,
params
pagination
.
PaginationParams
,
userID
,
groupID
*
int64
,
status
,
platform
,
sortBy
,
sortOrder
string
)
([]
UserSubscription
,
*
pagination
.
PaginationResult
,
error
)
ExistsByUserIDAndGroupID
(
ctx
context
.
Context
,
userID
,
groupID
int64
)
(
bool
,
error
)
ExtendExpiry
(
ctx
context
.
Context
,
subscriptionID
int64
,
newExpiresAt
time
.
Time
)
error
...
...
frontend/src/api/admin/subscriptions.ts
View file @
6c020763
...
...
@@ -27,6 +27,7 @@ export async function list(
status
?:
'
active
'
|
'
expired
'
|
'
revoked
'
user_id
?:
number
group_id
?:
number
platform
?:
string
sort_by
?:
string
sort_order
?:
'
asc
'
|
'
desc
'
},
...
...
frontend/src/i18n/locales/en.ts
View file @
6c020763
...
...
@@ -1702,6 +1702,7 @@ export default {
revokeSubscription
:
'
Revoke Subscription
'
,
allStatus
:
'
All Status
'
,
allGroups
:
'
All Groups
'
,
allPlatforms
:
'
All Platforms
'
,
daily
:
'
Daily
'
,
weekly
:
'
Weekly
'
,
monthly
:
'
Monthly
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
6c020763
...
...
@@ -1782,6 +1782,7 @@ export default {
revokeSubscription
:
'
撤销订阅
'
,
allStatus
:
'
全部状态
'
,
allGroups
:
'
全部分组
'
,
allPlatforms
:
'
全部平台
'
,
daily
:
'
每日
'
,
weekly
:
'
每周
'
,
monthly
:
'
每月
'
,
...
...
frontend/src/views/admin/SubscriptionsView.vue
View file @
6c020763
...
...
@@ -81,6 +81,14 @@
@
change=
"applyFilters"
/>
</div>
<div
class=
"w-full sm:w-40"
>
<Select
v-model=
"filters.platform"
:options=
"platformFilterOptions"
:placeholder=
"t('admin.subscriptions.allPlatforms')"
@
change=
"applyFilters"
/>
</div>
</div>
<!-- Right: Actions -->
...
...
@@ -908,6 +916,7 @@ let userSearchTimeout: ReturnType<typeof setTimeout> | null = null
const
filters
=
reactive
({
status
:
'
active
'
,
group_id
:
''
,
platform
:
''
,
user_id
:
null
as
number
|
null
}
)
...
...
@@ -950,6 +959,15 @@ const groupOptions = computed(() => [
...
groups
.
value
.
map
((
g
)
=>
({
value
:
g
.
id
.
toString
(),
label
:
g
.
name
}
))
])
const
platformFilterOptions
=
computed
(()
=>
[
{
value
:
''
,
label
:
t
(
'
admin.subscriptions.allPlatforms
'
)
}
,
{
value
:
'
anthropic
'
,
label
:
'
Anthropic
'
}
,
{
value
:
'
openai
'
,
label
:
'
OpenAI
'
}
,
{
value
:
'
gemini
'
,
label
:
'
Gemini
'
}
,
{
value
:
'
antigravity
'
,
label
:
'
Antigravity
'
}
,
{
value
:
'
sora
'
,
label
:
'
Sora
'
}
])
// Group options for assign (only subscription type groups)
const
subscriptionGroupOptions
=
computed
(()
=>
groups
.
value
...
...
@@ -985,6 +1003,7 @@ const loadSubscriptions = async () => {
{
status
:
(
filters
.
status
as
any
)
||
undefined
,
group_id
:
filters
.
group_id
?
parseInt
(
filters
.
group_id
)
:
undefined
,
platform
:
filters
.
platform
||
undefined
,
user_id
:
filters
.
user_id
||
undefined
,
sort_by
:
sortState
.
sort_by
,
sort_order
:
sortState
.
sort_order
...
...
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