Files
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

185 lines
7.0 KiB
Go

package tables
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/encrypt"
"gorm.io/gorm"
)
// TableProvider represents a provider configuration in the database
// NOTE: Any changes to the provider configuration should be reflected in the GenerateConfigHash function
// That helps us detect changes between config file and database config
type TableProvider struct {
ID uint `gorm:"primaryKey;autoIncrement" json:"id"`
Name string `gorm:"type:varchar(50);uniqueIndex;not null" json:"name"` // ModelProvider as string
NetworkConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.NetworkConfig
ConcurrencyBufferJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.ConcurrencyAndBufferSize
ProxyConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.ProxyConfig
CustomProviderConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.CustomProviderConfig
OpenAIConfigJSON string `gorm:"type:text" json:"-"` // JSON serialized schemas.OpenAIConfig
SendBackRawRequest bool `json:"send_back_raw_request"`
SendBackRawResponse bool `json:"send_back_raw_response"`
StoreRawRequestResponse bool `json:"store_raw_request_response"`
CreatedAt time.Time `gorm:"index;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"index;not null" json:"updated_at"`
// Relationships
Keys []TableKey `gorm:"foreignKey:ProviderID;constraint:OnDelete:CASCADE" json:"keys"`
// Virtual fields for runtime use (not stored in DB)
NetworkConfig *schemas.NetworkConfig `gorm:"-" json:"network_config,omitempty"`
ConcurrencyAndBufferSize *schemas.ConcurrencyAndBufferSize `gorm:"-" json:"concurrency_and_buffer_size,omitempty"`
ProxyConfig *schemas.ProxyConfig `gorm:"-" json:"proxy_config,omitempty"`
// Custom provider fields
CustomProviderConfig *schemas.CustomProviderConfig `gorm:"-" json:"custom_provider_config,omitempty"`
OpenAIConfig *schemas.OpenAIConfig `gorm:"-" json:"openai_config,omitempty"`
// Foreign keys
Models []TableModel `gorm:"foreignKey:ProviderID;constraint:OnDelete:CASCADE" json:"models"`
// Governance fields - Budget and Rate Limit for provider-level governance
BudgetID *string `gorm:"type:varchar(255);index:idx_provider_budget" json:"budget_id,omitempty"`
RateLimitID *string `gorm:"type:varchar(255);index:idx_provider_rate_limit" json:"rate_limit_id,omitempty"`
// Governance relationships
Budget *TableBudget `gorm:"foreignKey:BudgetID;onDelete:CASCADE" json:"budget,omitempty"`
RateLimit *TableRateLimit `gorm:"foreignKey:RateLimitID;onDelete:CASCADE" json:"rate_limit,omitempty"`
// Config hash is used to detect the changes synced from config.json file
// Every time we sync the config.json file, we will update the config hash
ConfigHash string `gorm:"type:varchar(255);null" json:"config_hash"`
// Model discovery status tracking for keyless providers
Status string `gorm:"type:varchar(50);default:'unknown'" json:"status"`
Description string `gorm:"type:text" json:"description,omitempty"`
EncryptionStatus string `gorm:"type:varchar(20);default:'plain_text'" json:"-"`
}
// TableName represents a provider configuration in the database
func (TableProvider) TableName() string { return "config_providers" }
// BeforeSave is a GORM hook that serializes runtime config structs into JSON columns,
// validates governance fields, and encrypts the proxy configuration before writing
// to the database.
func (p *TableProvider) BeforeSave(tx *gorm.DB) error {
if p.NetworkConfig != nil {
data, err := json.Marshal(p.NetworkConfig)
if err != nil {
return err
}
p.NetworkConfigJSON = string(data)
}
if p.ConcurrencyAndBufferSize != nil {
data, err := json.Marshal(p.ConcurrencyAndBufferSize)
if err != nil {
return err
}
p.ConcurrencyBufferJSON = string(data)
}
if p.ProxyConfig != nil {
data, err := json.Marshal(p.ProxyConfig)
if err != nil {
return err
}
p.ProxyConfigJSON = string(data)
}
if p.CustomProviderConfig != nil && p.CustomProviderConfig.BaseProviderType == "" {
return fmt.Errorf("base_provider_type is required when custom_provider_config is set")
}
if p.CustomProviderConfig != nil {
data, err := json.Marshal(p.CustomProviderConfig)
if err != nil {
return err
}
p.CustomProviderConfigJSON = string(data)
}
if p.OpenAIConfig != nil {
data, err := json.Marshal(p.OpenAIConfig)
if err != nil {
return err
}
p.OpenAIConfigJSON = string(data)
} else {
p.OpenAIConfigJSON = ""
}
// Validate governance fields
if p.BudgetID != nil && strings.TrimSpace(*p.BudgetID) == "" {
return fmt.Errorf("budget_id cannot be an empty string")
}
if p.RateLimitID != nil && strings.TrimSpace(*p.RateLimitID) == "" {
return fmt.Errorf("rate_limit_id cannot be an empty string")
}
// Encrypt proxy config after serialization (only if there's data to encrypt)
if encrypt.IsEnabled() && p.ProxyConfigJSON != "" {
encrypted, err := encrypt.Encrypt(p.ProxyConfigJSON)
if err != nil {
return fmt.Errorf("failed to encrypt proxy config: %w", err)
}
p.ProxyConfigJSON = encrypted
p.EncryptionStatus = EncryptionStatusEncrypted
}
return nil
}
// AfterFind is a GORM hook that decrypts the proxy configuration (if encrypted) and
// deserializes JSON columns back into runtime config structs after reading from the database.
func (p *TableProvider) AfterFind(tx *gorm.DB) error {
if p.NetworkConfigJSON != "" {
var config schemas.NetworkConfig
if err := json.Unmarshal([]byte(p.NetworkConfigJSON), &config); err != nil {
return err
}
p.NetworkConfig = &config
}
if p.ConcurrencyBufferJSON != "" {
var config schemas.ConcurrencyAndBufferSize
if err := json.Unmarshal([]byte(p.ConcurrencyBufferJSON), &config); err != nil {
return err
}
p.ConcurrencyAndBufferSize = &config
}
if p.EncryptionStatus == "encrypted" && p.ProxyConfigJSON != "" {
decrypted, err := encrypt.Decrypt(p.ProxyConfigJSON)
if err != nil {
return fmt.Errorf("failed to decrypt proxy config: %w", err)
}
p.ProxyConfigJSON = decrypted
}
if p.ProxyConfigJSON != "" {
var proxyConfig schemas.ProxyConfig
if err := json.Unmarshal([]byte(p.ProxyConfigJSON), &proxyConfig); err != nil {
return err
}
p.ProxyConfig = &proxyConfig
}
if p.CustomProviderConfigJSON != "" {
var customConfig schemas.CustomProviderConfig
if err := json.Unmarshal([]byte(p.CustomProviderConfigJSON), &customConfig); err != nil {
return err
}
p.CustomProviderConfig = &customConfig
}
if p.OpenAIConfigJSON != "" {
var openaiConfig schemas.OpenAIConfig
if err := json.Unmarshal([]byte(p.OpenAIConfigJSON), &openaiConfig); err != nil {
return err
}
p.OpenAIConfig = &openaiConfig
}
return nil
}