Commit 112a2d08 authored by ianshaw's avatar ianshaw
Browse files

chore: 更新依赖、配置和代码生成

主要更新:
- 更新 go.mod/go.sum 依赖
- 重新生成 Ent ORM 代码
- 更新 Wire 依赖注入配置
- 添加 docker-compose.override.yml 到 .gitignore
- 更新 README 文档(Simple Mode 说明和已知问题)
- 清理调试日志
- 其他代码优化和格式修复
parent b1702de5
...@@ -722,7 +722,7 @@ func HasAPIKeys() predicate.User { ...@@ -722,7 +722,7 @@ func HasAPIKeys() predicate.User {
} }
// HasAPIKeysWith applies the HasEdge predicate on the "api_keys" edge with a given conditions (other predicates). // HasAPIKeysWith applies the HasEdge predicate on the "api_keys" edge with a given conditions (other predicates).
func HasAPIKeysWith(preds ...predicate.APIKey) predicate.User { func HasAPIKeysWith(preds ...predicate.ApiKey) predicate.User {
return predicate.User(func(s *sql.Selector) { return predicate.User(func(s *sql.Selector) {
step := newAPIKeysStep() step := newAPIKeysStep()
sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) {
......
...@@ -166,14 +166,14 @@ func (_c *UserCreate) SetNillableNotes(v *string) *UserCreate { ...@@ -166,14 +166,14 @@ func (_c *UserCreate) SetNillableNotes(v *string) *UserCreate {
return _c return _c
} }
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. // AddAPIKeyIDs adds the "api_keys" edge to the ApiKey entity by IDs.
func (_c *UserCreate) AddAPIKeyIDs(ids ...int64) *UserCreate { func (_c *UserCreate) AddAPIKeyIDs(ids ...int64) *UserCreate {
_c.mutation.AddAPIKeyIDs(ids...) _c.mutation.AddAPIKeyIDs(ids...)
return _c return _c
} }
// AddAPIKeys adds the "api_keys" edges to the APIKey entity. // AddAPIKeys adds the "api_keys" edges to the ApiKey entity.
func (_c *UserCreate) AddAPIKeys(v ...*APIKey) *UserCreate { func (_c *UserCreate) AddAPIKeys(v ...*ApiKey) *UserCreate {
ids := make([]int64, len(v)) ids := make([]int64, len(v))
for i := range v { for i := range v {
ids[i] = v[i].ID ids[i] = v[i].ID
......
...@@ -30,7 +30,7 @@ type UserQuery struct { ...@@ -30,7 +30,7 @@ type UserQuery struct {
order []user.OrderOption order []user.OrderOption
inters []Interceptor inters []Interceptor
predicates []predicate.User predicates []predicate.User
withAPIKeys *APIKeyQuery withAPIKeys *ApiKeyQuery
withRedeemCodes *RedeemCodeQuery withRedeemCodes *RedeemCodeQuery
withSubscriptions *UserSubscriptionQuery withSubscriptions *UserSubscriptionQuery
withAssignedSubscriptions *UserSubscriptionQuery withAssignedSubscriptions *UserSubscriptionQuery
...@@ -75,8 +75,8 @@ func (_q *UserQuery) Order(o ...user.OrderOption) *UserQuery { ...@@ -75,8 +75,8 @@ func (_q *UserQuery) Order(o ...user.OrderOption) *UserQuery {
} }
// QueryAPIKeys chains the current query on the "api_keys" edge. // QueryAPIKeys chains the current query on the "api_keys" edge.
func (_q *UserQuery) QueryAPIKeys() *APIKeyQuery { func (_q *UserQuery) QueryAPIKeys() *ApiKeyQuery {
query := (&APIKeyClient{config: _q.config}).Query() query := (&ApiKeyClient{config: _q.config}).Query()
query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { query.path = func(ctx context.Context) (fromU *sql.Selector, err error) {
if err := _q.prepareQuery(ctx); err != nil { if err := _q.prepareQuery(ctx); err != nil {
return nil, err return nil, err
...@@ -458,8 +458,8 @@ func (_q *UserQuery) Clone() *UserQuery { ...@@ -458,8 +458,8 @@ func (_q *UserQuery) Clone() *UserQuery {
// WithAPIKeys tells the query-builder to eager-load the nodes that are connected to // WithAPIKeys tells the query-builder to eager-load the nodes that are connected to
// the "api_keys" edge. The optional arguments are used to configure the query builder of the edge. // the "api_keys" edge. The optional arguments are used to configure the query builder of the edge.
func (_q *UserQuery) WithAPIKeys(opts ...func(*APIKeyQuery)) *UserQuery { func (_q *UserQuery) WithAPIKeys(opts ...func(*ApiKeyQuery)) *UserQuery {
query := (&APIKeyClient{config: _q.config}).Query() query := (&ApiKeyClient{config: _q.config}).Query()
for _, opt := range opts { for _, opt := range opts {
opt(query) opt(query)
} }
...@@ -653,8 +653,8 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e ...@@ -653,8 +653,8 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
} }
if query := _q.withAPIKeys; query != nil { if query := _q.withAPIKeys; query != nil {
if err := _q.loadAPIKeys(ctx, query, nodes, if err := _q.loadAPIKeys(ctx, query, nodes,
func(n *User) { n.Edges.APIKeys = []*APIKey{} }, func(n *User) { n.Edges.APIKeys = []*ApiKey{} },
func(n *User, e *APIKey) { n.Edges.APIKeys = append(n.Edges.APIKeys, e) }); err != nil { func(n *User, e *ApiKey) { n.Edges.APIKeys = append(n.Edges.APIKeys, e) }); err != nil {
return nil, err return nil, err
} }
} }
...@@ -712,7 +712,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e ...@@ -712,7 +712,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e
return nodes, nil return nodes, nil
} }
func (_q *UserQuery) loadAPIKeys(ctx context.Context, query *APIKeyQuery, nodes []*User, init func(*User), assign func(*User, *APIKey)) error { func (_q *UserQuery) loadAPIKeys(ctx context.Context, query *ApiKeyQuery, nodes []*User, init func(*User), assign func(*User, *ApiKey)) error {
fks := make([]driver.Value, 0, len(nodes)) fks := make([]driver.Value, 0, len(nodes))
nodeids := make(map[int64]*User) nodeids := make(map[int64]*User)
for i := range nodes { for i := range nodes {
...@@ -725,7 +725,7 @@ func (_q *UserQuery) loadAPIKeys(ctx context.Context, query *APIKeyQuery, nodes ...@@ -725,7 +725,7 @@ func (_q *UserQuery) loadAPIKeys(ctx context.Context, query *APIKeyQuery, nodes
if len(query.ctx.Fields) > 0 { if len(query.ctx.Fields) > 0 {
query.ctx.AppendFieldOnce(apikey.FieldUserID) query.ctx.AppendFieldOnce(apikey.FieldUserID)
} }
query.Where(predicate.APIKey(func(s *sql.Selector) { query.Where(predicate.ApiKey(func(s *sql.Selector) {
s.Where(sql.InValues(s.C(user.APIKeysColumn), fks...)) s.Where(sql.InValues(s.C(user.APIKeysColumn), fks...))
})) }))
neighbors, err := query.All(ctx) neighbors, err := query.All(ctx)
......
...@@ -186,14 +186,14 @@ func (_u *UserUpdate) SetNillableNotes(v *string) *UserUpdate { ...@@ -186,14 +186,14 @@ func (_u *UserUpdate) SetNillableNotes(v *string) *UserUpdate {
return _u return _u
} }
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. // AddAPIKeyIDs adds the "api_keys" edge to the ApiKey entity by IDs.
func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate { func (_u *UserUpdate) AddAPIKeyIDs(ids ...int64) *UserUpdate {
_u.mutation.AddAPIKeyIDs(ids...) _u.mutation.AddAPIKeyIDs(ids...)
return _u return _u
} }
// AddAPIKeys adds the "api_keys" edges to the APIKey entity. // AddAPIKeys adds the "api_keys" edges to the ApiKey entity.
func (_u *UserUpdate) AddAPIKeys(v ...*APIKey) *UserUpdate { func (_u *UserUpdate) AddAPIKeys(v ...*ApiKey) *UserUpdate {
ids := make([]int64, len(v)) ids := make([]int64, len(v))
for i := range v { for i := range v {
ids[i] = v[i].ID ids[i] = v[i].ID
...@@ -296,20 +296,20 @@ func (_u *UserUpdate) Mutation() *UserMutation { ...@@ -296,20 +296,20 @@ func (_u *UserUpdate) Mutation() *UserMutation {
return _u.mutation return _u.mutation
} }
// ClearAPIKeys clears all "api_keys" edges to the APIKey entity. // ClearAPIKeys clears all "api_keys" edges to the ApiKey entity.
func (_u *UserUpdate) ClearAPIKeys() *UserUpdate { func (_u *UserUpdate) ClearAPIKeys() *UserUpdate {
_u.mutation.ClearAPIKeys() _u.mutation.ClearAPIKeys()
return _u return _u
} }
// RemoveAPIKeyIDs removes the "api_keys" edge to APIKey entities by IDs. // RemoveAPIKeyIDs removes the "api_keys" edge to ApiKey entities by IDs.
func (_u *UserUpdate) RemoveAPIKeyIDs(ids ...int64) *UserUpdate { func (_u *UserUpdate) RemoveAPIKeyIDs(ids ...int64) *UserUpdate {
_u.mutation.RemoveAPIKeyIDs(ids...) _u.mutation.RemoveAPIKeyIDs(ids...)
return _u return _u
} }
// RemoveAPIKeys removes "api_keys" edges to APIKey entities. // RemoveAPIKeys removes "api_keys" edges to ApiKey entities.
func (_u *UserUpdate) RemoveAPIKeys(v ...*APIKey) *UserUpdate { func (_u *UserUpdate) RemoveAPIKeys(v ...*ApiKey) *UserUpdate {
ids := make([]int64, len(v)) ids := make([]int64, len(v))
for i := range v { for i := range v {
ids[i] = v[i].ID ids[i] = v[i].ID
...@@ -1065,14 +1065,14 @@ func (_u *UserUpdateOne) SetNillableNotes(v *string) *UserUpdateOne { ...@@ -1065,14 +1065,14 @@ func (_u *UserUpdateOne) SetNillableNotes(v *string) *UserUpdateOne {
return _u return _u
} }
// AddAPIKeyIDs adds the "api_keys" edge to the APIKey entity by IDs. // AddAPIKeyIDs adds the "api_keys" edge to the ApiKey entity by IDs.
func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne { func (_u *UserUpdateOne) AddAPIKeyIDs(ids ...int64) *UserUpdateOne {
_u.mutation.AddAPIKeyIDs(ids...) _u.mutation.AddAPIKeyIDs(ids...)
return _u return _u
} }
// AddAPIKeys adds the "api_keys" edges to the APIKey entity. // AddAPIKeys adds the "api_keys" edges to the ApiKey entity.
func (_u *UserUpdateOne) AddAPIKeys(v ...*APIKey) *UserUpdateOne { func (_u *UserUpdateOne) AddAPIKeys(v ...*ApiKey) *UserUpdateOne {
ids := make([]int64, len(v)) ids := make([]int64, len(v))
for i := range v { for i := range v {
ids[i] = v[i].ID ids[i] = v[i].ID
...@@ -1175,20 +1175,20 @@ func (_u *UserUpdateOne) Mutation() *UserMutation { ...@@ -1175,20 +1175,20 @@ func (_u *UserUpdateOne) Mutation() *UserMutation {
return _u.mutation return _u.mutation
} }
// ClearAPIKeys clears all "api_keys" edges to the APIKey entity. // ClearAPIKeys clears all "api_keys" edges to the ApiKey entity.
func (_u *UserUpdateOne) ClearAPIKeys() *UserUpdateOne { func (_u *UserUpdateOne) ClearAPIKeys() *UserUpdateOne {
_u.mutation.ClearAPIKeys() _u.mutation.ClearAPIKeys()
return _u return _u
} }
// RemoveAPIKeyIDs removes the "api_keys" edge to APIKey entities by IDs. // RemoveAPIKeyIDs removes the "api_keys" edge to ApiKey entities by IDs.
func (_u *UserUpdateOne) RemoveAPIKeyIDs(ids ...int64) *UserUpdateOne { func (_u *UserUpdateOne) RemoveAPIKeyIDs(ids ...int64) *UserUpdateOne {
_u.mutation.RemoveAPIKeyIDs(ids...) _u.mutation.RemoveAPIKeyIDs(ids...)
return _u return _u
} }
// RemoveAPIKeys removes "api_keys" edges to APIKey entities. // RemoveAPIKeys removes "api_keys" edges to ApiKey entities.
func (_u *UserUpdateOne) RemoveAPIKeys(v ...*APIKey) *UserUpdateOne { func (_u *UserUpdateOne) RemoveAPIKeys(v ...*ApiKey) *UserUpdateOne {
ids := make([]int64, len(v)) ids := make([]int64, len(v))
for i := range v { for i := range v {
ids[i] = v[i].ID ids[i] = v[i].ID
......
...@@ -69,7 +69,6 @@ require ( ...@@ -69,7 +69,6 @@ require (
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/subcommands v1.2.0 // indirect github.com/google/subcommands v1.2.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.18.1 // indirect github.com/hashicorp/hcl/v2 v2.18.1 // indirect
......
...@@ -118,8 +118,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= ...@@ -118,8 +118,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
......
// Package config provides application configuration management.
package config package config
import ( import (
...@@ -140,7 +139,7 @@ type GatewayConfig struct { ...@@ -140,7 +139,7 @@ type GatewayConfig struct {
LogUpstreamErrorBodyMaxBytes int `mapstructure:"log_upstream_error_body_max_bytes"` LogUpstreamErrorBodyMaxBytes int `mapstructure:"log_upstream_error_body_max_bytes"`
// API-key 账号在客户端未提供 anthropic-beta 时,是否按需自动补齐(默认关闭以保持兼容) // API-key 账号在客户端未提供 anthropic-beta 时,是否按需自动补齐(默认关闭以保持兼容)
InjectBetaForAPIKey bool `mapstructure:"inject_beta_for_apikey"` InjectBetaForApiKey bool `mapstructure:"inject_beta_for_apikey"`
// 是否允许对部分 400 错误触发 failover(默认关闭以避免改变语义) // 是否允许对部分 400 错误触发 failover(默认关闭以避免改变语义)
FailoverOn400 bool `mapstructure:"failover_on_400"` FailoverOn400 bool `mapstructure:"failover_on_400"`
...@@ -242,7 +241,7 @@ type DefaultConfig struct { ...@@ -242,7 +241,7 @@ type DefaultConfig struct {
AdminPassword string `mapstructure:"admin_password"` AdminPassword string `mapstructure:"admin_password"`
UserConcurrency int `mapstructure:"user_concurrency"` UserConcurrency int `mapstructure:"user_concurrency"`
UserBalance float64 `mapstructure:"user_balance"` UserBalance float64 `mapstructure:"user_balance"`
APIKeyPrefix string `mapstructure:"api_key_prefix"` ApiKeyPrefix string `mapstructure:"api_key_prefix"`
RateMultiplier float64 `mapstructure:"rate_multiplier"` RateMultiplier float64 `mapstructure:"rate_multiplier"`
} }
......
// Package config provides application configuration management.
package config package config
import "github.com/google/wire" import "github.com/google/wire"
......
// Package admin provides HTTP handlers for administrative operations including
// dashboard statistics, user management, API key management, and account management.
package admin package admin
import ( import (
...@@ -77,8 +75,8 @@ func (h *DashboardHandler) GetStats(c *gin.Context) { ...@@ -77,8 +75,8 @@ func (h *DashboardHandler) GetStats(c *gin.Context) {
"active_users": stats.ActiveUsers, "active_users": stats.ActiveUsers,
// API Key 统计 // API Key 统计
"total_api_keys": stats.TotalAPIKeys, "total_api_keys": stats.TotalApiKeys,
"active_api_keys": stats.ActiveAPIKeys, "active_api_keys": stats.ActiveApiKeys,
// 账户统计 // 账户统计
"total_accounts": stats.TotalAccounts, "total_accounts": stats.TotalAccounts,
...@@ -195,10 +193,10 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) { ...@@ -195,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 // GET /api/v1/admin/dashboard/api-keys-trend
// Query params: start_date, end_date (YYYY-MM-DD), granularity (day/hour), limit (default 5) // 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) startTime, endTime := parseTimeRange(c)
granularity := c.DefaultQuery("granularity", "day") granularity := c.DefaultQuery("granularity", "day")
limitStr := c.DefaultQuery("limit", "5") limitStr := c.DefaultQuery("limit", "5")
...@@ -207,7 +205,7 @@ func (h *DashboardHandler) GetAPIKeyUsageTrend(c *gin.Context) { ...@@ -207,7 +205,7 @@ func (h *DashboardHandler) GetAPIKeyUsageTrend(c *gin.Context) {
limit = 5 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 { if err != nil {
response.Error(c, 500, "Failed to get API key usage trend") response.Error(c, 500, "Failed to get API key usage trend")
return return
...@@ -275,26 +273,26 @@ func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) { ...@@ -275,26 +273,26 @@ func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
response.Success(c, gin.H{"stats": stats}) response.Success(c, gin.H{"stats": stats})
} }
// BatchAPIKeysUsageRequest represents the request body for batch api key usage stats // BatchApiKeysUsageRequest represents the request body for batch api key usage stats
type BatchAPIKeysUsageRequest struct { type BatchApiKeysUsageRequest struct {
APIKeyIDs []int64 `json:"api_key_ids" binding:"required"` 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 // POST /api/v1/admin/dashboard/api-keys-usage
func (h *DashboardHandler) GetBatchAPIKeysUsage(c *gin.Context) { func (h *DashboardHandler) GetBatchApiKeysUsage(c *gin.Context) {
var req BatchAPIKeysUsageRequest var req BatchApiKeysUsageRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error()) response.BadRequest(c, "Invalid request: "+err.Error())
return return
} }
if len(req.APIKeyIDs) == 0 { if len(req.ApiKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}}) response.Success(c, gin.H{"stats": map[string]any{}})
return return
} }
stats, err := h.dashboardService.GetBatchAPIKeyUsageStats(c.Request.Context(), req.APIKeyIDs) stats, err := h.dashboardService.GetBatchApiKeyUsageStats(c.Request.Context(), req.ApiKeyIDs)
if err != nil { if err != nil {
response.Error(c, 500, "Failed to get API key usage stats") response.Error(c, 500, "Failed to get API key usage stats")
return return
......
...@@ -237,9 +237,9 @@ func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) { ...@@ -237,9 +237,9 @@ func (h *GroupHandler) GetGroupAPIKeys(c *gin.Context) {
return return
} }
outKeys := make([]dto.APIKey, 0, len(keys)) outKeys := make([]dto.ApiKey, 0, len(keys))
for i := range 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) response.Paginated(c, outKeys, total, page, pageSize)
} }
...@@ -36,24 +36,29 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { ...@@ -36,24 +36,29 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
response.Success(c, dto.SystemSettings{ response.Success(c, dto.SystemSettings{
RegistrationEnabled: settings.RegistrationEnabled, RegistrationEnabled: settings.RegistrationEnabled,
EmailVerifyEnabled: settings.EmailVerifyEnabled, EmailVerifyEnabled: settings.EmailVerifyEnabled,
SMTPHost: settings.SMTPHost, SmtpHost: settings.SmtpHost,
SMTPPort: settings.SMTPPort, SmtpPort: settings.SmtpPort,
SMTPUsername: settings.SMTPUsername, SmtpUsername: settings.SmtpUsername,
SMTPPassword: settings.SMTPPassword, SmtpPassword: settings.SmtpPassword,
SMTPFrom: settings.SMTPFrom, SmtpFrom: settings.SmtpFrom,
SMTPFromName: settings.SMTPFromName, SmtpFromName: settings.SmtpFromName,
SMTPUseTLS: settings.SMTPUseTLS, SmtpUseTLS: settings.SmtpUseTLS,
TurnstileEnabled: settings.TurnstileEnabled, TurnstileEnabled: settings.TurnstileEnabled,
TurnstileSiteKey: settings.TurnstileSiteKey, TurnstileSiteKey: settings.TurnstileSiteKey,
TurnstileSecretKey: settings.TurnstileSecretKey, TurnstileSecretKey: settings.TurnstileSecretKey,
SiteName: settings.SiteName, SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo, SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle, SiteSubtitle: settings.SiteSubtitle,
APIBaseURL: settings.APIBaseURL, ApiBaseUrl: settings.ApiBaseUrl,
ContactInfo: settings.ContactInfo, ContactInfo: settings.ContactInfo,
DocURL: settings.DocURL, DocUrl: settings.DocUrl,
DefaultConcurrency: settings.DefaultConcurrency, DefaultConcurrency: settings.DefaultConcurrency,
DefaultBalance: settings.DefaultBalance, DefaultBalance: settings.DefaultBalance,
EnableModelFallback: settings.EnableModelFallback,
FallbackModelAnthropic: settings.FallbackModelAnthropic,
FallbackModelOpenAI: settings.FallbackModelOpenAI,
FallbackModelGemini: settings.FallbackModelGemini,
FallbackModelAntigravity: settings.FallbackModelAntigravity,
}) })
} }
...@@ -64,13 +69,13 @@ type UpdateSettingsRequest struct { ...@@ -64,13 +69,13 @@ type UpdateSettingsRequest struct {
EmailVerifyEnabled bool `json:"email_verify_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"`
// 邮件服务设置 // 邮件服务设置
SMTPHost string `json:"smtp_host"` SmtpHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"` SmtpPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"` SmtpUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"` SmtpPassword string `json:"smtp_password"`
SMTPFrom string `json:"smtp_from_email"` SmtpFrom string `json:"smtp_from_email"`
SMTPFromName string `json:"smtp_from_name"` SmtpFromName string `json:"smtp_from_name"`
SMTPUseTLS bool `json:"smtp_use_tls"` SmtpUseTLS bool `json:"smtp_use_tls"`
// Cloudflare Turnstile 设置 // Cloudflare Turnstile 设置
TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"`
...@@ -81,13 +86,20 @@ type UpdateSettingsRequest struct { ...@@ -81,13 +86,20 @@ type UpdateSettingsRequest struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"` ApiBaseUrl string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocURL string `json:"doc_url"` DocUrl string `json:"doc_url"`
// 默认配置 // 默认配置
DefaultConcurrency int `json:"default_concurrency"` DefaultConcurrency int `json:"default_concurrency"`
DefaultBalance float64 `json:"default_balance"` DefaultBalance float64 `json:"default_balance"`
// Model fallback configuration
EnableModelFallback bool `json:"enable_model_fallback"`
FallbackModelAnthropic string `json:"fallback_model_anthropic"`
FallbackModelOpenAI string `json:"fallback_model_openai"`
FallbackModelGemini string `json:"fallback_model_gemini"`
FallbackModelAntigravity string `json:"fallback_model_antigravity"`
} }
// UpdateSettings 更新系统设置 // UpdateSettings 更新系统设置
...@@ -106,8 +118,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { ...@@ -106,8 +118,8 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
if req.DefaultBalance < 0 { if req.DefaultBalance < 0 {
req.DefaultBalance = 0 req.DefaultBalance = 0
} }
if req.SMTPPort <= 0 { if req.SmtpPort <= 0 {
req.SMTPPort = 587 req.SmtpPort = 587
} }
// Turnstile 参数验证 // Turnstile 参数验证
...@@ -143,24 +155,29 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { ...@@ -143,24 +155,29 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
settings := &service.SystemSettings{ settings := &service.SystemSettings{
RegistrationEnabled: req.RegistrationEnabled, RegistrationEnabled: req.RegistrationEnabled,
EmailVerifyEnabled: req.EmailVerifyEnabled, EmailVerifyEnabled: req.EmailVerifyEnabled,
SMTPHost: req.SMTPHost, SmtpHost: req.SmtpHost,
SMTPPort: req.SMTPPort, SmtpPort: req.SmtpPort,
SMTPUsername: req.SMTPUsername, SmtpUsername: req.SmtpUsername,
SMTPPassword: req.SMTPPassword, SmtpPassword: req.SmtpPassword,
SMTPFrom: req.SMTPFrom, SmtpFrom: req.SmtpFrom,
SMTPFromName: req.SMTPFromName, SmtpFromName: req.SmtpFromName,
SMTPUseTLS: req.SMTPUseTLS, SmtpUseTLS: req.SmtpUseTLS,
TurnstileEnabled: req.TurnstileEnabled, TurnstileEnabled: req.TurnstileEnabled,
TurnstileSiteKey: req.TurnstileSiteKey, TurnstileSiteKey: req.TurnstileSiteKey,
TurnstileSecretKey: req.TurnstileSecretKey, TurnstileSecretKey: req.TurnstileSecretKey,
SiteName: req.SiteName, SiteName: req.SiteName,
SiteLogo: req.SiteLogo, SiteLogo: req.SiteLogo,
SiteSubtitle: req.SiteSubtitle, SiteSubtitle: req.SiteSubtitle,
APIBaseURL: req.APIBaseURL, ApiBaseUrl: req.ApiBaseUrl,
ContactInfo: req.ContactInfo, ContactInfo: req.ContactInfo,
DocURL: req.DocURL, DocUrl: req.DocUrl,
DefaultConcurrency: req.DefaultConcurrency, DefaultConcurrency: req.DefaultConcurrency,
DefaultBalance: req.DefaultBalance, DefaultBalance: req.DefaultBalance,
EnableModelFallback: req.EnableModelFallback,
FallbackModelAnthropic: req.FallbackModelAnthropic,
FallbackModelOpenAI: req.FallbackModelOpenAI,
FallbackModelGemini: req.FallbackModelGemini,
FallbackModelAntigravity: req.FallbackModelAntigravity,
} }
if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil { if err := h.settingService.UpdateSettings(c.Request.Context(), settings); err != nil {
...@@ -178,67 +195,72 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { ...@@ -178,67 +195,72 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
response.Success(c, dto.SystemSettings{ response.Success(c, dto.SystemSettings{
RegistrationEnabled: updatedSettings.RegistrationEnabled, RegistrationEnabled: updatedSettings.RegistrationEnabled,
EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled, EmailVerifyEnabled: updatedSettings.EmailVerifyEnabled,
SMTPHost: updatedSettings.SMTPHost, SmtpHost: updatedSettings.SmtpHost,
SMTPPort: updatedSettings.SMTPPort, SmtpPort: updatedSettings.SmtpPort,
SMTPUsername: updatedSettings.SMTPUsername, SmtpUsername: updatedSettings.SmtpUsername,
SMTPPassword: updatedSettings.SMTPPassword, SmtpPassword: updatedSettings.SmtpPassword,
SMTPFrom: updatedSettings.SMTPFrom, SmtpFrom: updatedSettings.SmtpFrom,
SMTPFromName: updatedSettings.SMTPFromName, SmtpFromName: updatedSettings.SmtpFromName,
SMTPUseTLS: updatedSettings.SMTPUseTLS, SmtpUseTLS: updatedSettings.SmtpUseTLS,
TurnstileEnabled: updatedSettings.TurnstileEnabled, TurnstileEnabled: updatedSettings.TurnstileEnabled,
TurnstileSiteKey: updatedSettings.TurnstileSiteKey, TurnstileSiteKey: updatedSettings.TurnstileSiteKey,
TurnstileSecretKey: updatedSettings.TurnstileSecretKey, TurnstileSecretKey: updatedSettings.TurnstileSecretKey,
SiteName: updatedSettings.SiteName, SiteName: updatedSettings.SiteName,
SiteLogo: updatedSettings.SiteLogo, SiteLogo: updatedSettings.SiteLogo,
SiteSubtitle: updatedSettings.SiteSubtitle, SiteSubtitle: updatedSettings.SiteSubtitle,
APIBaseURL: updatedSettings.APIBaseURL, ApiBaseUrl: updatedSettings.ApiBaseUrl,
ContactInfo: updatedSettings.ContactInfo, ContactInfo: updatedSettings.ContactInfo,
DocURL: updatedSettings.DocURL, DocUrl: updatedSettings.DocUrl,
DefaultConcurrency: updatedSettings.DefaultConcurrency, DefaultConcurrency: updatedSettings.DefaultConcurrency,
DefaultBalance: updatedSettings.DefaultBalance, DefaultBalance: updatedSettings.DefaultBalance,
EnableModelFallback: updatedSettings.EnableModelFallback,
FallbackModelAnthropic: updatedSettings.FallbackModelAnthropic,
FallbackModelOpenAI: updatedSettings.FallbackModelOpenAI,
FallbackModelGemini: updatedSettings.FallbackModelGemini,
FallbackModelAntigravity: updatedSettings.FallbackModelAntigravity,
}) })
} }
// TestSMTPRequest 测试SMTP连接请求 // TestSmtpRequest 测试SMTP连接请求
type TestSMTPRequest struct { type TestSmtpRequest struct {
SMTPHost string `json:"smtp_host" binding:"required"` SmtpHost string `json:"smtp_host" binding:"required"`
SMTPPort int `json:"smtp_port"` SmtpPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"` SmtpUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"` SmtpPassword string `json:"smtp_password"`
SMTPUseTLS bool `json:"smtp_use_tls"` SmtpUseTLS bool `json:"smtp_use_tls"`
} }
// TestSMTPConnection 测试SMTP连接 // TestSmtpConnection 测试SMTP连接
// POST /api/v1/admin/settings/test-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 var req TestSmtpRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error()) response.BadRequest(c, "Invalid request: "+err.Error())
return return
} }
if req.SMTPPort <= 0 { if req.SmtpPort <= 0 {
req.SMTPPort = 587 req.SmtpPort = 587
} }
// 如果未提供密码,从数据库获取已保存的密码 // 如果未提供密码,从数据库获取已保存的密码
password := req.SMTPPassword password := req.SmtpPassword
if password == "" { if password == "" {
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context()) savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
if err == nil && savedConfig != nil { if err == nil && savedConfig != nil {
password = savedConfig.Password password = savedConfig.Password
} }
} }
config := &service.SMTPConfig{ config := &service.SmtpConfig{
Host: req.SMTPHost, Host: req.SmtpHost,
Port: req.SMTPPort, Port: req.SmtpPort,
Username: req.SMTPUsername, Username: req.SmtpUsername,
Password: password, Password: password,
UseTLS: req.SMTPUseTLS, UseTLS: req.SmtpUseTLS,
} }
err := h.emailService.TestSMTPConnectionWithConfig(config) err := h.emailService.TestSmtpConnectionWithConfig(config)
if err != nil { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
...@@ -250,13 +272,13 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) { ...@@ -250,13 +272,13 @@ func (h *SettingHandler) TestSMTPConnection(c *gin.Context) {
// SendTestEmailRequest 发送测试邮件请求 // SendTestEmailRequest 发送测试邮件请求
type SendTestEmailRequest struct { type SendTestEmailRequest struct {
Email string `json:"email" binding:"required,email"` Email string `json:"email" binding:"required,email"`
SMTPHost string `json:"smtp_host" binding:"required"` SmtpHost string `json:"smtp_host" binding:"required"`
SMTPPort int `json:"smtp_port"` SmtpPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"` SmtpUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"` SmtpPassword string `json:"smtp_password"`
SMTPFrom string `json:"smtp_from_email"` SmtpFrom string `json:"smtp_from_email"`
SMTPFromName string `json:"smtp_from_name"` SmtpFromName string `json:"smtp_from_name"`
SMTPUseTLS bool `json:"smtp_use_tls"` SmtpUseTLS bool `json:"smtp_use_tls"`
} }
// SendTestEmail 发送测试邮件 // SendTestEmail 发送测试邮件
...@@ -268,27 +290,27 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) { ...@@ -268,27 +290,27 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
return return
} }
if req.SMTPPort <= 0 { if req.SmtpPort <= 0 {
req.SMTPPort = 587 req.SmtpPort = 587
} }
// 如果未提供密码,从数据库获取已保存的密码 // 如果未提供密码,从数据库获取已保存的密码
password := req.SMTPPassword password := req.SmtpPassword
if password == "" { if password == "" {
savedConfig, err := h.emailService.GetSMTPConfig(c.Request.Context()) savedConfig, err := h.emailService.GetSmtpConfig(c.Request.Context())
if err == nil && savedConfig != nil { if err == nil && savedConfig != nil {
password = savedConfig.Password password = savedConfig.Password
} }
} }
config := &service.SMTPConfig{ config := &service.SmtpConfig{
Host: req.SMTPHost, Host: req.SmtpHost,
Port: req.SMTPPort, Port: req.SmtpPort,
Username: req.SMTPUsername, Username: req.SmtpUsername,
Password: password, Password: password,
From: req.SMTPFrom, From: req.SmtpFrom,
FromName: req.SMTPFromName, FromName: req.SmtpFromName,
UseTLS: req.SMTPUseTLS, UseTLS: req.SmtpUseTLS,
} }
siteName := h.settingService.GetSiteName(c.Request.Context()) siteName := h.settingService.GetSiteName(c.Request.Context())
...@@ -333,10 +355,10 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) { ...@@ -333,10 +355,10 @@ func (h *SettingHandler) SendTestEmail(c *gin.Context) {
response.Success(c, gin.H{"message": "Test email sent successfully"}) response.Success(c, gin.H{"message": "Test email sent successfully"})
} }
// GetAdminAPIKey 获取管理员 API Key 状态 // GetAdminApiKey 获取管理员 API Key 状态
// GET /api/v1/admin/settings/admin-api-key // GET /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) { func (h *SettingHandler) GetAdminApiKey(c *gin.Context) {
maskedKey, exists, err := h.settingService.GetAdminAPIKeyStatus(c.Request.Context()) maskedKey, exists, err := h.settingService.GetAdminApiKeyStatus(c.Request.Context())
if err != nil { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
...@@ -348,10 +370,10 @@ func (h *SettingHandler) GetAdminAPIKey(c *gin.Context) { ...@@ -348,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 // POST /api/v1/admin/settings/admin-api-key/regenerate
func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) { func (h *SettingHandler) RegenerateAdminApiKey(c *gin.Context) {
key, err := h.settingService.GenerateAdminAPIKey(c.Request.Context()) key, err := h.settingService.GenerateAdminApiKey(c.Request.Context())
if err != nil { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
...@@ -362,10 +384,10 @@ func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) { ...@@ -362,10 +384,10 @@ func (h *SettingHandler) RegenerateAdminAPIKey(c *gin.Context) {
}) })
} }
// DeleteAdminAPIKey 删除管理员 API Key // DeleteAdminApiKey 删除管理员 API Key
// DELETE /api/v1/admin/settings/admin-api-key // DELETE /api/v1/admin/settings/admin-api-key
func (h *SettingHandler) DeleteAdminAPIKey(c *gin.Context) { func (h *SettingHandler) DeleteAdminApiKey(c *gin.Context) {
if err := h.settingService.DeleteAdminAPIKey(c.Request.Context()); err != nil { if err := h.settingService.DeleteAdminApiKey(c.Request.Context()); err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
} }
......
...@@ -17,14 +17,14 @@ import ( ...@@ -17,14 +17,14 @@ import (
// UsageHandler handles admin usage-related requests // UsageHandler handles admin usage-related requests
type UsageHandler struct { type UsageHandler struct {
usageService *service.UsageService usageService *service.UsageService
apiKeyService *service.APIKeyService apiKeyService *service.ApiKeyService
adminService service.AdminService adminService service.AdminService
} }
// NewUsageHandler creates a new admin usage handler // NewUsageHandler creates a new admin usage handler
func NewUsageHandler( func NewUsageHandler(
usageService *service.UsageService, usageService *service.UsageService,
apiKeyService *service.APIKeyService, apiKeyService *service.ApiKeyService,
adminService service.AdminService, adminService service.AdminService,
) *UsageHandler { ) *UsageHandler {
return &UsageHandler{ return &UsageHandler{
...@@ -125,7 +125,7 @@ func (h *UsageHandler) List(c *gin.Context) { ...@@ -125,7 +125,7 @@ func (h *UsageHandler) List(c *gin.Context) {
params := pagination.PaginationParams{Page: page, PageSize: pageSize} params := pagination.PaginationParams{Page: page, PageSize: pageSize}
filters := usagestats.UsageLogFilters{ filters := usagestats.UsageLogFilters{
UserID: userID, UserID: userID,
APIKeyID: apiKeyID, ApiKeyID: apiKeyID,
AccountID: accountID, AccountID: accountID,
GroupID: groupID, GroupID: groupID,
Model: model, Model: model,
...@@ -207,7 +207,7 @@ func (h *UsageHandler) Stats(c *gin.Context) { ...@@ -207,7 +207,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
} }
if apiKeyID > 0 { 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 { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
...@@ -269,9 +269,9 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) { ...@@ -269,9 +269,9 @@ func (h *UsageHandler) SearchUsers(c *gin.Context) {
response.Success(c, result) 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 // 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") userIDStr := c.Query("user_id")
keyword := c.Query("q") keyword := c.Query("q")
...@@ -285,22 +285,22 @@ func (h *UsageHandler) SearchAPIKeys(c *gin.Context) { ...@@ -285,22 +285,22 @@ func (h *UsageHandler) SearchAPIKeys(c *gin.Context) {
userID = id 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 { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
} }
// Return simplified API key list (only id and name) // Return simplified API key list (only id and name)
type SimpleAPIKey struct { type SimpleApiKey struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Name string `json:"name"` Name string `json:"name"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
} }
result := make([]SimpleAPIKey, len(keys)) result := make([]SimpleApiKey, len(keys))
for i, k := range keys { for i, k := range keys {
result[i] = SimpleAPIKey{ result[i] = SimpleApiKey{
ID: k.ID, ID: k.ID,
Name: k.Name, Name: k.Name,
UserID: k.UserID, UserID: k.UserID,
......
...@@ -243,9 +243,9 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) { ...@@ -243,9 +243,9 @@ func (h *UserHandler) GetUserAPIKeys(c *gin.Context) {
return return
} }
out := make([]dto.APIKey, 0, len(keys)) out := make([]dto.ApiKey, 0, len(keys))
for i := range 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) response.Paginated(c, out, total, page, pageSize)
} }
......
...@@ -14,11 +14,11 @@ import ( ...@@ -14,11 +14,11 @@ import (
// APIKeyHandler handles API key-related requests // APIKeyHandler handles API key-related requests
type APIKeyHandler struct { type APIKeyHandler struct {
apiKeyService *service.APIKeyService apiKeyService *service.ApiKeyService
} }
// NewAPIKeyHandler creates a new APIKeyHandler // NewAPIKeyHandler creates a new APIKeyHandler
func NewAPIKeyHandler(apiKeyService *service.APIKeyService) *APIKeyHandler { func NewAPIKeyHandler(apiKeyService *service.ApiKeyService) *APIKeyHandler {
return &APIKeyHandler{ return &APIKeyHandler{
apiKeyService: apiKeyService, apiKeyService: apiKeyService,
} }
...@@ -56,9 +56,9 @@ func (h *APIKeyHandler) List(c *gin.Context) { ...@@ -56,9 +56,9 @@ func (h *APIKeyHandler) List(c *gin.Context) {
return return
} }
out := make([]dto.APIKey, 0, len(keys)) out := make([]dto.ApiKey, 0, len(keys))
for i := range 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) response.Paginated(c, out, result.Total, page, pageSize)
} }
...@@ -90,7 +90,7 @@ func (h *APIKeyHandler) GetByID(c *gin.Context) { ...@@ -90,7 +90,7 @@ func (h *APIKeyHandler) GetByID(c *gin.Context) {
return return
} }
response.Success(c, dto.APIKeyFromService(key)) response.Success(c, dto.ApiKeyFromService(key))
} }
// Create handles creating a new API key // Create handles creating a new API key
...@@ -108,7 +108,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) { ...@@ -108,7 +108,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
return return
} }
svcReq := service.CreateAPIKeyRequest{ svcReq := service.CreateApiKeyRequest{
Name: req.Name, Name: req.Name,
GroupID: req.GroupID, GroupID: req.GroupID,
CustomKey: req.CustomKey, CustomKey: req.CustomKey,
...@@ -119,7 +119,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) { ...@@ -119,7 +119,7 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
return return
} }
response.Success(c, dto.APIKeyFromService(key)) response.Success(c, dto.ApiKeyFromService(key))
} }
// Update handles updating an API key // Update handles updating an API key
...@@ -143,7 +143,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) { ...@@ -143,7 +143,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
return return
} }
svcReq := service.UpdateAPIKeyRequest{} svcReq := service.UpdateApiKeyRequest{}
if req.Name != "" { if req.Name != "" {
svcReq.Name = &req.Name svcReq.Name = &req.Name
} }
...@@ -158,7 +158,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) { ...@@ -158,7 +158,7 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
return return
} }
response.Success(c, dto.APIKeyFromService(key)) response.Success(c, dto.ApiKeyFromService(key))
} }
// Delete handles deleting an API key // Delete handles deleting an API key
......
...@@ -5,13 +5,13 @@ type SystemSettings struct { ...@@ -5,13 +5,13 @@ type SystemSettings struct {
RegistrationEnabled bool `json:"registration_enabled"` RegistrationEnabled bool `json:"registration_enabled"`
EmailVerifyEnabled bool `json:"email_verify_enabled"` EmailVerifyEnabled bool `json:"email_verify_enabled"`
SMTPHost string `json:"smtp_host"` SmtpHost string `json:"smtp_host"`
SMTPPort int `json:"smtp_port"` SmtpPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"` SmtpUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password,omitempty"` SmtpPassword string `json:"smtp_password,omitempty"`
SMTPFrom string `json:"smtp_from_email"` SmtpFrom string `json:"smtp_from_email"`
SMTPFromName string `json:"smtp_from_name"` SmtpFromName string `json:"smtp_from_name"`
SMTPUseTLS bool `json:"smtp_use_tls"` SmtpUseTLS bool `json:"smtp_use_tls"`
TurnstileEnabled bool `json:"turnstile_enabled"` TurnstileEnabled bool `json:"turnstile_enabled"`
TurnstileSiteKey string `json:"turnstile_site_key"` TurnstileSiteKey string `json:"turnstile_site_key"`
...@@ -20,12 +20,19 @@ type SystemSettings struct { ...@@ -20,12 +20,19 @@ type SystemSettings struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"` ApiBaseUrl string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocURL string `json:"doc_url"` DocUrl string `json:"doc_url"`
DefaultConcurrency int `json:"default_concurrency"` DefaultConcurrency int `json:"default_concurrency"`
DefaultBalance float64 `json:"default_balance"` DefaultBalance float64 `json:"default_balance"`
// Model fallback configuration
EnableModelFallback bool `json:"enable_model_fallback"`
FallbackModelAnthropic string `json:"fallback_model_anthropic"`
FallbackModelOpenAI string `json:"fallback_model_openai"`
FallbackModelGemini string `json:"fallback_model_gemini"`
FallbackModelAntigravity string `json:"fallback_model_antigravity"`
} }
type PublicSettings struct { type PublicSettings struct {
...@@ -36,8 +43,8 @@ type PublicSettings struct { ...@@ -36,8 +43,8 @@ type PublicSettings struct {
SiteName string `json:"site_name"` SiteName string `json:"site_name"`
SiteLogo string `json:"site_logo"` SiteLogo string `json:"site_logo"`
SiteSubtitle string `json:"site_subtitle"` SiteSubtitle string `json:"site_subtitle"`
APIBaseURL string `json:"api_base_url"` ApiBaseUrl string `json:"api_base_url"`
ContactInfo string `json:"contact_info"` ContactInfo string `json:"contact_info"`
DocURL string `json:"doc_url"` DocUrl string `json:"doc_url"`
Version string `json:"version"` Version string `json:"version"`
} }
...@@ -7,7 +7,6 @@ import ( ...@@ -7,7 +7,6 @@ import (
// AdminHandlers contains all admin-related HTTP handlers // AdminHandlers contains all admin-related HTTP handlers
type AdminHandlers struct { type AdminHandlers struct {
Dashboard *admin.DashboardHandler Dashboard *admin.DashboardHandler
Ops *admin.OpsHandler
User *admin.UserHandler User *admin.UserHandler
Group *admin.GroupHandler Group *admin.GroupHandler
Account *admin.AccountHandler Account *admin.AccountHandler
......
...@@ -22,7 +22,6 @@ type OpenAIGatewayHandler struct { ...@@ -22,7 +22,6 @@ type OpenAIGatewayHandler struct {
gatewayService *service.OpenAIGatewayService gatewayService *service.OpenAIGatewayService
billingCacheService *service.BillingCacheService billingCacheService *service.BillingCacheService
concurrencyHelper *ConcurrencyHelper concurrencyHelper *ConcurrencyHelper
opsService *service.OpsService
} }
// NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler // NewOpenAIGatewayHandler creates a new OpenAIGatewayHandler
...@@ -30,21 +29,19 @@ func NewOpenAIGatewayHandler( ...@@ -30,21 +29,19 @@ func NewOpenAIGatewayHandler(
gatewayService *service.OpenAIGatewayService, gatewayService *service.OpenAIGatewayService,
concurrencyService *service.ConcurrencyService, concurrencyService *service.ConcurrencyService,
billingCacheService *service.BillingCacheService, billingCacheService *service.BillingCacheService,
opsService *service.OpsService,
) *OpenAIGatewayHandler { ) *OpenAIGatewayHandler {
return &OpenAIGatewayHandler{ return &OpenAIGatewayHandler{
gatewayService: gatewayService, gatewayService: gatewayService,
billingCacheService: billingCacheService, billingCacheService: billingCacheService,
concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatNone), concurrencyHelper: NewConcurrencyHelper(concurrencyService, SSEPingFormatNone),
opsService: opsService,
} }
} }
// Responses handles OpenAI Responses API endpoint // Responses handles OpenAI Responses API endpoint
// POST /openai/v1/responses // POST /openai/v1/responses
func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// Get apiKey and user from context (set by APIKeyAuth middleware) // Get apiKey and user from context (set by ApiKeyAuth middleware)
apiKey, ok := middleware2.GetAPIKeyFromContext(c) apiKey, ok := middleware2.GetApiKeyFromContext(c)
if !ok { if !ok {
h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key") h.errorResponse(c, http.StatusUnauthorized, "authentication_error", "Invalid API key")
return return
...@@ -82,7 +79,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { ...@@ -82,7 +79,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
// Extract model and stream // Extract model and stream
reqModel, _ := reqBody["model"].(string) reqModel, _ := reqBody["model"].(string)
reqStream, _ := reqBody["stream"].(bool) reqStream, _ := reqBody["stream"].(bool)
setOpsRequestContext(c, reqModel, reqStream)
// 验证 model 必填 // 验证 model 必填
if reqModel == "" { if reqModel == "" {
...@@ -239,7 +235,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { ...@@ -239,7 +235,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
defer cancel() defer cancel()
if err := h.gatewayService.RecordUsage(ctx, &service.OpenAIRecordUsageInput{ if err := h.gatewayService.RecordUsage(ctx, &service.OpenAIRecordUsageInput{
Result: result, Result: result,
APIKey: apiKey, ApiKey: apiKey,
User: apiKey.User, User: apiKey.User,
Account: usedAccount, Account: usedAccount,
Subscription: subscription, Subscription: subscription,
...@@ -282,7 +278,6 @@ func (h *OpenAIGatewayHandler) mapUpstreamError(statusCode int) (int, string, st ...@@ -282,7 +278,6 @@ func (h *OpenAIGatewayHandler) mapUpstreamError(statusCode int) (int, string, st
// handleStreamingAwareError handles errors that may occur after streaming has started // handleStreamingAwareError handles errors that may occur after streaming has started
func (h *OpenAIGatewayHandler) handleStreamingAwareError(c *gin.Context, status int, errType, message string, streamStarted bool) { func (h *OpenAIGatewayHandler) handleStreamingAwareError(c *gin.Context, status int, errType, message string, streamStarted bool) {
if streamStarted { if streamStarted {
recordOpsError(c, h.opsService, status, errType, message, service.PlatformOpenAI)
// Stream already started, send error as SSE event then close // Stream already started, send error as SSE event then close
flusher, ok := c.Writer.(http.Flusher) flusher, ok := c.Writer.(http.Flusher)
if ok { if ok {
...@@ -302,7 +297,6 @@ func (h *OpenAIGatewayHandler) handleStreamingAwareError(c *gin.Context, status ...@@ -302,7 +297,6 @@ func (h *OpenAIGatewayHandler) handleStreamingAwareError(c *gin.Context, status
// errorResponse returns OpenAI API format error response // errorResponse returns OpenAI API format error response
func (h *OpenAIGatewayHandler) errorResponse(c *gin.Context, status int, errType, message string) { func (h *OpenAIGatewayHandler) errorResponse(c *gin.Context, status int, errType, message string) {
recordOpsError(c, h.opsService, status, errType, message, service.PlatformOpenAI)
c.JSON(status, gin.H{ c.JSON(status, gin.H{
"error": gin.H{ "error": gin.H{
"type": errType, "type": errType,
......
...@@ -39,9 +39,9 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) { ...@@ -39,9 +39,9 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) {
SiteName: settings.SiteName, SiteName: settings.SiteName,
SiteLogo: settings.SiteLogo, SiteLogo: settings.SiteLogo,
SiteSubtitle: settings.SiteSubtitle, SiteSubtitle: settings.SiteSubtitle,
APIBaseURL: settings.APIBaseURL, ApiBaseUrl: settings.ApiBaseUrl,
ContactInfo: settings.ContactInfo, ContactInfo: settings.ContactInfo,
DocURL: settings.DocURL, DocUrl: settings.DocUrl,
Version: h.version, Version: h.version,
}) })
} }
...@@ -18,11 +18,11 @@ import ( ...@@ -18,11 +18,11 @@ import (
// UsageHandler handles usage-related requests // UsageHandler handles usage-related requests
type UsageHandler struct { type UsageHandler struct {
usageService *service.UsageService usageService *service.UsageService
apiKeyService *service.APIKeyService apiKeyService *service.ApiKeyService
} }
// NewUsageHandler creates a new UsageHandler // 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{ return &UsageHandler{
usageService: usageService, usageService: usageService,
apiKeyService: apiKeyService, apiKeyService: apiKeyService,
...@@ -111,7 +111,7 @@ func (h *UsageHandler) List(c *gin.Context) { ...@@ -111,7 +111,7 @@ func (h *UsageHandler) List(c *gin.Context) {
params := pagination.PaginationParams{Page: page, PageSize: pageSize} params := pagination.PaginationParams{Page: page, PageSize: pageSize}
filters := usagestats.UsageLogFilters{ filters := usagestats.UsageLogFilters{
UserID: subject.UserID, // Always filter by current user for security UserID: subject.UserID, // Always filter by current user for security
APIKeyID: apiKeyID, ApiKeyID: apiKeyID,
Model: model, Model: model,
Stream: stream, Stream: stream,
BillingType: billingType, BillingType: billingType,
...@@ -235,7 +235,7 @@ func (h *UsageHandler) Stats(c *gin.Context) { ...@@ -235,7 +235,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
var stats *service.UsageStats var stats *service.UsageStats
var err error var err error
if apiKeyID > 0 { 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 { } else {
stats, err = h.usageService.GetStatsByUser(c.Request.Context(), subject.UserID, startTime, endTime) stats, err = h.usageService.GetStatsByUser(c.Request.Context(), subject.UserID, startTime, endTime)
} }
...@@ -346,49 +346,49 @@ func (h *UsageHandler) DashboardModels(c *gin.Context) { ...@@ -346,49 +346,49 @@ func (h *UsageHandler) DashboardModels(c *gin.Context) {
}) })
} }
// BatchAPIKeysUsageRequest represents the request for batch API keys usage // BatchApiKeysUsageRequest represents the request for batch API keys usage
type BatchAPIKeysUsageRequest struct { type BatchApiKeysUsageRequest struct {
APIKeyIDs []int64 `json:"api_key_ids" binding:"required"` 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 // 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) subject, ok := middleware2.GetAuthSubjectFromContext(c)
if !ok { if !ok {
response.Unauthorized(c, "User not authenticated") response.Unauthorized(c, "User not authenticated")
return return
} }
var req BatchAPIKeysUsageRequest var req BatchApiKeysUsageRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "Invalid request: "+err.Error()) response.BadRequest(c, "Invalid request: "+err.Error())
return return
} }
if len(req.APIKeyIDs) == 0 { if len(req.ApiKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}}) response.Success(c, gin.H{"stats": map[string]any{}})
return return
} }
// Limit the number of API key IDs to prevent SQL parameter overflow // 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)") response.BadRequest(c, "Too many API key IDs (maximum 100 allowed)")
return 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 { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
} }
if len(validAPIKeyIDs) == 0 { if len(validApiKeyIDs) == 0 {
response.Success(c, gin.H{"stats": map[string]any{}}) response.Success(c, gin.H{"stats": map[string]any{}})
return return
} }
stats, err := h.usageService.GetBatchAPIKeyUsageStats(c.Request.Context(), validAPIKeyIDs) stats, err := h.usageService.GetBatchApiKeyUsageStats(c.Request.Context(), validApiKeyIDs)
if err != nil { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
......
...@@ -10,7 +10,6 @@ import ( ...@@ -10,7 +10,6 @@ import (
// ProvideAdminHandlers creates the AdminHandlers struct // ProvideAdminHandlers creates the AdminHandlers struct
func ProvideAdminHandlers( func ProvideAdminHandlers(
dashboardHandler *admin.DashboardHandler, dashboardHandler *admin.DashboardHandler,
opsHandler *admin.OpsHandler,
userHandler *admin.UserHandler, userHandler *admin.UserHandler,
groupHandler *admin.GroupHandler, groupHandler *admin.GroupHandler,
accountHandler *admin.AccountHandler, accountHandler *admin.AccountHandler,
...@@ -28,7 +27,6 @@ func ProvideAdminHandlers( ...@@ -28,7 +27,6 @@ func ProvideAdminHandlers(
) *AdminHandlers { ) *AdminHandlers {
return &AdminHandlers{ return &AdminHandlers{
Dashboard: dashboardHandler, Dashboard: dashboardHandler,
Ops: opsHandler,
User: userHandler, User: userHandler,
Group: groupHandler, Group: groupHandler,
Account: accountHandler, Account: accountHandler,
...@@ -98,7 +96,6 @@ var ProviderSet = wire.NewSet( ...@@ -98,7 +96,6 @@ var ProviderSet = wire.NewSet(
// Admin handlers // Admin handlers
admin.NewDashboardHandler, admin.NewDashboardHandler,
admin.NewOpsHandler,
admin.NewUserHandler, admin.NewUserHandler,
admin.NewGroupHandler, admin.NewGroupHandler,
admin.NewAccountHandler, admin.NewAccountHandler,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment