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) // Keys management (admin session required) keys := v1.Group("/keys") keys.Use(middleware.AdminSessionAuth(adminJWTService)) 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 session required) modelLoad := v1.Group("/models") modelLoad.Use(middleware.AdminSessionAuth(adminJWTService)) 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) }) // Admin API v1 (JWT session auth) adminV1 := v1.Group("/admin") adminV1.POST("/login", adminLoginHandler.Login) adminV1.POST("/logout", adminLoginHandler.Logout) 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) return r }