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
e6326b29
Unverified
Commit
e6326b29
authored
Mar 18, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 18, 2026
Browse files
Merge pull request #1108 from DaydreamCoding/feat/admin-group-capacity-and-usage
feat(admin): 分组管理列表新增用量、账号分类与容量列
parents
17cdcebd
d4cc9871
Changes
32
Hide whitespace changes
Inline
Side-by-side
backend/cmd/server/wire_gen.go
View file @
e6326b29
...
@@ -110,7 +110,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -110,7 +110,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
concurrencyCache
:=
repository
.
ProvideConcurrencyCache
(
redisClient
,
configConfig
)
concurrencyCache
:=
repository
.
ProvideConcurrencyCache
(
redisClient
,
configConfig
)
concurrencyService
:=
service
.
ProvideConcurrencyService
(
concurrencyCache
,
accountRepository
,
configConfig
)
concurrencyService
:=
service
.
ProvideConcurrencyService
(
concurrencyCache
,
accountRepository
,
configConfig
)
adminUserHandler
:=
admin
.
NewUserHandler
(
adminService
,
concurrencyService
)
adminUserHandler
:=
admin
.
NewUserHandler
(
adminService
,
concurrencyService
)
groupHandler
:=
admin
.
NewGroupHandler
(
adminService
)
claudeOAuthClient
:=
repository
.
NewClaudeOAuthClient
()
claudeOAuthClient
:=
repository
.
NewClaudeOAuthClient
()
oAuthService
:=
service
.
NewOAuthService
(
proxyRepository
,
claudeOAuthClient
)
oAuthService
:=
service
.
NewOAuthService
(
proxyRepository
,
claudeOAuthClient
)
openAIOAuthClient
:=
repository
.
NewOpenAIOAuthClient
()
openAIOAuthClient
:=
repository
.
NewOpenAIOAuthClient
()
...
@@ -143,6 +142,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
...
@@ -143,6 +142,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
crsSyncService
:=
service
.
NewCRSSyncService
(
accountRepository
,
proxyRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
configConfig
)
crsSyncService
:=
service
.
NewCRSSyncService
(
accountRepository
,
proxyRepository
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
configConfig
)
sessionLimitCache
:=
repository
.
ProvideSessionLimitCache
(
redisClient
,
configConfig
)
sessionLimitCache
:=
repository
.
ProvideSessionLimitCache
(
redisClient
,
configConfig
)
rpmCache
:=
repository
.
NewRPMCache
(
redisClient
)
rpmCache
:=
repository
.
NewRPMCache
(
redisClient
)
groupCapacityService
:=
service
.
NewGroupCapacityService
(
accountRepository
,
groupRepository
,
concurrencyService
,
sessionLimitCache
,
rpmCache
)
groupHandler
:=
admin
.
NewGroupHandler
(
adminService
,
dashboardService
,
groupCapacityService
)
accountHandler
:=
admin
.
NewAccountHandler
(
adminService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
rateLimitService
,
accountUsageService
,
accountTestService
,
concurrencyService
,
crsSyncService
,
sessionLimitCache
,
rpmCache
,
compositeTokenCacheInvalidator
)
accountHandler
:=
admin
.
NewAccountHandler
(
adminService
,
oAuthService
,
openAIOAuthService
,
geminiOAuthService
,
antigravityOAuthService
,
rateLimitService
,
accountUsageService
,
accountTestService
,
concurrencyService
,
crsSyncService
,
sessionLimitCache
,
rpmCache
,
compositeTokenCacheInvalidator
)
adminAnnouncementHandler
:=
admin
.
NewAnnouncementHandler
(
announcementService
)
adminAnnouncementHandler
:=
admin
.
NewAnnouncementHandler
(
announcementService
)
dataManagementService
:=
service
.
NewDataManagementService
()
dataManagementService
:=
service
.
NewDataManagementService
()
...
...
backend/internal/handler/admin/admin_basic_handlers_test.go
View file @
e6326b29
...
@@ -17,7 +17,7 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
...
@@ -17,7 +17,7 @@ func setupAdminRouter() (*gin.Engine, *stubAdminService) {
adminSvc
:=
newStubAdminService
()
adminSvc
:=
newStubAdminService
()
userHandler
:=
NewUserHandler
(
adminSvc
,
nil
)
userHandler
:=
NewUserHandler
(
adminSvc
,
nil
)
groupHandler
:=
NewGroupHandler
(
adminSvc
)
groupHandler
:=
NewGroupHandler
(
adminSvc
,
nil
,
nil
)
proxyHandler
:=
NewProxyHandler
(
adminSvc
)
proxyHandler
:=
NewProxyHandler
(
adminSvc
)
redeemHandler
:=
NewRedeemHandler
(
adminSvc
,
nil
)
redeemHandler
:=
NewRedeemHandler
(
adminSvc
,
nil
)
...
...
backend/internal/handler/admin/group_handler.go
View file @
e6326b29
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/pkg/timezone"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
...
@@ -16,7 +17,9 @@ import (
...
@@ -16,7 +17,9 @@ import (
// GroupHandler handles admin group management
// GroupHandler handles admin group management
type
GroupHandler
struct
{
type
GroupHandler
struct
{
adminService
service
.
AdminService
adminService
service
.
AdminService
dashboardService
*
service
.
DashboardService
groupCapacityService
*
service
.
GroupCapacityService
}
}
type
optionalLimitField
struct
{
type
optionalLimitField
struct
{
...
@@ -69,9 +72,11 @@ func (f optionalLimitField) ToServiceInput() *float64 {
...
@@ -69,9 +72,11 @@ func (f optionalLimitField) ToServiceInput() *float64 {
}
}
// NewGroupHandler creates a new admin group handler
// NewGroupHandler creates a new admin group handler
func
NewGroupHandler
(
adminService
service
.
AdminService
)
*
GroupHandler
{
func
NewGroupHandler
(
adminService
service
.
AdminService
,
dashboardService
*
service
.
DashboardService
,
groupCapacityService
*
service
.
GroupCapacityService
)
*
GroupHandler
{
return
&
GroupHandler
{
return
&
GroupHandler
{
adminService
:
adminService
,
adminService
:
adminService
,
dashboardService
:
dashboardService
,
groupCapacityService
:
groupCapacityService
,
}
}
}
}
...
@@ -363,6 +368,33 @@ func (h *GroupHandler) GetStats(c *gin.Context) {
...
@@ -363,6 +368,33 @@ func (h *GroupHandler) GetStats(c *gin.Context) {
_
=
groupID
// TODO: implement actual stats
_
=
groupID
// TODO: implement actual stats
}
}
// GetUsageSummary returns today's and cumulative cost for all groups.
// GET /api/v1/admin/groups/usage-summary?timezone=Asia/Shanghai
func
(
h
*
GroupHandler
)
GetUsageSummary
(
c
*
gin
.
Context
)
{
userTZ
:=
c
.
Query
(
"timezone"
)
now
:=
timezone
.
NowInUserLocation
(
userTZ
)
todayStart
:=
timezone
.
StartOfDayInUserLocation
(
now
,
userTZ
)
results
,
err
:=
h
.
dashboardService
.
GetGroupUsageSummary
(
c
.
Request
.
Context
(),
todayStart
)
if
err
!=
nil
{
response
.
Error
(
c
,
500
,
"Failed to get group usage summary"
)
return
}
response
.
Success
(
c
,
results
)
}
// GetCapacitySummary returns aggregated capacity (concurrency/sessions/RPM) for all active groups.
// GET /api/v1/admin/groups/capacity-summary
func
(
h
*
GroupHandler
)
GetCapacitySummary
(
c
*
gin
.
Context
)
{
results
,
err
:=
h
.
groupCapacityService
.
GetAllGroupCapacity
(
c
.
Request
.
Context
())
if
err
!=
nil
{
response
.
Error
(
c
,
500
,
"Failed to get group capacity summary"
)
return
}
response
.
Success
(
c
,
results
)
}
// GetGroupAPIKeys handles getting API keys in a group
// GetGroupAPIKeys handles getting API keys in a group
// GET /api/v1/admin/groups/:id/api-keys
// GET /api/v1/admin/groups/:id/api-keys
func
(
h
*
GroupHandler
)
GetGroupAPIKeys
(
c
*
gin
.
Context
)
{
func
(
h
*
GroupHandler
)
GetGroupAPIKeys
(
c
*
gin
.
Context
)
{
...
...
backend/internal/handler/dto/mappers.go
View file @
e6326b29
...
@@ -135,14 +135,16 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
...
@@ -135,14 +135,16 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
return
nil
return
nil
}
}
out
:=
&
AdminGroup
{
out
:=
&
AdminGroup
{
Group
:
groupFromServiceBase
(
g
),
Group
:
groupFromServiceBase
(
g
),
ModelRouting
:
g
.
ModelRouting
,
ModelRouting
:
g
.
ModelRouting
,
ModelRoutingEnabled
:
g
.
ModelRoutingEnabled
,
ModelRoutingEnabled
:
g
.
ModelRoutingEnabled
,
MCPXMLInject
:
g
.
MCPXMLInject
,
MCPXMLInject
:
g
.
MCPXMLInject
,
DefaultMappedModel
:
g
.
DefaultMappedModel
,
DefaultMappedModel
:
g
.
DefaultMappedModel
,
SupportedModelScopes
:
g
.
SupportedModelScopes
,
SupportedModelScopes
:
g
.
SupportedModelScopes
,
AccountCount
:
g
.
AccountCount
,
AccountCount
:
g
.
AccountCount
,
SortOrder
:
g
.
SortOrder
,
ActiveAccountCount
:
g
.
ActiveAccountCount
,
RateLimitedAccountCount
:
g
.
RateLimitedAccountCount
,
SortOrder
:
g
.
SortOrder
,
}
}
if
len
(
g
.
AccountGroups
)
>
0
{
if
len
(
g
.
AccountGroups
)
>
0
{
out
.
AccountGroups
=
make
([]
AccountGroup
,
0
,
len
(
g
.
AccountGroups
))
out
.
AccountGroups
=
make
([]
AccountGroup
,
0
,
len
(
g
.
AccountGroups
))
...
...
backend/internal/handler/dto/types.go
View file @
e6326b29
...
@@ -122,9 +122,11 @@ type AdminGroup struct {
...
@@ -122,9 +122,11 @@ type AdminGroup struct {
DefaultMappedModel
string
`json:"default_mapped_model"`
DefaultMappedModel
string
`json:"default_mapped_model"`
// 支持的模型系列(仅 antigravity 平台使用)
// 支持的模型系列(仅 antigravity 平台使用)
SupportedModelScopes
[]
string
`json:"supported_model_scopes"`
SupportedModelScopes
[]
string
`json:"supported_model_scopes"`
AccountGroups
[]
AccountGroup
`json:"account_groups,omitempty"`
AccountGroups
[]
AccountGroup
`json:"account_groups,omitempty"`
AccountCount
int64
`json:"account_count,omitempty"`
AccountCount
int64
`json:"account_count,omitempty"`
ActiveAccountCount
int64
`json:"active_account_count,omitempty"`
RateLimitedAccountCount
int64
`json:"rate_limited_account_count,omitempty"`
// 分组排序
// 分组排序
SortOrder
int
`json:"sort_order"`
SortOrder
int
`json:"sort_order"`
...
...
backend/internal/handler/gateway_handler_warmup_intercept_unit_test.go
View file @
e6326b29
...
@@ -76,7 +76,7 @@ func (f *fakeGroupRepo) ListActiveByPlatform(context.Context, string) ([]service
...
@@ -76,7 +76,7 @@ func (f *fakeGroupRepo) ListActiveByPlatform(context.Context, string) ([]service
return
nil
,
nil
return
nil
,
nil
}
}
func
(
f
*
fakeGroupRepo
)
ExistsByName
(
context
.
Context
,
string
)
(
bool
,
error
)
{
return
false
,
nil
}
func
(
f
*
fakeGroupRepo
)
ExistsByName
(
context
.
Context
,
string
)
(
bool
,
error
)
{
return
false
,
nil
}
func
(
f
*
fakeGroupRepo
)
GetAccountCount
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
return
0
,
nil
}
func
(
f
*
fakeGroupRepo
)
GetAccountCount
(
context
.
Context
,
int64
)
(
int64
,
int64
,
error
)
{
return
0
,
0
,
nil
}
func
(
f
*
fakeGroupRepo
)
DeleteAccountGroupsByGroupID
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
func
(
f
*
fakeGroupRepo
)
DeleteAccountGroupsByGroupID
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
return
0
,
nil
return
0
,
nil
}
}
...
...
backend/internal/handler/sora_gateway_handler_test.go
View file @
e6326b29
...
@@ -273,8 +273,8 @@ func (r *stubGroupRepo) ListActiveByPlatform(ctx context.Context, platform strin
...
@@ -273,8 +273,8 @@ func (r *stubGroupRepo) ListActiveByPlatform(ctx context.Context, platform strin
func
(
r
*
stubGroupRepo
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
func
(
r
*
stubGroupRepo
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
return
false
,
nil
return
false
,
nil
}
}
func
(
r
*
stubGroupRepo
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
r
*
stubGroupRepo
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
int64
,
error
)
{
return
0
,
nil
return
0
,
0
,
nil
}
}
func
(
r
*
stubGroupRepo
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
r
*
stubGroupRepo
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
return
0
,
nil
return
0
,
nil
...
@@ -348,6 +348,9 @@ func (s *stubUsageLogRepo) GetGroupStatsWithFilters(ctx context.Context, startTi
...
@@ -348,6 +348,9 @@ func (s *stubUsageLogRepo) GetGroupStatsWithFilters(ctx context.Context, startTi
func
(
s
*
stubUsageLogRepo
)
GetUserBreakdownStats
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
dim
usagestats
.
UserBreakdownDimension
,
limit
int
)
([]
usagestats
.
UserBreakdownItem
,
error
)
{
func
(
s
*
stubUsageLogRepo
)
GetUserBreakdownStats
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
dim
usagestats
.
UserBreakdownDimension
,
limit
int
)
([]
usagestats
.
UserBreakdownItem
,
error
)
{
return
nil
,
nil
return
nil
,
nil
}
}
func
(
s
*
stubUsageLogRepo
)
GetAllGroupUsageSummary
(
ctx
context
.
Context
,
todayStart
time
.
Time
)
([]
usagestats
.
GroupUsageSummary
,
error
)
{
return
nil
,
nil
}
func
(
s
*
stubUsageLogRepo
)
GetAPIKeyUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
APIKeyUsageTrendPoint
,
error
)
{
func
(
s
*
stubUsageLogRepo
)
GetAPIKeyUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
APIKeyUsageTrendPoint
,
error
)
{
return
nil
,
nil
return
nil
,
nil
}
}
...
...
backend/internal/pkg/usagestats/usage_log_types.go
View file @
e6326b29
...
@@ -90,6 +90,13 @@ type EndpointStat struct {
...
@@ -90,6 +90,13 @@ type EndpointStat struct {
ActualCost
float64
`json:"actual_cost"`
// 实际扣除
ActualCost
float64
`json:"actual_cost"`
// 实际扣除
}
}
// GroupUsageSummary represents today's and cumulative cost for a single group.
type
GroupUsageSummary
struct
{
GroupID
int64
`json:"group_id"`
TodayCost
float64
`json:"today_cost"`
TotalCost
float64
`json:"total_cost"`
}
// GroupStat represents usage statistics for a single group
// GroupStat represents usage statistics for a single group
type
GroupStat
struct
{
type
GroupStat
struct
{
GroupID
int64
`json:"group_id"`
GroupID
int64
`json:"group_id"`
...
...
backend/internal/repository/group_repo.go
View file @
e6326b29
...
@@ -88,8 +88,9 @@ func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group
...
@@ -88,8 +88,9 @@ func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
count
,
_
:=
r
.
GetAccountCount
(
ctx
,
out
.
ID
)
total
,
active
,
_
:=
r
.
GetAccountCount
(
ctx
,
out
.
ID
)
out
.
AccountCount
=
count
out
.
AccountCount
=
total
out
.
ActiveAccountCount
=
active
return
out
,
nil
return
out
,
nil
}
}
...
@@ -256,7 +257,10 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
...
@@ -256,7 +257,10 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
if
err
==
nil
{
if
err
==
nil
{
for
i
:=
range
outGroups
{
for
i
:=
range
outGroups
{
outGroups
[
i
]
.
AccountCount
=
counts
[
outGroups
[
i
]
.
ID
]
c
:=
counts
[
outGroups
[
i
]
.
ID
]
outGroups
[
i
]
.
AccountCount
=
c
.
Total
outGroups
[
i
]
.
ActiveAccountCount
=
c
.
Active
outGroups
[
i
]
.
RateLimitedAccountCount
=
c
.
RateLimited
}
}
}
}
...
@@ -283,7 +287,10 @@ func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, erro
...
@@ -283,7 +287,10 @@ func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, erro
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
if
err
==
nil
{
if
err
==
nil
{
for
i
:=
range
outGroups
{
for
i
:=
range
outGroups
{
outGroups
[
i
]
.
AccountCount
=
counts
[
outGroups
[
i
]
.
ID
]
c
:=
counts
[
outGroups
[
i
]
.
ID
]
outGroups
[
i
]
.
AccountCount
=
c
.
Total
outGroups
[
i
]
.
ActiveAccountCount
=
c
.
Active
outGroups
[
i
]
.
RateLimitedAccountCount
=
c
.
RateLimited
}
}
}
}
...
@@ -310,7 +317,10 @@ func (r *groupRepository) ListActiveByPlatform(ctx context.Context, platform str
...
@@ -310,7 +317,10 @@ func (r *groupRepository) ListActiveByPlatform(ctx context.Context, platform str
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
counts
,
err
:=
r
.
loadAccountCounts
(
ctx
,
groupIDs
)
if
err
==
nil
{
if
err
==
nil
{
for
i
:=
range
outGroups
{
for
i
:=
range
outGroups
{
outGroups
[
i
]
.
AccountCount
=
counts
[
outGroups
[
i
]
.
ID
]
c
:=
counts
[
outGroups
[
i
]
.
ID
]
outGroups
[
i
]
.
AccountCount
=
c
.
Total
outGroups
[
i
]
.
ActiveAccountCount
=
c
.
Active
outGroups
[
i
]
.
RateLimitedAccountCount
=
c
.
RateLimited
}
}
}
}
...
@@ -369,12 +379,20 @@ func (r *groupRepository) ExistsByIDs(ctx context.Context, ids []int64) (map[int
...
@@ -369,12 +379,20 @@ func (r *groupRepository) ExistsByIDs(ctx context.Context, ids []int64) (map[int
return
result
,
nil
return
result
,
nil
}
}
func
(
r
*
groupRepository
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
r
*
groupRepository
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
total
int64
,
active
int64
,
err
error
)
{
var
count
int64
var
rateLimited
int64
if
err
:=
scanSingleRow
(
ctx
,
r
.
sql
,
"SELECT COUNT(*) FROM account_groups WHERE group_id = $1"
,
[]
any
{
groupID
},
&
count
);
err
!=
nil
{
err
=
scanSingleRow
(
ctx
,
r
.
sql
,
return
0
,
err
`SELECT COUNT(*),
}
COUNT(*) FILTER (WHERE a.status = 'active' AND a.schedulable = true),
return
count
,
nil
COUNT(*) FILTER (WHERE a.status = 'active' AND (
a.rate_limit_reset_at > NOW() OR
a.overload_until > NOW() OR
a.temp_unschedulable_until > NOW()
))
FROM account_groups ag JOIN accounts a ON a.id = ag.account_id
WHERE ag.group_id = $1`
,
[]
any
{
groupID
},
&
total
,
&
active
,
&
rateLimited
)
return
}
}
func
(
r
*
groupRepository
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
r
*
groupRepository
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
...
@@ -500,15 +518,32 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64,
...
@@ -500,15 +518,32 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64,
return
affectedUserIDs
,
nil
return
affectedUserIDs
,
nil
}
}
func
(
r
*
groupRepository
)
loadAccountCounts
(
ctx
context
.
Context
,
groupIDs
[]
int64
)
(
counts
map
[
int64
]
int64
,
err
error
)
{
type
groupAccountCounts
struct
{
counts
=
make
(
map
[
int64
]
int64
,
len
(
groupIDs
))
Total
int64
Active
int64
RateLimited
int64
}
func
(
r
*
groupRepository
)
loadAccountCounts
(
ctx
context
.
Context
,
groupIDs
[]
int64
)
(
counts
map
[
int64
]
groupAccountCounts
,
err
error
)
{
counts
=
make
(
map
[
int64
]
groupAccountCounts
,
len
(
groupIDs
))
if
len
(
groupIDs
)
==
0
{
if
len
(
groupIDs
)
==
0
{
return
counts
,
nil
return
counts
,
nil
}
}
rows
,
err
:=
r
.
sql
.
QueryContext
(
rows
,
err
:=
r
.
sql
.
QueryContext
(
ctx
,
ctx
,
"SELECT group_id, COUNT(*) FROM account_groups WHERE group_id = ANY($1) GROUP BY group_id"
,
`SELECT ag.group_id,
COUNT(*) AS total,
COUNT(*) FILTER (WHERE a.status = 'active' AND a.schedulable = true) AS active,
COUNT(*) FILTER (WHERE a.status = 'active' AND (
a.rate_limit_reset_at > NOW() OR
a.overload_until > NOW() OR
a.temp_unschedulable_until > NOW()
)) AS rate_limited
FROM account_groups ag
JOIN accounts a ON a.id = ag.account_id
WHERE ag.group_id = ANY($1)
GROUP BY ag.group_id`
,
pq
.
Array
(
groupIDs
),
pq
.
Array
(
groupIDs
),
)
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -523,11 +558,11 @@ func (r *groupRepository) loadAccountCounts(ctx context.Context, groupIDs []int6
...
@@ -523,11 +558,11 @@ func (r *groupRepository) loadAccountCounts(ctx context.Context, groupIDs []int6
for
rows
.
Next
()
{
for
rows
.
Next
()
{
var
groupID
int64
var
groupID
int64
var
c
ount
int64
var
c
groupAccountCounts
if
err
=
rows
.
Scan
(
&
groupID
,
&
c
ount
);
err
!=
nil
{
if
err
=
rows
.
Scan
(
&
groupID
,
&
c
.
Total
,
&
c
.
Active
,
&
c
.
RateLimited
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
counts
[
groupID
]
=
c
ount
counts
[
groupID
]
=
c
}
}
if
err
=
rows
.
Err
();
err
!=
nil
{
if
err
=
rows
.
Err
();
err
!=
nil
{
return
nil
,
err
return
nil
,
err
...
...
backend/internal/repository/group_repo_integration_test.go
View file @
e6326b29
...
@@ -603,7 +603,7 @@ func (s *GroupRepoSuite) TestGetAccountCount() {
...
@@ -603,7 +603,7 @@ func (s *GroupRepoSuite) TestGetAccountCount() {
_
,
err
=
s
.
tx
.
ExecContext
(
s
.
ctx
,
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())"
,
a2
,
group
.
ID
,
2
)
_
,
err
=
s
.
tx
.
ExecContext
(
s
.
ctx
,
"INSERT INTO account_groups (account_id, group_id, priority, created_at) VALUES ($1, $2, $3, NOW())"
,
a2
,
group
.
ID
,
2
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
NoError
(
err
)
count
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
group
.
ID
)
count
,
_
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
group
.
ID
)
s
.
Require
()
.
NoError
(
err
,
"GetAccountCount"
)
s
.
Require
()
.
NoError
(
err
,
"GetAccountCount"
)
s
.
Require
()
.
Equal
(
int64
(
2
),
count
)
s
.
Require
()
.
Equal
(
int64
(
2
),
count
)
}
}
...
@@ -619,7 +619,7 @@ func (s *GroupRepoSuite) TestGetAccountCount_Empty() {
...
@@ -619,7 +619,7 @@ func (s *GroupRepoSuite) TestGetAccountCount_Empty() {
}
}
s
.
Require
()
.
NoError
(
s
.
repo
.
Create
(
s
.
ctx
,
group
))
s
.
Require
()
.
NoError
(
s
.
repo
.
Create
(
s
.
ctx
,
group
))
count
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
group
.
ID
)
count
,
_
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
group
.
ID
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Zero
(
count
)
s
.
Require
()
.
Zero
(
count
)
}
}
...
@@ -651,7 +651,7 @@ func (s *GroupRepoSuite) TestDeleteAccountGroupsByGroupID() {
...
@@ -651,7 +651,7 @@ func (s *GroupRepoSuite) TestDeleteAccountGroupsByGroupID() {
s
.
Require
()
.
NoError
(
err
,
"DeleteAccountGroupsByGroupID"
)
s
.
Require
()
.
NoError
(
err
,
"DeleteAccountGroupsByGroupID"
)
s
.
Require
()
.
Equal
(
int64
(
1
),
affected
,
"expected 1 affected row"
)
s
.
Require
()
.
Equal
(
int64
(
1
),
affected
,
"expected 1 affected row"
)
count
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
g
.
ID
)
count
,
_
,
err
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
g
.
ID
)
s
.
Require
()
.
NoError
(
err
,
"GetAccountCount"
)
s
.
Require
()
.
NoError
(
err
,
"GetAccountCount"
)
s
.
Require
()
.
Equal
(
int64
(
0
),
count
,
"expected 0 account groups"
)
s
.
Require
()
.
Equal
(
int64
(
0
),
count
,
"expected 0 account groups"
)
}
}
...
@@ -692,7 +692,7 @@ func (s *GroupRepoSuite) TestDeleteAccountGroupsByGroupID_MultipleAccounts() {
...
@@ -692,7 +692,7 @@ func (s *GroupRepoSuite) TestDeleteAccountGroupsByGroupID_MultipleAccounts() {
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
NoError
(
err
)
s
.
Require
()
.
Equal
(
int64
(
3
),
affected
)
s
.
Require
()
.
Equal
(
int64
(
3
),
affected
)
count
,
_
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
g
.
ID
)
count
,
_
,
_
:=
s
.
repo
.
GetAccountCount
(
s
.
ctx
,
g
.
ID
)
s
.
Require
()
.
Zero
(
count
)
s
.
Require
()
.
Zero
(
count
)
}
}
...
...
backend/internal/repository/usage_log_repo.go
View file @
e6326b29
...
@@ -3067,6 +3067,41 @@ func (r *usageLogRepository) GetUserBreakdownStats(ctx context.Context, startTim
...
@@ -3067,6 +3067,41 @@ func (r *usageLogRepository) GetUserBreakdownStats(ctx context.Context, startTim
return
results
,
nil
return
results
,
nil
}
}
// GetAllGroupUsageSummary returns today's and cumulative actual_cost for every group.
// todayStart is the start-of-day in the caller's timezone (UTC-based).
// TODO(perf): This query scans ALL usage_logs rows for total_cost aggregation.
// When usage_logs exceeds ~1M rows, consider adding a short-lived cache (30s)
// or a materialized view / pre-aggregation table for cumulative costs.
func
(
r
*
usageLogRepository
)
GetAllGroupUsageSummary
(
ctx
context
.
Context
,
todayStart
time
.
Time
)
([]
usagestats
.
GroupUsageSummary
,
error
)
{
query
:=
`
SELECT
g.id AS group_id,
COALESCE(SUM(ul.actual_cost), 0) AS total_cost,
COALESCE(SUM(CASE WHEN ul.created_at >= $1 THEN ul.actual_cost ELSE 0 END), 0) AS today_cost
FROM groups g
LEFT JOIN usage_logs ul ON ul.group_id = g.id
GROUP BY g.id
`
rows
,
err
:=
r
.
sql
.
QueryContext
(
ctx
,
query
,
todayStart
)
if
err
!=
nil
{
return
nil
,
err
}
defer
func
()
{
_
=
rows
.
Close
()
}()
var
results
[]
usagestats
.
GroupUsageSummary
for
rows
.
Next
()
{
var
row
usagestats
.
GroupUsageSummary
if
err
:=
rows
.
Scan
(
&
row
.
GroupID
,
&
row
.
TotalCost
,
&
row
.
TodayCost
);
err
!=
nil
{
return
nil
,
err
}
results
=
append
(
results
,
row
)
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
return
nil
,
err
}
return
results
,
nil
}
// resolveEndpointColumn maps endpoint type to the corresponding DB column name.
// resolveEndpointColumn maps endpoint type to the corresponding DB column name.
func
resolveEndpointColumn
(
endpointType
string
)
string
{
func
resolveEndpointColumn
(
endpointType
string
)
string
{
switch
endpointType
{
switch
endpointType
{
...
...
backend/internal/server/api_contract_test.go
View file @
e6326b29
...
@@ -924,8 +924,8 @@ func (stubGroupRepo) ExistsByName(ctx context.Context, name string) (bool, error
...
@@ -924,8 +924,8 @@ func (stubGroupRepo) ExistsByName(ctx context.Context, name string) (bool, error
return
false
,
errors
.
New
(
"not implemented"
)
return
false
,
errors
.
New
(
"not implemented"
)
}
}
func
(
stubGroupRepo
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
stubGroupRepo
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
int64
,
error
)
{
return
0
,
errors
.
New
(
"not implemented"
)
return
0
,
0
,
errors
.
New
(
"not implemented"
)
}
}
func
(
stubGroupRepo
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
stubGroupRepo
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
...
@@ -1786,6 +1786,9 @@ func (r *stubUsageLogRepo) GetAccountUsageStats(ctx context.Context, accountID i
...
@@ -1786,6 +1786,9 @@ func (r *stubUsageLogRepo) GetAccountUsageStats(ctx context.Context, accountID i
func
(
r
*
stubUsageLogRepo
)
GetStatsWithFilters
(
ctx
context
.
Context
,
filters
usagestats
.
UsageLogFilters
)
(
*
usagestats
.
UsageStats
,
error
)
{
func
(
r
*
stubUsageLogRepo
)
GetStatsWithFilters
(
ctx
context
.
Context
,
filters
usagestats
.
UsageLogFilters
)
(
*
usagestats
.
UsageStats
,
error
)
{
return
nil
,
errors
.
New
(
"not implemented"
)
return
nil
,
errors
.
New
(
"not implemented"
)
}
}
func
(
r
*
stubUsageLogRepo
)
GetAllGroupUsageSummary
(
ctx
context
.
Context
,
todayStart
time
.
Time
)
([]
usagestats
.
GroupUsageSummary
,
error
)
{
return
nil
,
errors
.
New
(
"not implemented"
)
}
type
stubSettingRepo
struct
{
type
stubSettingRepo
struct
{
all
map
[
string
]
string
all
map
[
string
]
string
...
...
backend/internal/server/routes/admin.go
View file @
e6326b29
...
@@ -227,6 +227,8 @@ func registerGroupRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
...
@@ -227,6 +227,8 @@ func registerGroupRoutes(admin *gin.RouterGroup, h *handler.Handlers) {
{
{
groups
.
GET
(
""
,
h
.
Admin
.
Group
.
List
)
groups
.
GET
(
""
,
h
.
Admin
.
Group
.
List
)
groups
.
GET
(
"/all"
,
h
.
Admin
.
Group
.
GetAll
)
groups
.
GET
(
"/all"
,
h
.
Admin
.
Group
.
GetAll
)
groups
.
GET
(
"/usage-summary"
,
h
.
Admin
.
Group
.
GetUsageSummary
)
groups
.
GET
(
"/capacity-summary"
,
h
.
Admin
.
Group
.
GetCapacitySummary
)
groups
.
PUT
(
"/sort-order"
,
h
.
Admin
.
Group
.
UpdateSortOrder
)
groups
.
PUT
(
"/sort-order"
,
h
.
Admin
.
Group
.
UpdateSortOrder
)
groups
.
GET
(
"/:id"
,
h
.
Admin
.
Group
.
GetByID
)
groups
.
GET
(
"/:id"
,
h
.
Admin
.
Group
.
GetByID
)
groups
.
POST
(
""
,
h
.
Admin
.
Group
.
Create
)
groups
.
POST
(
""
,
h
.
Admin
.
Group
.
Create
)
...
...
backend/internal/service/account_usage_service.go
View file @
e6326b29
...
@@ -49,6 +49,7 @@ type UsageLogRepository interface {
...
@@ -49,6 +49,7 @@ type UsageLogRepository interface {
GetUpstreamEndpointStatsWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
userID
,
apiKeyID
,
accountID
,
groupID
int64
,
model
string
,
requestType
*
int16
,
stream
*
bool
,
billingType
*
int8
)
([]
usagestats
.
EndpointStat
,
error
)
GetUpstreamEndpointStatsWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
userID
,
apiKeyID
,
accountID
,
groupID
int64
,
model
string
,
requestType
*
int16
,
stream
*
bool
,
billingType
*
int8
)
([]
usagestats
.
EndpointStat
,
error
)
GetGroupStatsWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
userID
,
apiKeyID
,
accountID
,
groupID
int64
,
requestType
*
int16
,
stream
*
bool
,
billingType
*
int8
)
([]
usagestats
.
GroupStat
,
error
)
GetGroupStatsWithFilters
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
userID
,
apiKeyID
,
accountID
,
groupID
int64
,
requestType
*
int16
,
stream
*
bool
,
billingType
*
int8
)
([]
usagestats
.
GroupStat
,
error
)
GetUserBreakdownStats
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
dim
usagestats
.
UserBreakdownDimension
,
limit
int
)
([]
usagestats
.
UserBreakdownItem
,
error
)
GetUserBreakdownStats
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
dim
usagestats
.
UserBreakdownDimension
,
limit
int
)
([]
usagestats
.
UserBreakdownItem
,
error
)
GetAllGroupUsageSummary
(
ctx
context
.
Context
,
todayStart
time
.
Time
)
([]
usagestats
.
GroupUsageSummary
,
error
)
GetAPIKeyUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
APIKeyUsageTrendPoint
,
error
)
GetAPIKeyUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
APIKeyUsageTrendPoint
,
error
)
GetUserUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
UserUsageTrendPoint
,
error
)
GetUserUsageTrend
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
granularity
string
,
limit
int
)
([]
usagestats
.
UserUsageTrendPoint
,
error
)
GetUserSpendingRanking
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
limit
int
)
(
*
usagestats
.
UserSpendingRankingResponse
,
error
)
GetUserSpendingRanking
(
ctx
context
.
Context
,
startTime
,
endTime
time
.
Time
,
limit
int
)
(
*
usagestats
.
UserSpendingRankingResponse
,
error
)
...
...
backend/internal/service/admin_service_apikey_test.go
View file @
e6326b29
...
@@ -194,7 +194,7 @@ func (s *groupRepoStubForGroupUpdate) ListActiveByPlatform(context.Context, stri
...
@@ -194,7 +194,7 @@ func (s *groupRepoStubForGroupUpdate) ListActiveByPlatform(context.Context, stri
func
(
s
*
groupRepoStubForGroupUpdate
)
ExistsByName
(
context
.
Context
,
string
)
(
bool
,
error
)
{
func
(
s
*
groupRepoStubForGroupUpdate
)
ExistsByName
(
context
.
Context
,
string
)
(
bool
,
error
)
{
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
func
(
s
*
groupRepoStubForGroupUpdate
)
GetAccountCount
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStubForGroupUpdate
)
GetAccountCount
(
context
.
Context
,
int64
)
(
int64
,
int64
,
error
)
{
panic
(
"unexpected"
)
panic
(
"unexpected"
)
}
}
func
(
s
*
groupRepoStubForGroupUpdate
)
DeleteAccountGroupsByGroupID
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStubForGroupUpdate
)
DeleteAccountGroupsByGroupID
(
context
.
Context
,
int64
)
(
int64
,
error
)
{
...
...
backend/internal/service/admin_service_delete_test.go
View file @
e6326b29
...
@@ -160,7 +160,7 @@ func (s *groupRepoStub) ExistsByName(ctx context.Context, name string) (bool, er
...
@@ -160,7 +160,7 @@ func (s *groupRepoStub) ExistsByName(ctx context.Context, name string) (bool, er
panic
(
"unexpected ExistsByName call"
)
panic
(
"unexpected ExistsByName call"
)
}
}
func
(
s
*
groupRepoStub
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStub
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
int64
,
error
)
{
panic
(
"unexpected GetAccountCount call"
)
panic
(
"unexpected GetAccountCount call"
)
}
}
...
...
backend/internal/service/admin_service_group_test.go
View file @
e6326b29
...
@@ -100,7 +100,7 @@ func (s *groupRepoStubForAdmin) ExistsByName(_ context.Context, _ string) (bool,
...
@@ -100,7 +100,7 @@ func (s *groupRepoStubForAdmin) ExistsByName(_ context.Context, _ string) (bool,
panic
(
"unexpected ExistsByName call"
)
panic
(
"unexpected ExistsByName call"
)
}
}
func
(
s
*
groupRepoStubForAdmin
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStubForAdmin
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
int64
,
error
)
{
panic
(
"unexpected GetAccountCount call"
)
panic
(
"unexpected GetAccountCount call"
)
}
}
...
@@ -383,7 +383,7 @@ func (s *groupRepoStubForFallbackCycle) ExistsByName(_ context.Context, _ string
...
@@ -383,7 +383,7 @@ func (s *groupRepoStubForFallbackCycle) ExistsByName(_ context.Context, _ string
panic
(
"unexpected ExistsByName call"
)
panic
(
"unexpected ExistsByName call"
)
}
}
func
(
s
*
groupRepoStubForFallbackCycle
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStubForFallbackCycle
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
int64
,
error
)
{
panic
(
"unexpected GetAccountCount call"
)
panic
(
"unexpected GetAccountCount call"
)
}
}
...
@@ -458,7 +458,7 @@ func (s *groupRepoStubForInvalidRequestFallback) ExistsByName(_ context.Context,
...
@@ -458,7 +458,7 @@ func (s *groupRepoStubForInvalidRequestFallback) ExistsByName(_ context.Context,
panic
(
"unexpected ExistsByName call"
)
panic
(
"unexpected ExistsByName call"
)
}
}
func
(
s
*
groupRepoStubForInvalidRequestFallback
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
error
)
{
func
(
s
*
groupRepoStubForInvalidRequestFallback
)
GetAccountCount
(
_
context
.
Context
,
_
int64
)
(
int64
,
int64
,
error
)
{
panic
(
"unexpected GetAccountCount call"
)
panic
(
"unexpected GetAccountCount call"
)
}
}
...
...
backend/internal/service/dashboard_service.go
View file @
e6326b29
...
@@ -148,6 +148,15 @@ func (s *DashboardService) GetGroupStatsWithFilters(ctx context.Context, startTi
...
@@ -148,6 +148,15 @@ func (s *DashboardService) GetGroupStatsWithFilters(ctx context.Context, startTi
return
stats
,
nil
return
stats
,
nil
}
}
// GetGroupUsageSummary returns today's and cumulative cost for all groups.
func
(
s
*
DashboardService
)
GetGroupUsageSummary
(
ctx
context
.
Context
,
todayStart
time
.
Time
)
([]
usagestats
.
GroupUsageSummary
,
error
)
{
results
,
err
:=
s
.
usageRepo
.
GetAllGroupUsageSummary
(
ctx
,
todayStart
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"get group usage summary: %w"
,
err
)
}
return
results
,
nil
}
func
(
s
*
DashboardService
)
getCachedDashboardStats
(
ctx
context
.
Context
)
(
*
usagestats
.
DashboardStats
,
bool
,
error
)
{
func
(
s
*
DashboardService
)
getCachedDashboardStats
(
ctx
context
.
Context
)
(
*
usagestats
.
DashboardStats
,
bool
,
error
)
{
data
,
err
:=
s
.
cache
.
GetDashboardStats
(
ctx
)
data
,
err
:=
s
.
cache
.
GetDashboardStats
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
backend/internal/service/gateway_multiplatform_test.go
View file @
e6326b29
...
@@ -278,8 +278,8 @@ func (m *mockGroupRepoForGateway) ListActiveByPlatform(ctx context.Context, plat
...
@@ -278,8 +278,8 @@ func (m *mockGroupRepoForGateway) ListActiveByPlatform(ctx context.Context, plat
func
(
m
*
mockGroupRepoForGateway
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
func
(
m
*
mockGroupRepoForGateway
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
return
false
,
nil
return
false
,
nil
}
}
func
(
m
*
mockGroupRepoForGateway
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
m
*
mockGroupRepoForGateway
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
int64
,
error
)
{
return
0
,
nil
return
0
,
0
,
nil
}
}
func
(
m
*
mockGroupRepoForGateway
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
m
*
mockGroupRepoForGateway
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
return
0
,
nil
return
0
,
nil
...
...
backend/internal/service/gemini_multiplatform_test.go
View file @
e6326b29
...
@@ -230,8 +230,8 @@ func (m *mockGroupRepoForGemini) ListActiveByPlatform(ctx context.Context, platf
...
@@ -230,8 +230,8 @@ func (m *mockGroupRepoForGemini) ListActiveByPlatform(ctx context.Context, platf
func
(
m
*
mockGroupRepoForGemini
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
func
(
m
*
mockGroupRepoForGemini
)
ExistsByName
(
ctx
context
.
Context
,
name
string
)
(
bool
,
error
)
{
return
false
,
nil
return
false
,
nil
}
}
func
(
m
*
mockGroupRepoForGemini
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
m
*
mockGroupRepoForGemini
)
GetAccountCount
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
int64
,
error
)
{
return
0
,
nil
return
0
,
0
,
nil
}
}
func
(
m
*
mockGroupRepoForGemini
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
func
(
m
*
mockGroupRepoForGemini
)
DeleteAccountGroupsByGroupID
(
ctx
context
.
Context
,
groupID
int64
)
(
int64
,
error
)
{
return
0
,
nil
return
0
,
nil
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment