392 lines
11 KiB
Go
392 lines
11 KiB
Go
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)
|
|
}
|