fix(lint): 修复所有 Go 命名规范问题

- 全局替换 ApiKey → APIKey(类型、字段、方法、变量)
- 修复所有 initialism 命名(API, SMTP, HTML, URL 等)
- 添加所有缺失的包注释
- 修复导出符号的注释格式

主要修改:
- ApiKey → APIKey(所有出现的地方)
- ApiKeyID → APIKeyID
- ApiKeyIDs → APIKeyIDs
- TestSmtpConnection → TestSMTPConnection
- HtmlURL → HTMLURL
- 添加 20+ 个包注释
- 修复 10+ 个导出符号注释格式

验证结果:
- ✓ golangci-lint: 0 issues
- ✓ 单元测试: 通过
- ✓ 集成测试: 通过
This commit is contained in:
IanShaw027
2026-01-04 19:27:53 +08:00
parent d92e71a1f0
commit a4953785d9
121 changed files with 2747 additions and 2725 deletions

View File

@@ -1,3 +1,4 @@
// Package admin provides HTTP handlers for administrative operations.
package admin
import (

View File

@@ -75,8 +75,8 @@ func (h *DashboardHandler) GetStats(c *gin.Context) {
"active_users": stats.ActiveUsers,
// API Key 统计
"total_api_keys": stats.TotalApiKeys,
"active_api_keys": stats.ActiveApiKeys,
"total_api_keys": stats.TotalAPIKeys,
"active_api_keys": stats.ActiveAPIKeys,
// 账户统计
"total_accounts": stats.TotalAccounts,
@@ -193,10 +193,10 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
})
}
// GetApiKeyUsageTrend handles getting API key usage trend data
// GetAPIKeyUsageTrend handles getting API key usage trend data
// GET /api/v1/admin/dashboard/api-keys-trend
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), limit (default 5)
func (h *DashboardHandler) GetApiKeyUsageTrend(c *gin.Context) {
func (h *DashboardHandler) GetAPIKeyUsageTrend(c *gin.Context) {
startTime, endTime := parseTimeRange(c)
granularity := c.DefaultQuery("granularity", "day")
limitStr := c.DefaultQuery("limit", "5")
@@ -205,7 +205,7 @@ func (h *DashboardHandler) GetApiKeyUsageTrend(c *gin.Context) {
limit = 5
}
trend, err := h.dashboardService.GetApiKeyUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
trend, err := h.dashboardService.GetAPIKeyUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
if err != nil {
response.Error(c, 500, "Failed to get API key usage trend")
return
@@ -273,26 +273,26 @@ func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
response.Success(c, gin.H{"stats": stats})
}
// BatchApiKeysUsageRequest represents the request body for batch api key usage stats
type BatchApiKeysUsageRequest struct {
ApiKeyIDs []int64 `json:"api_key_ids" binding:"required"`
// BatchAPIKeysUsageRequest represents the request body for batch api key usage stats
type BatchAPIKeysUsageRequest struct {
APIKeyIDs []int64 `json:"api_key_ids" binding:"required"`
}
// GetBatchApiKeysUsage handles getting usage stats for multiple API keys
// GetBatchAPIKeysUsage handles getting usage stats for multiple API keys
// POST /api/v1/admin/dashboard/api-keys-usage
func (h *DashboardHandler) GetBatchApiKeysUsage(c *gin.Context) {
var req BatchApiKeysUsageRequest
func (h *DashboardHandler) GetBatchAPIKeysUsage(c *gin.Context) {
var req BatchAPIKeysUsageRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if len(req.ApiKeyIDs) == 0 {
if len(req.APIKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}})
return
}
stats, err := h.dashboardService.GetBatchApiKeyUsageStats(c.Request.Context(), req.ApiKeyIDs)
stats, err := h.dashboardService.GetBatchAPIKeyUsageStats(c.Request.Context(), req.APIKeyIDs)
if err != nil {
response.Error(c, 500, "Failed to get API key usage stats")
return

View File

@@ -18,6 +18,7 @@ func NewGeminiOAuthHandler(geminiOAuthService *service.GeminiOAuthService) *Gemi
return &GeminiOAuthHandler{geminiOAuthService: geminiOAuthService}
}
// GetCapabilities returns the Gemini OAuth configuration capabilities.
// GET /api/v1/admin/gemini/oauth/capabilities
func (h *GeminiOAuthHandler) GetCapabilities(c *gin.Context) {
cfg := h.geminiOAuthService.GetOAuthConfig()

View File

@@ -237,9 +237,9 @@ func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) {
return
}
outKeys := make([]dto.ApiKey, 0, len(keys))
outKeys := make([]dto.APIKey, 0, len(keys))
for i := range keys {
outKeys = append(outKeys, *dto.ApiKeyFromService(&keys[i]))
outKeys = append(outKeys, *dto.APIKeyFromService(&keys[i]))
}
response.Paginated(c, outKeys, total, page, pageSize)
}

View File

@@ -230,9 +230,9 @@ type TestSMTPRequest struct {
SMTPUseTLS bool `json:"smtp_use_tls"`
}
// TestSmtpConnection 测试SMTP连接
// TestSMTPConnection 测试SMTP连接
// POST /api/v1/admin/settings/test-smtp
func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
var req TestSMTPRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
@@ -246,13 +246,13 @@ func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
// 如果未提供密码,从数据库获取已保存的密码
password := req.SMTPPassword
if password == "" {
savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context())
if err == nil && savedConfig != nil {
password = savedConfig.Password
}
}
config := &service.SmtpConfig{
config := &service.SMTPConfig{
Host: req.SMTPHost,
Port: req.SMTPPort,
Username: req.SMTPUsername,
@@ -260,7 +260,7 @@ func (h *SettingHandler) TestSmtpConnection(c *gin.Context) {
UseTLS: req.SMTPUseTLS,
}
err := h.emailService.TestSmtpConnectionWithConfig(config)
err := h.emailService.TestSMTPConnectionWithConfig(config)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -297,13 +297,13 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
// 如果未提供密码,从数据库获取已保存的密码
password := req.SMTPPassword
if password == "" {
savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context())
if err == nil && savedConfig != nil {
password = savedConfig.Password
}
}
config := &service.SmtpConfig{
config := &service.SMTPConfig{
Host: req.SMTPHost,
Port: req.SMTPPort,
Username: req.SMTPUsername,
@@ -355,10 +355,10 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
response.Success(c, gin.H{"message": "Test email sent successfully"})
}
// GetAdminApiKey 获取管理员 API Key 状态
// GetAdminAPIKey 获取管理员 API Key 状态
// GET /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) GetAdminApiKey(c *gin.Context) {
maskedKey, exists, err := h.settingService.GetAdminApiKeyStatus(c.Request.Context())
func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) {
maskedKey, exists, err := h.settingService.GetAdminAPIKeyStatus(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
@@ -370,10 +370,10 @@ func (h *SettingHandler) GetAdminApiKey(c *gin.Context) {
})
}
// RegenerateAdminApiKey 生成/重新生成管理员 API Key
// RegenerateAdminAPIKey 生成/重新生成管理员 API Key
// POST /api/v1/admin/settings/admin-api-key/regenerate
func (h *SettingHandler) RegenerateAdminApiKey(c *gin.Context) {
key, err := h.settingService.GenerateAdminApiKey(c.Request.Context())
func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) {
key, err := h.settingService.GenerateAdminAPIKey(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
return
@@ -384,10 +384,10 @@ func (h *SettingHandler) RegenerateAdminApiKey(c *gin.Context) {
})
}
// DeleteAdminApiKey 删除管理员 API Key
// DeleteAdminAPIKey 删除管理员 API Key
// DELETE /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) DeleteAdminApiKey(c *gin.Context) {
if err := h.settingService.DeleteAdminApiKey(c.Request.Context()); err != nil {
func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) {
if err := h.settingService.DeleteAdminAPIKey(c.Request.Context()); err != nil {
response.ErrorFrom(c, err)
return
}

View File

@@ -17,14 +17,14 @@ import (
// UsageHandler handles admin usage-related requests
type UsageHandler struct {
usageService *service.UsageService
apiKeyService *service.ApiKeyService
apiKeyService *service.APIKeyService
adminService service.AdminService
}
// NewUsageHandler creates a new admin usage handler
func NewUsageHandler(
usageService *service.UsageService,
apiKeyService *service.ApiKeyService,
apiKeyService *service.APIKeyService,
adminService service.AdminService,
) *UsageHandler {
return &UsageHandler{
@@ -125,7 +125,7 @@ func (h *UsageHandler) List(c *gin.Context) {
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
filters := usagestats.UsageLogFilters{
UserID: userID,
ApiKeyID: apiKeyID,
APIKeyID: apiKeyID,
AccountID: accountID,
GroupID: groupID,
Model: model,
@@ -207,7 +207,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
}
if apiKeyID > 0 {
stats, err := h.usageService.GetStatsByApiKey(c.Request.Context(), apiKeyID, startTime, endTime)
stats, err := h.usageService.GetStatsByAPIKey(c.Request.Context(), apiKeyID, startTime, endTime)
if err != nil {
response.ErrorFrom(c, err)
return
@@ -269,9 +269,9 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
response.Success(c, result)
}
// SearchApiKeys handles searching API keys by user
// SearchAPIKeys handles searching API keys by user
// GET /api/v1/admin/usage/search-api-keys
func (h *UsageHandler) SearchApiKeys(c *gin.Context) {
func (h *UsageHandler) SearchAPIKeys(c *gin.Context) {
userIDStr := c.Query("user_id")
keyword := c.Query("q")
@@ -285,22 +285,22 @@ func (h *UsageHandler) SearchApiKeys(c *gin.Context) {
userID = id
}
keys, err := h.apiKeyService.SearchApiKeys(c.Request.Context(), userID, keyword, 30)
keys, err := h.apiKeyService.SearchAPIKeys(c.Request.Context(), userID, keyword, 30)
if err != nil {
response.ErrorFrom(c, err)
return
}
// Return simplified API key list (only id and name)
type SimpleApiKey struct {
type SimpleAPIKey struct {
ID int64 `json:"id"`
Name string `json:"name"`
UserID int64 `json:"user_id"`
}
result := make([]SimpleApiKey, len(keys))
result := make([]SimpleAPIKey, len(keys))
for i, k := range keys {
result[i] = SimpleApiKey{
result[i] = SimpleAPIKey{
ID: k.ID,
Name: k.Name,
UserID: k.UserID,

View File

@@ -243,9 +243,9 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
return
}
out := make([]dto.ApiKey, 0, len(keys))
out := make([]dto.APIKey, 0, len(keys))
for i := range keys {
out = append(out, *dto.ApiKeyFromService(&keys[i]))
out = append(out, *dto.APIKeyFromService(&keys[i]))
}
response.Paginated(c, out, total, page, pageSize)
}

View File

@@ -1,3 +1,4 @@
// Package handler provides HTTP request handlers for the application.
package handler
import (
@@ -14,11 +15,11 @@ import (
// APIKeyHandler handles API key-related requests
type APIKeyHandler struct {
apiKeyService *service.ApiKeyService
apiKeyService *service.APIKeyService
}
// NewAPIKeyHandler creates a new APIKeyHandler
func NewAPIKeyHandler(apiKeyService *service.ApiKeyService) *APIKeyHandler {
func NewAPIKeyHandler(apiKeyService *service.APIKeyService) *APIKeyHandler {
return &APIKeyHandler{
apiKeyService: apiKeyService,
}
@@ -56,9 +57,9 @@ func (h *APIKeyHandler) List(c *gin.Context) {
return
}
out := make([]dto.ApiKey, 0, len(keys))
out := make([]dto.APIKey, 0, len(keys))
for i := range keys {
out = append(out, *dto.ApiKeyFromService(&keys[i]))
out = append(out, *dto.APIKeyFromService(&keys[i]))
}
response.Paginated(c, out, result.Total, page, pageSize)
}
@@ -90,7 +91,7 @@ func (h *APIKeyHandler) GetByID(c *gin.Context) {
return
}
response.Success(c, dto.ApiKeyFromService(key))
response.Success(c, dto.APIKeyFromService(key))
}
// Create handles creating a new API key
@@ -108,7 +109,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
return
}
svcReq := service.CreateApiKeyRequest{
svcReq := service.CreateAPIKeyRequest{
Name: req.Name,
GroupID: req.GroupID,
CustomKey: req.CustomKey,
@@ -119,7 +120,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
return
}
response.Success(c, dto.ApiKeyFromService(key))
response.Success(c, dto.APIKeyFromService(key))
}
// Update handles updating an API key
@@ -143,7 +144,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
return
}
svcReq := service.UpdateApiKeyRequest{}
svcReq := service.UpdateAPIKeyRequest{}
if req.Name != "" {
svcReq.Name = &req.Name
}
@@ -158,7 +159,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
return
}
response.Success(c, dto.ApiKeyFromService(key))
response.Success(c, dto.APIKeyFromService(key))
}
// Delete handles deleting an API key

View File

@@ -1,3 +1,4 @@
// Package dto provides data transfer objects for HTTP handlers.
package dto
import "github.com/Wei-Shaw/sub2api/internal/service"
@@ -26,11 +27,11 @@ func UserFromService(u *service.User) *User {
return nil
}
out := UserFromServiceShallow(u)
if len(u.ApiKeys) > 0 {
out.ApiKeys = make([]ApiKey, 0, len(u.ApiKeys))
for i := range u.ApiKeys {
k := u.ApiKeys[i]
out.ApiKeys = append(out.ApiKeys, *ApiKeyFromService(&k))
if len(u.APIKeys) > 0 {
out.APIKeys = make([]APIKey, 0, len(u.APIKeys))
for i := range u.APIKeys {
k := u.APIKeys[i]
out.APIKeys = append(out.APIKeys, *APIKeyFromService(&k))
}
}
if len(u.Subscriptions) > 0 {
@@ -43,11 +44,11 @@ func UserFromService(u *service.User) *User {
return out
}
func ApiKeyFromService(k *service.ApiKey) *ApiKey {
func APIKeyFromService(k *service.APIKey) *APIKey {
if k == nil {
return nil
}
return &ApiKey{
return &APIKey{
ID: k.ID,
UserID: k.UserID,
Key: k.Key,
@@ -222,7 +223,7 @@ func UsageLogFromService(l *service.UsageLog) *UsageLog {
return &UsageLog{
ID: l.ID,
UserID: l.UserID,
ApiKeyID: l.ApiKeyID,
APIKeyID: l.APIKeyID,
AccountID: l.AccountID,
RequestID: l.RequestID,
Model: l.Model,
@@ -247,7 +248,7 @@ func UsageLogFromService(l *service.UsageLog) *UsageLog {
FirstTokenMs: l.FirstTokenMs,
CreatedAt: l.CreatedAt,
User: UserFromServiceShallow(l.User),
ApiKey: ApiKeyFromService(l.ApiKey),
APIKey: APIKeyFromService(l.APIKey),
Account: AccountFromService(l.Account),
Group: GroupFromServiceShallow(l.Group),
Subscription: UserSubscriptionFromService(l.Subscription),

View File

@@ -15,11 +15,11 @@ type User struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ApiKeys []ApiKey `json:"api_keys,omitempty"`
APIKeys []APIKey `json:"api_keys,omitempty"`
Subscriptions []UserSubscription `json:"subscriptions,omitempty"`
}
type ApiKey struct {
type APIKey struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Key string `json:"key"`
@@ -139,7 +139,7 @@ type RedeemCode struct {
type UsageLog struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
ApiKeyID int64 `json:"api_key_id"`
APIKeyID int64 `json:"api_key_id"`
AccountID int64 `json:"account_id"`
RequestID string `json:"request_id"`
Model string `json:"model"`
@@ -171,7 +171,7 @@ type UsageLog struct {
CreatedAt time.Time `json:"created_at"`
User *User `json:"user,omitempty"`
ApiKey *ApiKey `json:"api_key,omitempty"`
APIKey *APIKey `json:"api_key,omitempty"`
Account *Account `json:"account,omitempty"`
Group *Group `json:"group,omitempty"`
Subscription *UserSubscription `json:"subscription,omitempty"`

View File

@@ -53,7 +53,7 @@ func NewGatewayHandler(
// POST /v1/messages
func (h *GatewayHandler) Messages(c *gin.Context) {
// 从context获取apiKey和userApiKeyAuth中间件已设置
apiKey, ok := middleware2.GetApiKeyFromContext(c)
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return
@@ -259,7 +259,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
defer cancel()
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
Result: result,
ApiKey: apiKey,
APIKey: apiKey,
User: apiKey.User,
Account: usedAccount,
Subscription: subscription,
@@ -383,7 +383,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
defer cancel()
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
Result: result,
ApiKey: apiKey,
APIKey: apiKey,
User: apiKey.User,
Account: usedAccount,
Subscription: subscription,
@@ -400,7 +400,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
// Returns models based on account configurations (model_mapping whitelist)
// Falls back to default models if no whitelist is configured
func (h *GatewayHandler) Models(c *gin.Context) {
apiKey, _ := middleware2.GetApiKeyFromContext(c)
apiKey, _ := middleware2.GetAPIKeyFromContext(c)
var groupID *int64
var platform string
@@ -458,7 +458,7 @@ func (h *GatewayHandler) AntigravityModels(c *gin.Context) {
// Usage handles getting account balance for CC Switch integration
// GET /v1/usage
func (h *GatewayHandler) Usage(c *gin.Context) {
apiKey, ok := middleware2.GetApiKeyFromContext(c)
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return
@@ -628,7 +628,7 @@ func (h *GatewayHandler) errorResponse(c *gin.Context, status int, errType, mess
// 特点:校验订阅/余额,但不计算并发、不记录使用量
func (h *GatewayHandler) CountTokens(c *gin.Context) {
// 从context获取apiKey和userApiKeyAuth中间件已设置
apiKey, ok := middleware2.GetApiKeyFromContext(c)
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return

View File

@@ -21,7 +21,7 @@ import (
// GeminiV1BetaListModels proxies:
// GET /v1beta/models
func (h *GatewayHandler) GeminiV1BetaListModels(c *gin.Context) {
apiKey, ok := middleware.GetApiKeyFromContext(c)
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if !ok || apiKey == nil {
googleError(c, http.StatusUnauthorized, "Invalid API key")
return
@@ -67,7 +67,7 @@ func (h *GatewayHandler) GeminiV1BetaListModels(c *gin.Context) {
// GeminiV1BetaGetModel proxies:
// GET /v1beta/models/{model}
func (h *GatewayHandler) GeminiV1BetaGetModel(c *gin.Context) {
apiKey, ok := middleware.GetApiKeyFromContext(c)
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if !ok || apiKey == nil {
googleError(c, http.StatusUnauthorized, "Invalid API key")
return
@@ -120,7 +120,7 @@ func (h *GatewayHandler) GeminiV1BetaGetModel(c *gin.Context) {
// POST /v1beta/models/{model}:generateContent
// POST /v1beta/models/{model}:streamGenerateContent?alt=sse
func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
apiKey, ok := middleware.GetApiKeyFromContext(c)
apiKey, ok := middleware.GetAPIKeyFromContext(c)
if !ok || apiKey == nil {
googleError(c, http.StatusUnauthorized, "Invalid API key")
return
@@ -299,7 +299,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
defer cancel()
if err := h.gatewayService.RecordUsage(ctx, &service.RecordUsageInput{
Result: result,
ApiKey: apiKey,
APIKey: apiKey,
User: apiKey.User,
Account: usedAccount,
Subscription: subscription,

View File

@@ -41,7 +41,7 @@ func NewOpenAIGatewayHandler(
// POST /openai/v1/responses
func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// Get apiKey and user from context (set by ApiKeyAuth middleware)
apiKey, ok := middleware2.GetApiKeyFromContext(c)
apiKey, ok := middleware2.GetAPIKeyFromContext(c)
if !ok {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return
@@ -235,7 +235,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
defer cancel()
if err := h.gatewayService.RecordUsage(ctx, &service.OpenAIRecordUsageInput{
Result: result,
ApiKey: apiKey,
APIKey: apiKey,
User: apiKey.User,
Account: usedAccount,
Subscription: subscription,

View File

@@ -18,11 +18,11 @@ import (
// UsageHandler handles usage-related requests
type UsageHandler struct {
usageService *service.UsageService
apiKeyService *service.ApiKeyService
apiKeyService *service.APIKeyService
}
// NewUsageHandler creates a new UsageHandler
func NewUsageHandler(usageService *service.UsageService, apiKeyService *service.ApiKeyService) *UsageHandler {
func NewUsageHandler(usageService *service.UsageService, apiKeyService *service.APIKeyService) *UsageHandler {
return &UsageHandler{
usageService: usageService,
apiKeyService: apiKeyService,
@@ -111,7 +111,7 @@ func (h *UsageHandler) List(c *gin.Context) {
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
filters := usagestats.UsageLogFilters{
UserID: subject.UserID, // Always filter by current user for security
ApiKeyID: apiKeyID,
APIKeyID: apiKeyID,
Model: model,
Stream: stream,
BillingType: billingType,
@@ -235,7 +235,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
var stats *service.UsageStats
var err error
if apiKeyID > 0 {
stats, err = h.usageService.GetStatsByApiKey(c.Request.Context(), apiKeyID, startTime, endTime)
stats, err = h.usageService.GetStatsByAPIKey(c.Request.Context(), apiKeyID, startTime, endTime)
} else {
stats, err = h.usageService.GetStatsByUser(c.Request.Context(), subject.UserID, startTime, endTime)
}
@@ -346,49 +346,49 @@ func (h *UsageHandler) DashboardModels(c *gin.Context) {
})
}
// BatchApiKeysUsageRequest represents the request for batch API keys usage
type BatchApiKeysUsageRequest struct {
ApiKeyIDs []int64 `json:"api_key_ids" binding:"required"`
// BatchAPIKeysUsageRequest represents the request for batch API keys usage
type BatchAPIKeysUsageRequest struct {
APIKeyIDs []int64 `json:"api_key_ids" binding:"required"`
}
// DashboardApiKeysUsage handles getting usage stats for user's own API keys
// DashboardAPIKeysUsage handles getting usage stats for user's own API keys
// POST /api/v1/usage/dashboard/api-keys-usage
func (h *UsageHandler) DashboardApiKeysUsage(c *gin.Context) {
func (h *UsageHandler) DashboardAPIKeysUsage(c *gin.Context) {
subject, ok := middleware2.GetAuthSubjectFromContext(c)
if !ok {
response.Unauthorized(c, "User not authenticated")
return
}
var req BatchApiKeysUsageRequest
var req BatchAPIKeysUsageRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error())
return
}
if len(req.ApiKeyIDs) == 0 {
if len(req.APIKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}})
return
}
// Limit the number of API key IDs to prevent SQL parameter overflow
if len(req.ApiKeyIDs) > 100 {
if len(req.APIKeyIDs) > 100 {
response.BadRequest(c, "Too many API key IDs (maximum 100 allowed)")
return
}
validApiKeyIDs, err := h.apiKeyService.VerifyOwnership(c.Request.Context(), subject.UserID, req.ApiKeyIDs)
validAPIKeyIDs, err := h.apiKeyService.VerifyOwnership(c.Request.Context(), subject.UserID, req.APIKeyIDs)
if err != nil {
response.ErrorFrom(c, err)
return
}
if len(validApiKeyIDs) == 0 {
if len(validAPIKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}})
return
}
stats, err := h.usageService.GetBatchApiKeyUsageStats(c.Request.Context(), validApiKeyIDs)
stats, err := h.usageService.GetBatchAPIKeyUsageStats(c.Request.Context(), validAPIKeyIDs)
if err != nil {
response.ErrorFrom(c, err)
return