package routers import ( "os" "path/filepath" "strings" "lokideploy/controllers" "lokideploy/middleware" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) func SetupRouter() *gin.Engine { r := gin.Default() // CORS cfg := cors.DefaultConfig() cfg.AllowAllOrigins = true cfg.AllowHeaders = []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"} cfg.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"} r.Use(cors.New(cfg)) // Controllers authCtrl := controllers.NewAuthController() clusterCtrl := controllers.NewClusterController() hostCtrl := controllers.NewHostController() storageCtrl := controllers.NewStorageController() deploymentCtrl := controllers.NewDeploymentController() // API v1 api := r.Group("/api/v1") // Public api.POST("/auth/login", authCtrl.Login) // Auth required authorized := api.Group("") authorized.Use(middleware.JWTAuth()) // Auth routes authorized.GET("/auth/user", authCtrl.GetUser) authorized.POST("/auth/password", authCtrl.ChangePassword) authorized.POST("/auth/register", middleware.AdminAuth(), authCtrl.Register) // Clusters authorized.GET("/clusters", clusterCtrl.List) authorized.GET("/clusters/:id", clusterCtrl.Get) authorized.POST("/clusters", clusterCtrl.Create) authorized.PUT("/clusters/:id", clusterCtrl.Update) authorized.DELETE("/clusters/:id", clusterCtrl.Delete) authorized.POST("/clusters/:id/validate", clusterCtrl.Validate) authorized.GET("/clusters/:id/info", clusterCtrl.GetInfo) // Hosts authorized.GET("/hosts", hostCtrl.List) authorized.GET("/hosts/:id", hostCtrl.Get) authorized.POST("/hosts", hostCtrl.Create) authorized.PUT("/hosts/:id", hostCtrl.Update) authorized.DELETE("/hosts/:id", hostCtrl.Delete) authorized.POST("/hosts/:id/validate", hostCtrl.Validate) authorized.GET("/hosts/:id/info", hostCtrl.GetInfo) authorized.POST("/hosts/:id/execute", hostCtrl.Execute) // Storage authorized.GET("/storages", storageCtrl.List) authorized.GET("/storages/:id", storageCtrl.Get) authorized.POST("/storages", storageCtrl.Create) authorized.PUT("/storages/:id", storageCtrl.Update) authorized.DELETE("/storages/:id", storageCtrl.Delete) authorized.GET("/storages/default", storageCtrl.GetDefault) // Deployments authorized.GET("/deployments", deploymentCtrl.List) authorized.GET("/deployments/:id", deploymentCtrl.Get) authorized.POST("/deployments", deploymentCtrl.Create) authorized.PUT("/deployments/:id", deploymentCtrl.Update) authorized.DELETE("/deployments/:id", deploymentCtrl.Delete) authorized.POST("/deployments/:id/deploy", deploymentCtrl.Deploy) authorized.POST("/deployments/:id/stop", deploymentCtrl.Stop) authorized.POST("/deployments/:id/restart", deploymentCtrl.Restart) authorized.GET("/deployments/:id/logs", deploymentCtrl.GetLogs) authorized.GET("/deployments/stats", deploymentCtrl.GetStats) // Static files - use executable directory as base distPath := findDistPath() if distPath != "" { r.Static("/assets", filepath.Join(distPath, "assets")) r.NoRoute(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/api/") { c.JSON(404, gin.H{"code": 404, "message": "API endpoint not found"}) return } c.File(filepath.Join(distPath, "index.html")) }) } return r } // findDistPath finds the frontend dist directory func findDistPath() string { // Try relative to executable if ex, err := os.Executable(); err == nil { execDir := filepath.Dir(ex) // If running via "go run", executable is in temp dir - fall through if !strings.Contains(execDir, "/tmp/go-build") { for _, rel := range []string{"dist", "../dist", "../../dist"} { p := filepath.Join(execDir, rel) if _, err := os.Stat(filepath.Join(p, "index.html")); err == nil { return p } } } } // Try working directory relative paths for _, p := range []string{"./dist", "../dist", "/app/dist"} { if abs, err := filepath.Abs(p); err == nil { if _, err := os.Stat(filepath.Join(abs, "index.html")); err == nil { return abs } } } return "" }