185 lines
7.0 KiB
Go
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
|
|
}
|