Files
Luminary/internal/handler/admin/ingress.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

164 lines
4.6 KiB
Go

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
}