Skip to content

Commit 0e642d9

Browse files
committed
feat: 增加账号配额看板与共享邀请结算支持
1 parent 3bac382 commit 0e642d9

25 files changed

Lines changed: 1251 additions & 182 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ docs/*
132132
!docs/PAYMENT_CN.md
133133
!docs/ADMIN_PAYMENT_INTEGRATION_API.md
134134
!docs/OFFICIAL_UPDATE_AND_DEPLOY_CN.md
135+
!docs/LOCAL_IMAGE_UPLOAD_DEPLOY_CN.md
135136
.serena/
136137
.codex/
137138
frontend/coverage/

backend/internal/handler/admin/account_handler.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,18 @@ func ifNoneMatchMatched(ifNoneMatch, etag string) bool {
456456
return false
457457
}
458458

459+
// GetQuotaDashboard returns account quota summaries grouped by platform and account type.
460+
// GET /api/v1/admin/accounts/quota-dashboard
461+
func (h *AccountHandler) GetQuotaDashboard(c *gin.Context) {
462+
dashboard, err := h.adminService.GetAccountQuotaDashboard(c.Request.Context())
463+
if err != nil {
464+
response.ErrorFrom(c, err)
465+
return
466+
}
467+
468+
response.Success(c, dashboard)
469+
}
470+
459471
// GetByID handles getting an account by ID
460472
// GET /api/v1/admin/accounts/:id
461473
func (h *AccountHandler) GetByID(c *gin.Context) {

backend/internal/handler/admin/account_share_policy_handler.go

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,23 @@ func NewAccountSharePolicyHandler(service *service.AccountSharePolicyService) *A
2222
}
2323

2424
type createAccountSharePolicyRequest struct {
25-
ScopeType string `json:"scope_type" binding:"omitempty,oneof=global"`
26-
ScopeID *int64 `json:"scope_id"`
27-
Platform *string `json:"platform"`
28-
OwnerShareRatio *float64 `json:"owner_share_ratio" binding:"required,gte=0,lte=1"`
29-
Enabled *bool `json:"enabled"`
30-
EffectiveAt *time.Time `json:"effective_at"`
25+
ScopeType string `json:"scope_type" binding:"omitempty,oneof=global"`
26+
ScopeID *int64 `json:"scope_id"`
27+
Platform *string `json:"platform"`
28+
OwnerShareRatio *float64 `json:"owner_share_ratio" binding:"required,gte=0,lte=1"`
29+
InviteShareRatio *float64 `json:"invite_share_ratio" binding:"omitempty,gte=0,lte=1"`
30+
Enabled *bool `json:"enabled"`
31+
EffectiveAt *time.Time `json:"effective_at"`
3132
}
3233

3334
type updateAccountSharePolicyRequest struct {
34-
ScopeType *string `json:"scope_type" binding:"omitempty,oneof=global"`
35-
ScopeID *int64 `json:"scope_id"`
36-
Platform *string `json:"platform"`
37-
OwnerShareRatio *float64 `json:"owner_share_ratio" binding:"omitempty,gte=0,lte=1"`
38-
Enabled *bool `json:"enabled"`
39-
EffectiveAt *time.Time `json:"effective_at"`
35+
ScopeType *string `json:"scope_type" binding:"omitempty,oneof=global"`
36+
ScopeID *int64 `json:"scope_id"`
37+
Platform *string `json:"platform"`
38+
OwnerShareRatio *float64 `json:"owner_share_ratio" binding:"omitempty,gte=0,lte=1"`
39+
InviteShareRatio *float64 `json:"invite_share_ratio" binding:"omitempty,gte=0,lte=1"`
40+
Enabled *bool `json:"enabled"`
41+
EffectiveAt *time.Time `json:"effective_at"`
4042
}
4143

4244
func (h *AccountSharePolicyHandler) List(c *gin.Context) {
@@ -95,6 +97,7 @@ func (h *AccountSharePolicyHandler) Create(c *gin.Context) {
9597
ScopeID: req.ScopeID,
9698
Platform: req.Platform,
9799
OwnerShareRatio: *req.OwnerShareRatio,
100+
InviteShareRatio: valueOrZeroFloat64(req.InviteShareRatio),
98101
Enabled: req.Enabled,
99102
EffectiveAt: req.EffectiveAt,
100103
CreatedByAdminID: adminID,
@@ -117,12 +120,13 @@ func (h *AccountSharePolicyHandler) Update(c *gin.Context) {
117120
return
118121
}
119122
policy, err := h.service.Update(c.Request.Context(), id, service.UpdateAccountSharePolicyInput{
120-
ScopeType: req.ScopeType,
121-
ScopeID: req.ScopeID,
122-
Platform: req.Platform,
123-
OwnerShareRatio: req.OwnerShareRatio,
124-
Enabled: req.Enabled,
125-
EffectiveAt: req.EffectiveAt,
123+
ScopeType: req.ScopeType,
124+
ScopeID: req.ScopeID,
125+
Platform: req.Platform,
126+
OwnerShareRatio: req.OwnerShareRatio,
127+
InviteShareRatio: req.InviteShareRatio,
128+
Enabled: req.Enabled,
129+
EffectiveAt: req.EffectiveAt,
126130
})
127131
if err != nil {
128132
response.ErrorFrom(c, err)
@@ -131,6 +135,13 @@ func (h *AccountSharePolicyHandler) Update(c *gin.Context) {
131135
response.Success(c, policy)
132136
}
133137

138+
func valueOrZeroFloat64(v *float64) float64 {
139+
if v == nil {
140+
return 0
141+
}
142+
return *v
143+
}
144+
134145
func (h *AccountSharePolicyHandler) Delete(c *gin.Context) {
135146
id, ok := parseAccountSharePolicyID(c)
136147
if !ok {

backend/internal/handler/admin/admin_service_stub_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,25 @@ func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int,
308308
return s.accounts, int64(len(s.accounts)), nil
309309
}
310310

311+
func (s *stubAdminService) GetAccountQuotaDashboard(ctx context.Context) (*service.AccountQuotaDashboard, error) {
312+
now := time.Now().UTC()
313+
return &service.AccountQuotaDashboard{
314+
GeneratedAt: now,
315+
Summaries: []service.AccountQuotaSummary{
316+
{
317+
Platform: service.PlatformAnthropic,
318+
Type: service.AccountTypeOAuth,
319+
AccountCount: len(s.accounts),
320+
},
321+
},
322+
Totals: service.AccountQuotaSummary{
323+
Platform: "all",
324+
Type: "all",
325+
AccountCount: len(s.accounts),
326+
},
327+
}, nil
328+
}
329+
311330
func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
312331
account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
313332
return &account, nil

backend/internal/repository/account_share_policy_repo.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (r *accountSharePolicyRepository) ListAccountSharePolicies(ctx context.Cont
3030
}
3131

3232
query := `
33-
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, version, enabled,
33+
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, invite_share_ratio::text, version, enabled,
3434
effective_at, created_by_admin_id, created_at, updated_at, deleted_at
3535
FROM account_share_policies
3636
WHERE deleted_at IS NULL` + where + `
@@ -60,7 +60,7 @@ func (r *accountSharePolicyRepository) ListAccountSharePolicies(ctx context.Cont
6060

6161
func (r *accountSharePolicyRepository) GetAccountSharePolicyByID(ctx context.Context, id int64) (*service.AccountSharePolicy, error) {
6262
row := r.db.QueryRowContext(ctx, `
63-
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, version, enabled,
63+
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, invite_share_ratio::text, version, enabled,
6464
effective_at, created_by_admin_id, created_at, updated_at, deleted_at
6565
FROM account_share_policies
6666
WHERE id = $1 AND deleted_at IS NULL
@@ -85,7 +85,7 @@ func (r *accountSharePolicyRepository) ResolveEnabledAccountSharePolicy(ctx cont
8585

8686
func (r *accountSharePolicyRepository) queryEnabledAccountSharePolicy(ctx context.Context, predicate string, args ...any) (*service.AccountSharePolicy, bool, error) {
8787
query := `
88-
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, version, enabled,
88+
SELECT id, scope_type, scope_id, platform, owner_share_ratio::text, invite_share_ratio::text, version, enabled,
8989
effective_at, created_by_admin_id, created_at, updated_at, deleted_at
9090
FROM account_share_policies
9191
WHERE deleted_at IS NULL
@@ -108,17 +108,18 @@ func (r *accountSharePolicyRepository) queryEnabledAccountSharePolicy(ctx contex
108108
func (r *accountSharePolicyRepository) CreateAccountSharePolicy(ctx context.Context, input service.CreateAccountSharePolicyInput) (*service.AccountSharePolicy, error) {
109109
row := r.db.QueryRowContext(ctx, `
110110
INSERT INTO account_share_policies (
111-
scope_type, scope_id, platform, owner_share_ratio, enabled, effective_at, created_by_admin_id
111+
scope_type, scope_id, platform, owner_share_ratio, invite_share_ratio, enabled, effective_at, created_by_admin_id
112112
) VALUES (
113-
$1, $2, $3, $4::numeric, $5, $6, $7
113+
$1, $2, $3, $4::numeric, $5::numeric, $6, $7, $8
114114
)
115-
RETURNING id, scope_type, scope_id, platform, owner_share_ratio::text, version, enabled,
115+
RETURNING id, scope_type, scope_id, platform, owner_share_ratio::text, invite_share_ratio::text, version, enabled,
116116
effective_at, created_by_admin_id, created_at, updated_at, deleted_at
117117
`,
118118
input.ScopeType,
119119
nullablePtrInt64(input.ScopeID),
120120
nullableStringPtr(input.Platform),
121121
strconv.FormatFloat(input.OwnerShareRatio, 'f', 6, 64),
122+
strconv.FormatFloat(input.InviteShareRatio, 'f', 6, 64),
122123
*input.Enabled,
123124
*input.EffectiveAt,
124125
nullablePtrInt64(input.CreatedByAdminID),
@@ -135,6 +136,7 @@ func (r *accountSharePolicyRepository) UpdateAccountSharePolicy(ctx context.Cont
135136
scopeID := current.ScopeID
136137
platform := current.Platform
137138
ratio := current.OwnerShareRatio
139+
inviteRatio := current.InviteShareRatio
138140
enabled := current.Enabled
139141
effectiveAt := current.EffectiveAt
140142
if input.ScopeType != nil {
@@ -160,6 +162,9 @@ func (r *accountSharePolicyRepository) UpdateAccountSharePolicy(ctx context.Cont
160162
if input.OwnerShareRatio != nil {
161163
ratio = *input.OwnerShareRatio
162164
}
165+
if input.InviteShareRatio != nil {
166+
inviteRatio = *input.InviteShareRatio
167+
}
163168
if input.Enabled != nil {
164169
enabled = *input.Enabled
165170
}
@@ -173,14 +178,15 @@ func (r *accountSharePolicyRepository) UpdateAccountSharePolicy(ctx context.Cont
173178
scope_id = $3,
174179
platform = $4,
175180
owner_share_ratio = $5::numeric,
176-
enabled = $6,
177-
effective_at = $7,
181+
invite_share_ratio = $6::numeric,
182+
enabled = $7,
183+
effective_at = $8,
178184
version = version + 1,
179185
updated_at = NOW()
180186
WHERE id = $1 AND deleted_at IS NULL
181-
RETURNING id, scope_type, scope_id, platform, owner_share_ratio::text, version, enabled,
187+
RETURNING id, scope_type, scope_id, platform, owner_share_ratio::text, invite_share_ratio::text, version, enabled,
182188
effective_at, created_by_admin_id, created_at, updated_at, deleted_at
183-
`, id, scopeType, nullablePtrInt64(scopeID), nullableStringPtr(platform), strconv.FormatFloat(ratio, 'f', 6, 64), enabled, effectiveAt)
189+
`, id, scopeType, nullablePtrInt64(scopeID), nullableStringPtr(platform), strconv.FormatFloat(ratio, 'f', 6, 64), strconv.FormatFloat(inviteRatio, 'f', 6, 64), enabled, effectiveAt)
184190
policy, err := scanAccountSharePolicy(row)
185191
if errors.Is(err, sql.ErrNoRows) {
186192
return nil, service.ErrAccountNotFound
@@ -237,6 +243,7 @@ func scanAccountSharePolicy(scanner sqlScanner) (*service.AccountSharePolicy, er
237243
scopeID sql.NullInt64
238244
platform sql.NullString
239245
ratioRaw string
246+
inviteRatioRaw string
240247
createdByAdmin sql.NullInt64
241248
deletedAt sql.NullTime
242249
)
@@ -246,6 +253,7 @@ func scanAccountSharePolicy(scanner sqlScanner) (*service.AccountSharePolicy, er
246253
&scopeID,
247254
&platform,
248255
&ratioRaw,
256+
&inviteRatioRaw,
249257
&policy.Version,
250258
&policy.Enabled,
251259
&policy.EffectiveAt,
@@ -261,6 +269,11 @@ func scanAccountSharePolicy(scanner sqlScanner) (*service.AccountSharePolicy, er
261269
return nil, err
262270
}
263271
policy.OwnerShareRatio = ratio
272+
inviteRatio, err := strconv.ParseFloat(strings.TrimSpace(inviteRatioRaw), 64)
273+
if err != nil {
274+
return nil, err
275+
}
276+
policy.InviteShareRatio = inviteRatio
264277
if scopeID.Valid {
265278
policy.ScopeID = &scopeID.Int64
266279
}

0 commit comments

Comments
 (0)