Files
llama-link/internal/api/router.go
T
darroyo b18d4bd146
CI / test (push) Failing after 12m25s
fix: use mime.TypeByExtension for static asset Content-Type
http.DetectContentType() cannot detect JS, CSS, SVG, etc. from magic
bytes — it falls back to text/plain, causing browsers to reject module
scripts with 'Expected a JavaScript module' error.

Use mime.TypeByExtension(ext) which correctly maps .js ->
application/javascript, .css -> text/css, etc.
2026-07-31 15:37:16 -04:00

115 lines
3.4 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/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)
// 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)
// 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)
// Keys management (admin only)
keys := v1.Group("/keys")
keys.Use(middleware.APIKeyAuth(authService))
keys.Use(middleware.AdminOnly())
keys.GET("", keysHandler.ListKeys)
keys.POST("", keysHandler.CreateKey)
keys.DELETE("/:id", keysHandler.RevokeKey)
// 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
// Model load (admin only)
modelLoad := v1.Group("/models")
modelLoad.Use(middleware.APIKeyAuth(authService))
modelLoad.Use(middleware.AdminOnly())
modelLoad.POST("/:name/load", modelsHandler.LoadModel)
// 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)
})
return r
}