diff --git a/backend/internal/service/sora_quota_service.go b/backend/internal/service/sora_quota_service.go index c2d8e4d9..9a98322c 100644 --- a/backend/internal/service/sora_quota_service.go +++ b/backend/internal/service/sora_quota_service.go @@ -8,32 +8,27 @@ import ( "github.com/Wei-Shaw/sub2api/internal/pkg/logger" ) -// SoraQuotaService 管理 Sora 用户存储配额。 -// 仅支持系统默认配额(用户级和分组级配额已移除)。 +// SoraQuotaService manages Sora storage quota using only the system default quota. type SoraQuotaService struct { settingService *SettingService } -// NewSoraQuotaService 创建配额服务实例。 func NewSoraQuotaService(settingService *SettingService) *SoraQuotaService { return &SoraQuotaService{ settingService: settingService, } } -// QuotaInfo 返回给客户端的配额信息。 type QuotaInfo struct { - QuotaBytes int64 `json:"quota_bytes"` // 总配额(0 表示无限制) - UsedBytes int64 `json:"used_bytes"` // 已使用(已移除追踪功能,始终为0) - AvailableBytes int64 `json:"available_bytes"` // 剩余可用(无限制时为 0) - QuotaSource string `json:"quota_source"` // 配额来源:system / unlimited - Source string `json:"source,omitempty"` // 兼容旧字段 + QuotaBytes int64 `json:"quota_bytes"` + UsedBytes int64 `json:"used_bytes"` + AvailableBytes int64 `json:"available_bytes"` + QuotaSource string `json:"quota_source"` + Source string `json:"source,omitempty"` } -// ErrSoraStorageQuotaExceeded 表示配额不足。 var ErrSoraStorageQuotaExceeded = errors.New("sora storage quota exceeded") -// QuotaExceededError 包含配额不足的上下文信息。 type QuotaExceededError struct { QuotaBytes int64 UsedBytes int64 @@ -41,47 +36,39 @@ type QuotaExceededError struct { func (e *QuotaExceededError) Error() string { if e == nil { - return "存储配额不足" + return "storage quota exceeded" } - return fmt.Sprintf("存储配额不足(已用 %d / 配额 %d 字节)", e.UsedBytes, e.QuotaBytes) + return fmt.Sprintf("storage quota exceeded (used %d / quota %d bytes)", e.UsedBytes, e.QuotaBytes) } -// GetQuota 获取用户的存储配额信息。 -// 仅使用系统默认值(用户级和分组级配额已移除)。 func (s *SoraQuotaService) GetQuota(ctx context.Context, userID int64) (*QuotaInfo, error) { info := &QuotaInfo{ - UsedBytes: 0, // 已移除用量追踪 + UsedBytes: 0, } - // 系统默认值 defaultQuota := s.getSystemDefaultQuota(ctx) if defaultQuota > 0 { info.QuotaBytes = defaultQuota info.QuotaSource = "system" info.Source = info.QuotaSource - info.AvailableBytes = defaultQuota // 无用量追踪,可用等于配额 + info.AvailableBytes = defaultQuota return info, nil } - // 无配额限制 info.QuotaSource = "unlimited" info.Source = info.QuotaSource info.AvailableBytes = 0 return info, nil } -// CheckQuota 检查用户是否有足够的存储配额。 -// 返回 nil 表示配额充足或无限制。 func (s *SoraQuotaService) CheckQuota(ctx context.Context, userID int64, additionalBytes int64) error { quota, err := s.GetQuota(ctx, userID) if err != nil { return err } - // 0 表示无限制 if quota.QuotaBytes == 0 { return nil } - // 无用量追踪,只要请求大小不超过配额即可 if additionalBytes > quota.QuotaBytes { return &QuotaExceededError{ QuotaBytes: quota.QuotaBytes, @@ -91,7 +78,6 @@ func (s *SoraQuotaService) CheckQuota(ctx context.Context, userID int64, additio return nil } -// AddUsage 累加用量(已移除追踪功能,仅记录日志)。 func (s *SoraQuotaService) AddUsage(ctx context.Context, userID int64, bytes int64) error { if bytes <= 0 { return nil @@ -100,7 +86,6 @@ func (s *SoraQuotaService) AddUsage(ctx context.Context, userID int64, bytes int return nil } -// ReleaseUsage 释放用量(已移除追踪功能,仅记录日志)。 func (s *SoraQuotaService) ReleaseUsage(ctx context.Context, userID int64, bytes int64) error { if bytes <= 0 { return nil @@ -120,12 +105,6 @@ func (s *SoraQuotaService) getSystemDefaultQuota(ctx context.Context) int64 { return settings.DefaultStorageQuotaBytes } -// GetQuotaFromSettings 从系统设置获取默认配额(供外部使用)。 func (s *SoraQuotaService) GetQuotaFromSettings(ctx context.Context) int64 { return s.getSystemDefaultQuota(ctx) } - -// SetUserSoraQuota 设置用户级配额(已移除此功能)。 -func SetUserSoraQuota(ctx context.Context, userRepo UserRepository, userID int64, quotaBytes int64) error { - return errors.New("user-level sora quota setting is no longer supported") -} \ No newline at end of file diff --git a/backend/internal/service/sora_quota_service_test.go b/backend/internal/service/sora_quota_service_test.go index 89499bf8..fff06683 100644 --- a/backend/internal/service/sora_quota_service_test.go +++ b/backend/internal/service/sora_quota_service_test.go @@ -110,7 +110,10 @@ func TestCheckQuota_Exceeded(t *testing.T) { err := svc.CheckQuota(context.Background(), 1, 2048) require.Error(t, err) - require.Contains(t, err.Error(), "配额不足") + var qe *QuotaExceededError + require.ErrorAs(t, err, &qe) + require.Equal(t, int64(1024), qe.QuotaBytes) + require.Equal(t, int64(0), qe.UsedBytes) } func TestCheckQuota_NoLimit(t *testing.T) { @@ -171,11 +174,6 @@ func TestGetQuotaFromSettings_WithSettings(t *testing.T) { // ==================== SetUserSoraQuota ==================== // 用户级配额设置已移除 -func TestSetUserSoraQuota_NotSupported(t *testing.T) { - err := SetUserSoraQuota(context.Background(), nil, 1, 1024) - require.Error(t, err) - require.Contains(t, err.Error(), "no longer supported") -} // ==================== GetQuota — 字段完整性 ==================== @@ -265,7 +263,7 @@ func TestReleaseUsage_NegativeBytes(t *testing.T) { func TestQuotaExceededError_NilSafe(t *testing.T) { var e *QuotaExceededError msg := e.Error() - require.Contains(t, msg, "配额不足") + require.Contains(t, msg, "storage quota exceeded") } func TestQuotaExceededError_Format(t *testing.T) { @@ -273,4 +271,4 @@ func TestQuotaExceededError_Format(t *testing.T) { msg := e.Error() require.Contains(t, msg, "512") require.Contains(t, msg, "1024") -} \ No newline at end of file +} diff --git a/frontend/src/api/admin/dashboard.ts b/frontend/src/api/admin/dashboard.ts index 159ef9d2..5077e158 100644 --- a/frontend/src/api/admin/dashboard.ts +++ b/frontend/src/api/admin/dashboard.ts @@ -9,7 +9,6 @@ import type { TrendDataPoint, ModelStat, GroupStat, - ApiKeyUsageTrendPoint, UserUsageTrendPoint, UserSpendingRankingResponse, UserBreakdownItem, @@ -30,23 +29,6 @@ export interface TrendParams { billing_type?: number | null } -export interface TrendResponse { - trend: TrendDataPoint[] - start_date: string - end_date: string - granularity: string -} - -/** - * Get usage trend data - * @param params - Query parameters for filtering - * @returns Usage trend data - */ -export async function getUsageTrend(params?: TrendParams): Promise { - const { data } = await apiClient.get('/admin/dashboard/trend', { params }) - return data -} - export interface ModelStatsParams { start_date?: string end_date?: string @@ -77,24 +59,6 @@ export async function getModelStats(params?: ModelStatsParams): Promise { - const { data } = await apiClient.get('/admin/dashboard/groups', { params }) - return data -} - export interface UserBreakdownParams { start_date?: string end_date?: string @@ -171,31 +125,6 @@ export async function getSnapshotV2(params?: DashboardSnapshotV2Params): Promise return data } -export interface ApiKeyTrendParams extends TrendParams { - limit?: number -} - -export interface ApiKeyTrendResponse { - trend: ApiKeyUsageTrendPoint[] - start_date: string - end_date: string - granularity: string -} - -/** - * Get API key usage trend data - * @param params - Query parameters for filtering - * @returns API key usage trend data - */ -export async function getApiKeyUsageTrend( - params?: ApiKeyTrendParams -): Promise { - const { data } = await apiClient.get('/admin/dashboard/api-keys-trend', { - params - }) - return data -} - export interface UserTrendParams extends TrendParams { limit?: number } @@ -260,43 +189,13 @@ export async function getBatchUsersUsage(userIds: number[]): Promise -} - -/** - * Get batch usage stats for multiple API keys - * @param apiKeyIds - Array of API key IDs - * @returns Usage stats map keyed by API key ID - */ -export async function getBatchApiKeysUsage( - apiKeyIds: number[] -): Promise { - const { data } = await apiClient.post( - '/admin/dashboard/api-keys-usage', - { - api_key_ids: apiKeyIds - } - ) - return data -} - export const dashboardAPI = { - getUsageTrend, getModelStats, - getGroupStats, + getUserBreakdown, getSnapshotV2, - getApiKeyUsageTrend, getUserUsageTrend, getUserSpendingRanking, - getBatchUsersUsage, - getBatchApiKeysUsage + getBatchUsersUsage } export default dashboardAPI diff --git a/frontend/src/router/__tests__/deprecatedAdminFeatures.spec.ts b/frontend/src/router/__tests__/deprecatedAdminFeatures.spec.ts index fce82cb2..ea376be2 100644 --- a/frontend/src/router/__tests__/deprecatedAdminFeatures.spec.ts +++ b/frontend/src/router/__tests__/deprecatedAdminFeatures.spec.ts @@ -8,6 +8,8 @@ const routerPath = resolve(dirname(fileURLToPath(import.meta.url)), '../index.ts const routerSource = readFileSync(routerPath, 'utf8') const adminApiIndexPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../api/admin/index.ts') const adminApiIndexSource = readFileSync(adminApiIndexPath, 'utf8') +const adminDashboardApiPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../api/admin/dashboard.ts') +const adminDashboardApiSource = readFileSync(adminDashboardApiPath, 'utf8') describe('deprecated admin features', () => { it('does not expose the deprecated data-management admin route', () => { @@ -21,4 +23,11 @@ describe('deprecated admin features', () => { expect(adminApiIndexSource).not.toContain('dataManagementAPI,') expect(adminApiIndexSource).not.toContain("from './dataManagement'") }) + + it('does not expose dead dashboard admin wrappers', () => { + expect(adminDashboardApiSource).not.toContain('getUsageTrend,') + expect(adminDashboardApiSource).not.toContain('getGroupStats,') + expect(adminDashboardApiSource).not.toContain('getApiKeyUsageTrend,') + expect(adminDashboardApiSource).not.toContain('getBatchApiKeysUsage') + }) })