76ba500417
CI / docker (push) Successful in 2m4s
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
3.4 KiB
Go
137 lines
3.4 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/rose_cat707/luminary/internal/auth"
|
|
"github.com/rose_cat707/luminary/internal/model"
|
|
"github.com/rose_cat707/luminary/internal/settings"
|
|
"github.com/rose_cat707/luminary/internal/store/cache"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
cache *cache.Store
|
|
}
|
|
|
|
func NewRepository(store *Store, c *cache.Store) *Repository {
|
|
return &Repository{db: store.DB(), cache: c}
|
|
}
|
|
|
|
func (r *Repository) BootstrapFromDB() error {
|
|
var providers []model.Provider
|
|
if err := r.db.Find(&providers).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadProviders(providers)
|
|
|
|
var keys []model.ProviderKey
|
|
if err := r.db.Find(&keys).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadProviderKeys(keys)
|
|
|
|
var models []model.ModelEntry
|
|
if err := r.db.Find(&models).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadModels(models)
|
|
|
|
var ingress []model.IngressKey
|
|
if err := r.db.Find(&ingress).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadIngressKeys(ingress)
|
|
|
|
var rules []model.IPRule
|
|
if err := r.db.Find(&rules).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadIPRules(rules)
|
|
|
|
var routing []model.RoutingRule
|
|
if err := r.db.Find(&routing).Error; err != nil {
|
|
return err
|
|
}
|
|
r.cache.LoadRoutingRules(routing)
|
|
r.PruneUsageRecords(model.MaxUsageLogRecords)
|
|
return nil
|
|
}
|
|
|
|
func (r *Repository) EnsureAdmin() error {
|
|
var count int64
|
|
r.db.Model(&model.AdminUser{}).Count(&count)
|
|
if count > 0 {
|
|
return nil
|
|
}
|
|
username, password := settings.LegacyAdminCredentials()
|
|
hash, err := auth.HashPassword(password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return r.db.Create(&model.AdminUser{Username: username, PasswordHash: hash}).Error
|
|
}
|
|
|
|
func (r *Repository) FlushUsage(deltas []cache.UsageDelta) {
|
|
for _, d := range deltas {
|
|
r.db.Create(&model.UsageRecord{
|
|
IngressKeyID: d.IngressKeyID,
|
|
ProviderID: d.ProviderID,
|
|
ProviderKeyID: d.ProviderKeyID,
|
|
ClientIP: d.ClientIP,
|
|
Endpoint: d.Endpoint,
|
|
Model: d.Model,
|
|
TokensIn: d.TokensIn,
|
|
TokensOut: d.TokensOut,
|
|
Cost: d.Cost,
|
|
LatencyMs: d.LatencyMs,
|
|
Status: d.Status,
|
|
Stream: d.Stream,
|
|
ErrorMsg: d.ErrorMsg,
|
|
RequestBody: d.RequestBody,
|
|
ResponseBody: d.ResponseBody,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
if d.IngressKeyID > 0 {
|
|
r.db.Model(&model.IngressKey{}).Where("id = ?", d.IngressKeyID).Updates(map[string]any{
|
|
"budget_used": gorm.Expr("budget_used + ?", d.BudgetUsed),
|
|
"request_count": gorm.Expr("request_count + ?", d.Requests),
|
|
"token_count": gorm.Expr("token_count + ?", d.Tokens),
|
|
})
|
|
}
|
|
if d.ProviderKeyID > 0 {
|
|
r.db.Model(&model.ProviderKey{}).Where("id = ?", d.ProviderKeyID).Updates(map[string]any{
|
|
"requests_today": gorm.Expr("requests_today + ?", d.Requests),
|
|
"tokens_today": gorm.Expr("tokens_today + ?", d.Tokens),
|
|
})
|
|
}
|
|
}
|
|
r.PruneUsageRecords(model.MaxUsageLogRecords)
|
|
}
|
|
|
|
func (r *Repository) RecordRequestLog(rec model.UsageRecord) {
|
|
if rec.CreatedAt.IsZero() {
|
|
rec.CreatedAt = time.Now()
|
|
}
|
|
r.db.Create(&rec)
|
|
r.PruneUsageRecords(model.MaxUsageLogRecords)
|
|
}
|
|
|
|
func (r *Repository) PruneUsageRecords(max int) {
|
|
if max <= 0 {
|
|
return
|
|
}
|
|
var count int64
|
|
r.db.Model(&model.UsageRecord{}).Count(&count)
|
|
if count <= int64(max) {
|
|
return
|
|
}
|
|
var keepIDs []uint
|
|
r.db.Model(&model.UsageRecord{}).Order("created_at desc").Limit(max).Pluck("id", &keepIDs)
|
|
if len(keepIDs) == 0 {
|
|
return
|
|
}
|
|
r.db.Where("id NOT IN ?", keepIDs).Delete(&model.UsageRecord{})
|
|
}
|