de5f7ca4ee
CI / test (push) Failing after 12m28s
- Remove dead AdminOnly() and AdminTokenAuth() middleware (unused since JWT switch) - Remove dead RequireScope() middleware (also unused) - Fix seed.go: only log password when auto-generated (was logging every time) - Fix seed.go: use admin.Username (normalized) instead of raw username in log - Simplify generateRandomPassword: 12 bytes (24 hex chars) instead of 24 bytes then truncate - Me handler: use claims.Subject (UUID) for lookup instead of username - Remove unused /logout endpoint (stateless JWT)
122 lines
4.0 KiB
Go
122 lines
4.0 KiB
Go
package api
|
|
|
|
import (
|
|
"mime"
|
|
"net/http"
|
|
"path"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/llamalink/llamalink/internal/api/handlers"
|
|
"github.com/llamalink/llamalink/internal/api/handlers/admin"
|
|
"github.com/llamalink/llamalink/internal/api/middleware"
|
|
"github.com/llamalink/llamalink/internal/auth"
|
|
"github.com/llamalink/llamalink/internal/config"
|
|
"github.com/llamalink/llamalink/internal/llama"
|
|
"github.com/llamalink/llamalink/internal/quota"
|
|
"github.com/llamalink/llamalink/internal/web"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func New(cfg *config.Config, db *gorm.DB, llamaManager *llama.Manager) *gin.Engine {
|
|
if cfg.LlamalinkEnv == "production" {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
|
|
r := gin.New()
|
|
r.Use(gin.Recovery())
|
|
r.Use(gin.Logger())
|
|
|
|
// Initialize services
|
|
authService := auth.NewService(db)
|
|
quotaSvc := quota.NewService(db)
|
|
webhookSvc := quota.NewWebhookService(db)
|
|
proxy := llama.NewProxy(llamaManager)
|
|
adminJWTService := auth.NewAdminJWTService(cfg.JWTSecret, cfg.AdminSessionTTL)
|
|
|
|
// Initialize handlers
|
|
healthHandler := handlers.NewHealthHandler(db, llamaManager)
|
|
chatHandler := handlers.NewChatHandler(proxy, authService, quotaSvc, webhookSvc)
|
|
keysHandler := handlers.NewKeysHandler(authService)
|
|
modelsHandler := handlers.NewModelsHandler(llamaManager)
|
|
usageHandler := handlers.NewUsageHandler(db, quotaSvc)
|
|
adminLoginHandler := admin.NewLoginHandler(db, adminJWTService)
|
|
adminMeHandler := admin.NewMeHandler(db)
|
|
adminUsersHandler := admin.NewUsersHandler(db)
|
|
|
|
// Health endpoints (public)
|
|
r.GET("/health", healthHandler.Health)
|
|
r.GET("/ready", healthHandler.Ready)
|
|
|
|
// API v1 group
|
|
v1 := r.Group("/v1")
|
|
|
|
// Chat completions (requires API key auth + chat scope)
|
|
chat := v1.Group("/chat")
|
|
chat.Use(middleware.APIKeyAuth(authService))
|
|
chat.POST("/completions", chatHandler.ChatCompletions)
|
|
|
|
// Models management
|
|
models := v1.Group("/models")
|
|
models.Use(middleware.APIKeyAuth(authService))
|
|
models.GET("", modelsHandler.ListModels)
|
|
models.POST("", modelsHandler.CreateModel)
|
|
models.GET("/active", modelsHandler.GetActiveModel)
|
|
models.GET("/:name", modelsHandler.GetActiveModel) // alias for compatibility
|
|
|
|
// Usage (requires API key auth)
|
|
usage := v1.Group("/usage")
|
|
usage.Use(middleware.APIKeyAuth(authService))
|
|
usage.GET("", usageHandler.GetCurrentKeyUsage)
|
|
usage.GET("/:key_id", usageHandler.GetUsage)
|
|
|
|
// Admin SPA (static files with embedded frontend)
|
|
r.GET("/assets/*filepath", func(c *gin.Context) {
|
|
filepath := c.Param("filepath")
|
|
data, err := web.ServeAsset(filepath)
|
|
if err != nil {
|
|
c.String(http.StatusNotFound, "asset not found")
|
|
return
|
|
}
|
|
ext := path.Ext(filepath)
|
|
contentType := mime.TypeByExtension(ext)
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
c.Data(http.StatusOK, contentType, data)
|
|
})
|
|
r.GET("/admin", func(c *gin.Context) {
|
|
index, err := web.Index()
|
|
if err != nil {
|
|
c.String(http.StatusInternalServerError, "index.html not found")
|
|
return
|
|
}
|
|
c.Data(http.StatusOK, "text/html; charset=utf-8", index)
|
|
})
|
|
r.GET("/admin/*filepath", func(c *gin.Context) {
|
|
index, err := web.Index()
|
|
if err != nil {
|
|
c.String(http.StatusInternalServerError, "index.html not found")
|
|
return
|
|
}
|
|
c.Data(http.StatusOK, "text/html; charset=utf-8", index)
|
|
})
|
|
|
|
// Admin API v1 (JWT session auth)
|
|
adminV1 := v1.Group("/admin")
|
|
adminV1.POST("/login", adminLoginHandler.Login)
|
|
adminSession := adminV1.Group("")
|
|
adminSession.Use(middleware.AdminSessionAuth(adminJWTService))
|
|
adminSession.GET("/me", adminMeHandler.Me)
|
|
adminSession.POST("/change-password", adminMeHandler.ChangePassword)
|
|
adminSession.GET("/users", adminUsersHandler.List)
|
|
adminSession.POST("/users", adminUsersHandler.Create)
|
|
adminSession.PUT("/users/:id", adminUsersHandler.Update)
|
|
adminSession.DELETE("/users/:id", adminUsersHandler.Delete)
|
|
adminSession.GET("/keys", keysHandler.ListKeys)
|
|
adminSession.POST("/keys", keysHandler.CreateKey)
|
|
adminSession.DELETE("/keys/:id", keysHandler.RevokeKey)
|
|
adminSession.POST("/models/:name/load", modelsHandler.LoadModel)
|
|
|
|
return r
|
|
}
|