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>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rose_cat707/luminary/internal/auth"
|
||||
"github.com/rose_cat707/luminary/internal/model"
|
||||
"github.com/rose_cat707/luminary/internal/store/cache"
|
||||
)
|
||||
|
||||
func (h *Handler) ListIngressKeys(c *gin.Context) {
|
||||
keys := h.cache.ListIngressKeys()
|
||||
c.JSON(http.StatusOK, gin.H{"data": keys})
|
||||
}
|
||||
|
||||
func (h *Handler) CreateIngressKey(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
BudgetLimit float64 `json:"budget_limit"`
|
||||
RateLimitRPM int `json:"rate_limit_rpm"`
|
||||
RateLimitTPM int `json:"rate_limit_tpm"`
|
||||
AllowedProviders []string `json:"allowed_providers"`
|
||||
AllowedModels []string `json:"allowed_models"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
plainKey, err := generateIngressKey()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "key generation failed"})
|
||||
return
|
||||
}
|
||||
hash := auth.HashIngressKey(plainKey)
|
||||
providersJSON := `["*"]`
|
||||
modelsJSON := `["*"]`
|
||||
if req.AllowedProviders != nil {
|
||||
if len(req.AllowedProviders) == 0 {
|
||||
providersJSON = `["*"]`
|
||||
} else {
|
||||
providersJSON = cache.ModelsToJSON(req.AllowedProviders)
|
||||
}
|
||||
}
|
||||
if req.AllowedModels != nil {
|
||||
if len(req.AllowedModels) == 0 {
|
||||
modelsJSON = `["*"]`
|
||||
} else {
|
||||
modelsJSON = cache.ModelsToJSON(req.AllowedModels)
|
||||
}
|
||||
}
|
||||
k := model.IngressKey{
|
||||
Name: req.Name,
|
||||
KeyHash: hash,
|
||||
Prefix: plainKey[:12] + "...",
|
||||
Enabled: true,
|
||||
BudgetLimit: req.BudgetLimit,
|
||||
RateLimitRPM: req.RateLimitRPM,
|
||||
RateLimitTPM: req.RateLimitTPM,
|
||||
AllowedProvidersJSON: providersJSON,
|
||||
AllowedModelsJSON: modelsJSON,
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
}
|
||||
if err := h.db.DB().Create(&k).Error; err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.cache.SetIngressKey(k)
|
||||
c.JSON(http.StatusCreated, gin.H{"key": k, "plain_key": plainKey})
|
||||
}
|
||||
|
||||
func (h *Handler) GetIngressKey(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
k, ok := h.cache.GetIngressKey(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, k)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateIngressKey(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
k, ok := h.cache.GetIngressKey(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
BudgetLimit *float64 `json:"budget_limit"`
|
||||
RateLimitRPM *int `json:"rate_limit_rpm"`
|
||||
RateLimitTPM *int `json:"rate_limit_tpm"`
|
||||
AllowedProviders []string `json:"allowed_providers"`
|
||||
AllowedModels []string `json:"allowed_models"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if req.Name != "" {
|
||||
k.Name = req.Name
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
k.Enabled = *req.Enabled
|
||||
}
|
||||
if req.BudgetLimit != nil {
|
||||
k.BudgetLimit = *req.BudgetLimit
|
||||
}
|
||||
if req.RateLimitRPM != nil {
|
||||
k.RateLimitRPM = *req.RateLimitRPM
|
||||
}
|
||||
if req.RateLimitTPM != nil {
|
||||
k.RateLimitTPM = *req.RateLimitTPM
|
||||
}
|
||||
if req.AllowedProviders != nil {
|
||||
if len(req.AllowedProviders) == 0 {
|
||||
k.AllowedProvidersJSON = `["*"]`
|
||||
} else {
|
||||
k.AllowedProvidersJSON = cache.ModelsToJSON(req.AllowedProviders)
|
||||
}
|
||||
}
|
||||
if req.AllowedModels != nil {
|
||||
if len(req.AllowedModels) == 0 {
|
||||
k.AllowedModelsJSON = `["*"]`
|
||||
} else {
|
||||
k.AllowedModelsJSON = cache.ModelsToJSON(req.AllowedModels)
|
||||
}
|
||||
}
|
||||
h.db.DB().Save(&k)
|
||||
h.cache.SetIngressKey(k)
|
||||
c.JSON(http.StatusOK, k)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteIngressKey(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
h.db.DB().Where("ingress_key_id = ?", id).Delete(&model.RoutingRule{})
|
||||
h.db.DB().Delete(&model.IngressKey{}, id)
|
||||
h.cache.DeleteIngressKey(id)
|
||||
h.cache.DeleteRoutingRulesForIngress(id)
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Handler) IngressUsage(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
var records []model.UsageRecord
|
||||
h.db.DB().Where("ingress_key_id = ?", id).Order("created_at desc").Limit(50).Find(&records)
|
||||
k, _ := h.cache.GetIngressKey(id)
|
||||
c.JSON(http.StatusOK, gin.H{"key": k, "recent": records})
|
||||
}
|
||||
|
||||
func generateIngressKey() (string, error) {
|
||||
b := make([]byte, 24)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "sk-lum-" + hex.EncodeToString(b), nil
|
||||
}
|
||||
Reference in New Issue
Block a user