Files
Luminary/internal/store/sqlite/repository.go
T
renjue 76ba500417
CI / docker (push) Successful in 2m4s
Initial commit: Luminary AI Gateway
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>
2026-06-23 21:46:16 +08:00

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{})
}