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

65 lines
2.5 KiB
Go

package tables
import (
"fmt"
"time"
"gorm.io/gorm"
)
// TableBudget defines spending limits with configurable reset periods
type TableBudget struct {
ID string `gorm:"primaryKey;type:varchar(255)" json:"id"`
MaxLimit float64 `gorm:"not null" json:"max_limit"` // Maximum budget in dollars
ResetDuration string `gorm:"type:varchar(50);not null" json:"reset_duration"` // e.g., "30s", "5m", "1h", "1d", "1w", "1M", "1Y"
LastReset time.Time `gorm:"index" json:"last_reset"` // Last time budget was reset
CurrentUsage float64 `gorm:"default:0" json:"current_usage"` // Current usage in dollars
// Owner FKs: a budget belongs to at most one Team, one VK, or one ProviderConfig
TeamID *string `gorm:"type:varchar(255);index" json:"team_id,omitempty"`
VirtualKeyID *string `gorm:"type:varchar(255);index" json:"virtual_key_id,omitempty"`
ProviderConfigID *uint `gorm:"index" json:"provider_config_id,omitempty"`
CalendarAligned bool `gorm:"default:false" json:"calendar_aligned"` // When true, all budgets under this VK reset at clean calendar boundaries
// 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"`
CreatedAt time.Time `gorm:"index;not null" json:"created_at"`
UpdatedAt time.Time `gorm:"index;not null" json:"updated_at"`
}
// TableName sets the table name for each model
func (TableBudget) TableName() string { return "governance_budgets" }
// BeforeSave hook for Budget to validate reset duration format and max limit
func (b *TableBudget) BeforeSave(tx *gorm.DB) error {
// A budget belongs to at most one owner type
owners := 0
if b.TeamID != nil {
owners++
}
if b.VirtualKeyID != nil {
owners++
}
if b.ProviderConfigID != nil {
owners++
}
if owners > 1 {
return fmt.Errorf("budget cannot have more than one owner (team/virtual key/provider config)")
}
// Validate that ResetDuration is in correct format (e.g., "30s", "5m", "1h", "1d", "1w", "1M", "1Y")
if d, err := ParseDuration(b.ResetDuration); err != nil {
return fmt.Errorf("invalid reset duration format: %s", b.ResetDuration)
} else if d <= 0 {
return fmt.Errorf("reset duration must be > 0: %s", b.ResetDuration)
}
// Validate that MaxLimit is not negative (budgets should be positive)
if b.MaxLimit < 0 {
return fmt.Errorf("budget max_limit cannot be negative: %.2f", b.MaxLimit)
}
return nil
}