package store import ( "time" "github.com/shopspring/decimal" "gorm.io/gorm" ) // User is a registered account. type User struct { ID int64 `gorm:"primaryKey"` Username string `gorm:"uniqueIndex;size:64"` Email string `gorm:"uniqueIndex;size:255"` PasswordHash string `gorm:"size:255"` Role string `gorm:"size:16;default:user"` // admin | user Balance decimal.Decimal `gorm:"type:numeric(20,8);default:0"` Status string `gorm:"size:16;default:active"` // active | disabled InviteCode string `gorm:"size:64"` LastLoginAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // ApiKey is an API key issued to a user. type ApiKey struct { ID int64 `gorm:"primaryKey"` UserID int64 `gorm:"index"` Name string `gorm:"size:128"` KeyHash string `gorm:"uniqueIndex;size:128"` KeyPrefix string `gorm:"size:16"` QuotaTokensPerDay *int64 QuotaRequestsPerDay *int AllowedModels []string `gorm:"serializer:json"` ExpiresAt *time.Time Status string `gorm:"size:16;default:active"` // active | revoked LastUsedAt *time.Time CreatedAt time.Time UpdatedAt time.Time } // Channel is an upstream provider. type Channel struct { ID int64 `gorm:"primaryKey"` Name string `gorm:"size:128"` Provider string `gorm:"size:32"` // openai | anthropic | compatible BaseURL string `gorm:"size:512"` APIKeyEnc string `gorm:"size:2048"` // AES-GCM ciphertext Weight int `gorm:"default:1"` Priority int `gorm:"default:0"` // lower = preferred TimeoutMs int `gorm:"default:300000"` MaxConcurrency int `gorm:"default:100"` HealthStatus string `gorm:"size:16;default:healthy"` // healthy | degraded | cooldown HealthFailures int Enabled bool `gorm:"default:true"` Formats []string `gorm:"serializer:json"` // client API formats served natively; empty = derive from Provider CreatedAt time.Time UpdatedAt time.Time } // Client API format keys a channel can serve natively. They match the proxy // route protocol values so SupportsFormat can compare them directly. const ( FormatOpenAIChat = "openai-chat" FormatOpenAIResponses = "openai-responses" FormatAnthropic = "anthropic" ) // FormatsResolved returns the API formats the channel serves directly. When // Formats is empty it falls back to the provider's native formats so legacy // rows behave exactly as before. func (c *Channel) FormatsResolved() []string { if len(c.Formats) > 0 { out := make([]string, len(c.Formats)) copy(out, c.Formats) return out } if c.Provider == "anthropic" { return []string{FormatAnthropic} } return []string{FormatOpenAIChat, FormatOpenAIResponses} } // SupportsFormat reports whether the channel serves a client protocol // natively (passthrough) without conversion. func (c *Channel) SupportsFormat(f string) bool { for _, x := range c.FormatsResolved() { if x == f { return true } } return false } // Model is the global model registry with pricing. type Model struct { ID int64 `gorm:"primaryKey"` Name string `gorm:"uniqueIndex;size:128"` DisplayName string `gorm:"size:255"` InputPrice decimal.Decimal `gorm:"type:numeric(20,8);default:0"` // per 1M tokens OutputPrice decimal.Decimal `gorm:"type:numeric(20,8);default:0"` CacheReadPrice decimal.Decimal `gorm:"type:numeric(20,8);default:0"` Enabled bool `gorm:"default:true"` Sort int `gorm:"default:0"` CreatedAt time.Time UpdatedAt time.Time } // ChannelModelBinding binds a global model to a channel with an upstream name. type ChannelModelBinding struct { ID int64 `gorm:"primaryKey"` ChannelID int64 `gorm:"index;uniqueIndex:idx_channel_model"` ModelID int64 `gorm:"index;uniqueIndex:idx_channel_model"` UpstreamModel string `gorm:"size:255"` Weight int `gorm:"default:1"` Channel Channel `gorm:"foreignKey:ChannelID"` Model Model `gorm:"foreignKey:ModelID"` } // UsageLog is one proxied request's billing record. type UsageLog struct { ID int64 `gorm:"primaryKey"` RequestID string `gorm:"size:128"` UserID int64 `gorm:"index:idx_user_created,priority:1"` KeyID int64 ChannelID int64 ModelID int64 ModelName string `gorm:"size:128"` InputTokens int64 OutputTokens int64 CacheReadTokens int64 CacheCreationTokens int64 InputPrice decimal.Decimal `gorm:"type:numeric(20,8)"` OutputPrice decimal.Decimal `gorm:"type:numeric(20,8)"` CacheReadPrice decimal.Decimal `gorm:"type:numeric(20,8)"` Cost decimal.Decimal `gorm:"type:numeric(20,8)"` LatencyMs int Status string `gorm:"size:16"` // success | error | canceled ErrorCode string `gorm:"size:64"` CreatedAt time.Time `gorm:"index:idx_user_created,priority:2"` } // UsageDaily is the pre-aggregated per-user per-model daily rollup. type UsageDaily struct { ID int64 `gorm:"primaryKey"` UserID int64 `gorm:"index:idx_user_date,priority:1"` ModelID int64 Date string `gorm:"size:10;index:idx_user_date,priority:2"` // YYYY-MM-DD Requests int InputTokens int64 OutputTokens int64 CacheReadTokens int64 CacheCreationTokens int64 Cost decimal.Decimal `gorm:"type:numeric(20,8)"` } // RechargeOrder is reserved for the (paused) recharge feature. type RechargeOrder struct { ID int64 `gorm:"primaryKey"` UserID int64 `gorm:"index"` Amount decimal.Decimal `gorm:"type:numeric(20,8)"` Status string `gorm:"size:16;default:pending"` // pending | credited | rejected Method string `gorm:"size:16;default:manual"` // manual | online TransactionID string `gorm:"size:128"` ReviewedBy *int64 ReviewedAt *time.Time Remark string `gorm:"size:512"` CreatedAt time.Time UpdatedAt time.Time User User `gorm:"foreignKey:UserID"` } // BalanceLog is a user balance ledger entry. type BalanceLog struct { ID int64 `gorm:"primaryKey"` UserID int64 `gorm:"index:idx_balance_user_created,priority:1"` Change decimal.Decimal `gorm:"type:numeric(20,8)"` BalanceAfter decimal.Decimal `gorm:"type:numeric(20,8)"` Type string `gorm:"size:32"` // recharge | usage | refund | admin_adjust RefID string `gorm:"size:128"` CreatedAt time.Time `gorm:"index:idx_balance_user_created,priority:2"` } // SystemConfig is a key/value store for runtime settings. type SystemConfig struct { Key string `gorm:"primaryKey;size:128"` Value []byte `gorm:"serializer:json"` } // Migrate creates/upgrades the schema. func Migrate(db *gorm.DB) error { return db.AutoMigrate( &User{}, &ApiKey{}, &Channel{}, &Model{}, &ChannelModelBinding{}, &UsageLog{}, &UsageDaily{}, &RechargeOrder{}, &BalanceLog{}, &SystemConfig{}, ) }