package main import ( "fmt" "os" "path/filepath" "strings" "time" ) // HandlerData contains template data for handler generation type HandlerData struct { Name string NameLower string NamePlural string ModuleName string HasGet bool HasPost bool HasPut bool HasDelete bool HasParam bool HasRequest bool HasResponse bool Timestamp string } func main() { if len(os.Args) < 2 { fmt.Println("Usage: go run generate-handler.go entity [methods]") os.Exit(1) } handlerName := strings.Title(os.Args[1]) // PascalCase entity name methods := []string{} if len(os.Args) > 2 { methods = os.Args[2:] } handlerLower := strings.ToLower(handlerName) handlerPlural := handlerLower + "s" data := HandlerData{ Name: handlerName, NameLower: handlerLower, NamePlural: handlerPlural, ModuleName: "api-service", Timestamp: time.Now().Format("2006-01-02 15:04:05"), HasResponse: true, } for _, m := range methods { switch strings.ToLower(m) { case "get": data.HasGet = true case "post": data.HasPost = true case "put": data.HasPut = true case "delete": data.HasDelete = true } } data.HasRequest = data.HasPost || data.HasPut data.HasParam = data.HasGet || data.HasPut || data.HasDelete handlerDir := filepath.Join("internal", "handlers", handlerLower) modelDir := filepath.Join("internal", "models", handlerLower) for _, d := range []string{handlerDir, modelDir} { if err := os.MkdirAll(d, 0755); err != nil { panic(err) } } generateHandlerFile(data, handlerDir) generateModelFile(data, modelDir) updateRoutesFile(data) fmt.Printf("✅ Successfully generated handler: %s\n", handlerName) } // ================= HANDLER ===================== func generateHandlerFile(data HandlerData, handlerDir string) { handlerContent := fmt.Sprintf(`package handlers import ( models "%s/internal/models/%s" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" ) // %sHandler handles %s services type %sHandler struct{} // New%sHandler creates a new %sHandler func New%sHandler() *%sHandler { return &%sHandler{} } `, data.ModuleName, data.NameLower, data.Name, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.Name, data.Name) var methodsContent string // GET if data.HasGet { methodsContent += fmt.Sprintf(` // Get%s godoc // @Summary Get %s // @Description Returns a list of %s // @Tags %s // @Accept json // @Produce json // @Success 200 {object} models.%sGetResponse "%s GET response" // @Router /api/v1/%s [get] func (h *%sHandler) Get%s(c *gin.Context) { response := models.%sGetResponse{ Message: "List of %s", Data: []string{"%s 1", "%s 2"}, } c.JSON(http.StatusOK, response) } // Get%sByID godoc // @Summary Get %s by ID // @Description Returns a single %s by ID // @Tags %s // @Accept json // @Produce json // @Param id path string true "%s ID" // @Success 200 {object} models.%sGetByIDResponse "%s GET by ID response" // @Failure 404 {object} models.ErrorResponse "%s not found" // @Router /api/v1/%s/{id} [get] func (h *%sHandler) Get%sByID(c *gin.Context) { id := c.Param("id") response := models.%sGetByIDResponse{ ID: id, Message: "%s details", } c.JSON(http.StatusOK, response) } `, data.Name, data.NameLower, data.NamePlural, data.NameLower, data.Name, data.Name, data.NamePlural, data.Name, data.Name, data.Name, data.NamePlural, data.Name, data.Name, data.Name, data.Name, data.NameLower, data.NameLower, data.NameLower, data.Name, data.Name, data.Name, data.NameLower, data.Name, data.Name, data.Name) } // POST if data.HasPost { methodsContent += fmt.Sprintf(` // Create%s godoc // @Summary Create %s // @Description Creates a new %s // @Tags %s // @Accept json // @Produce json // @Param request body models.%sCreateRequest true "%s creation request" // @Success 201 {object} models.%sCreateResponse "%s created successfully" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Router /api/v1/%s [post] func (h *%sHandler) Create%s(c *gin.Context) { var req models.%sCreateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response := models.%sCreateResponse{ ID: uuid.NewString(), Message: "%s created successfully", Data: req, } c.JSON(http.StatusCreated, response) } `, data.Name, data.NameLower, data.NameLower, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.NamePlural, data.Name, data.Name, data.Name, data.Name, data.Name) } // PUT if data.HasPut { methodsContent += fmt.Sprintf(` // Update%s godoc // @Summary Update %s // @Description Updates an existing %s // @Tags %s // @Accept json // @Produce json // @Param id path string true "%s ID" // @Param request body models.%sUpdateRequest true "%s update request" // @Success 200 {object} models.%sUpdateResponse "%s updated successfully" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Failure 404 {object} models.ErrorResponse "%s not found" // @Router /api/v1/%s/{id} [put] func (h *%sHandler) Update%s(c *gin.Context) { id := c.Param("id") var req models.%sUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response := models.%sUpdateResponse{ ID: id, Message: "%s updated successfully", Data: req, } c.JSON(http.StatusOK, response) } `, data.Name, data.NameLower, data.NameLower, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.Name, data.Name, data.Name, data.NameLower, data.Name, data.Name, data.Name, data.Name) } // DELETE if data.HasDelete { methodsContent += fmt.Sprintf(` // Delete%s godoc // @Summary Delete %s // @Description Deletes a %s by ID // @Tags %s // @Accept json // @Produce json // @Param id path string true "%s ID" // @Success 200 {object} models.%sDeleteResponse "%s deleted successfully" // @Failure 404 {object} models.ErrorResponse "%s not found" // @Router /api/v1/%s/{id} [delete] func (h *%sHandler) Delete%s(c *gin.Context) { id := c.Param("id") response := models.%sDeleteResponse{ ID: id, Message: "%s deleted successfully", } c.JSON(http.StatusOK, response) } `, data.Name, data.NameLower, data.NameLower, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.NameLower, data.Name, data.Name, data.Name) } writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent+methodsContent) } // ================= MODELS ===================== func generateModelFile(data HandlerData, modelDir string) { modelContent := "package models\n\n" if data.HasGet { modelContent += fmt.Sprintf(`// %sGetResponse response for GET %s type %sGetResponse struct { Message string `+"`json:\"message\"`"+` Data interface{} `+"`json:\"data\"`"+` } // %sGetByIDResponse response for GET %s by ID type %sGetByIDResponse struct { ID string `+"`json:\"id\"`"+` Message string `+"`json:\"message\"`"+` } `, data.Name, data.NamePlural, data.Name, data.Name, data.NameLower, data.Name) } if data.HasPost { modelContent += fmt.Sprintf(`// %sCreateRequest request for creating %s type %sCreateRequest struct { Name string `+"`json:\"name\" binding:\"required\"`"+` } // %sCreateResponse response for creating %s type %sCreateResponse struct { ID string `+"`json:\"id\"`"+` Message string `+"`json:\"message\"`"+` Data interface{} `+"`json:\"data\"`"+` } `, data.Name, data.NameLower, data.Name, data.Name, data.NameLower, data.Name) } if data.HasPut { modelContent += fmt.Sprintf(`// %sUpdateRequest request for updating %s type %sUpdateRequest struct { Name string `+"`json:\"name\" binding:\"required\"`"+` } // %sUpdateResponse response for updating %s type %sUpdateResponse struct { ID string `+"`json:\"id\"`"+` Message string `+"`json:\"message\"`"+` Data interface{} `+"`json:\"data\"`"+` } `, data.Name, data.NameLower, data.Name, data.Name, data.NameLower, data.Name) } if data.HasDelete { modelContent += fmt.Sprintf(`// %sDeleteResponse response for deleting %s type %sDeleteResponse struct { ID string `+"`json:\"id\"`"+` Message string `+"`json:\"message\"`"+` } `, data.Name, data.NameLower, data.Name) } modelContent += `// ErrorResponse common error response type ErrorResponse struct { Error string ` + "`json:\"error\"`" + ` } ` writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) } // ================= ROUTES ===================== func updateRoutesFile(data HandlerData) { routesFile := "internal/routes/v1/routes.go" content, err := os.ReadFile(routesFile) if err != nil { fmt.Printf("Error reading routes.go: %v\n", err) return } routesContent := string(content) // import importPattern := fmt.Sprintf("%sHandlers \"%s/internal/handlers/%s\"", data.NameLower, data.ModuleName, data.NameLower) if !strings.Contains(routesContent, importPattern) { importToAdd := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", data.NameLower, data.ModuleName, data.NameLower) if strings.Contains(routesContent, "import (") { routesContent = strings.Replace(routesContent, "import (", "import (\n"+importToAdd, 1) } } // routes newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name) newRoutes += fmt.Sprintf("\t\t%sHandler := %sHandlers.New%sHandler()\n", data.NameLower, data.NameLower, data.Name) if data.HasGet { newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s\", %sHandler.Get%s)\n", data.NamePlural, data.NameLower, data.Name) newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n", data.NameLower, data.NameLower, data.Name) } if data.HasPost { newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", data.NamePlural, data.NameLower, data.Name) } if data.HasPut { newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s/:id\", %sHandler.Update%s)\n", data.NameLower, data.NameLower, data.Name) } if data.HasDelete { newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", data.NameLower, data.NameLower, data.Name) } newRoutes += "\n" insertMarker := "\t\tprotected := v1.Group(\"/\")" if strings.Contains(routesContent, insertMarker) { if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { routesContent = strings.Replace(routesContent, insertMarker, newRoutes+insertMarker, 1) } else { fmt.Printf("⚠️ Routes for %s already exist, skipping...\n", data.Name) } } if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { fmt.Printf("Error writing routes.go: %v\n", err) return } fmt.Printf("✅ Updated routes.go with %s endpoints\n", data.Name) } // ================= UTIL ===================== func writeFile(filename, content string) { if err := os.WriteFile(filename, []byte(content), 0644); err != nil { fmt.Printf("Error creating file %s: %v\n", filename, err) return } fmt.Printf("Generated: %s\n", filename) }