Generete Module Handler

This commit is contained in:
2025-08-15 15:07:38 +07:00
parent a64cbf4438
commit 66f6d0a83b
24 changed files with 1977 additions and 2369 deletions

View File

@@ -3,8 +3,8 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
@@ -26,9 +26,9 @@ type HandlerData struct {
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run generate-handler.go <handler-name> [methods]")
fmt.Println("Usage: go run generate-handler.go <nama-handler> [methods]")
fmt.Println("Example: go run generate-handler.go user get post put delete")
fmt.Println("Methods: get, post, put, delete (optional, default: get post)")
fmt.Println("Example: go run generate-handler.go product get post")
os.Exit(1)
}
@@ -71,217 +71,267 @@ func main() {
fmt.Printf("Generating handler: %s with methods: %v\n", handlerName, methods)
// Create directories with proper structure
handlerDir := filepath.Join("internal", "handlers", handlerLower)
modelDir := filepath.Join("internal", "models", handlerLower)
// Create directories if they don't exist
os.MkdirAll("internal/handlers", 0755)
os.MkdirAll("internal/models", 0755)
dirs := []string{
handlerDir,
modelDir,
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
fmt.Printf("Error creating directory %s: %v\n", dir, err)
os.Exit(1)
}
fmt.Printf("Created directory: %s\n", dir)
}
// Generate files
generateHandlerFile(data)
generateModelFile(data)
generateHandlerFile(data, handlerDir)
generateModelFile(data, modelDir)
updateRoutesFile(data)
fmt.Printf("Successfully generated handler: %s\n", handlerName)
fmt.Println("Don't forget to run: swag init -g cmd/api/main.go")
fmt.Println("Don't forget to:")
fmt.Println("1. Run: swag init -g cmd/api/main.go")
fmt.Println("2. Update your service layer if needed")
fmt.Println("3. Add repository layer if required")
}
func generateHandlerFile(data HandlerData) {
handlerTemplate := `package handlers
func generateHandlerFile(data HandlerData, handlerDir string) {
handlerContent := fmt.Sprintf(`package handlers
import (
"net/http"
"strings"
"{{.ModuleName}}/internal/models"
"api-service/internal/models/%s"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// {{.Name}}Handler handles {{.NameLower}} services
type {{.Name}}Handler struct{}
// %sHandler handles %s services
type %sHandler struct{}
// New{{.Name}}Handler creates a new {{.Name}}Handler
func New{{.Name}}Handler() *{{.Name}}Handler {
return &{{.Name}}Handler{}
}
{{if .HasGet}}
// Get{{.Name}} godoc
// @Summary Get {{.NameLower}}
// @Description Returns a list of {{.NamePlural}}
// @Tags {{.NameLower}}
// New%sHandler creates a new %sHandler
func New%sHandler() *%sHandler {
return &%sHandler{}
}`, data.NameLower, data.Name, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.Name)
// Add methods based on requested operations
var methodsContent string
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.{{.Name}}GetResponse "{{.Name}} GET response"
// @Router /api/v1/{{.NamePlural}} [get]
func (h *{{.Name}}Handler) Get{{.Name}}(c *gin.Context) {
response := models.{{.Name}}GetResponse{
Message: "List of {{.NamePlural}}",
Data: []string{"{{.Name}} 1", "{{.Name}} 2"},
// @Success 200 {object} %s.%sGetResponse "%s GET response"
// @Router /api/v1/%s [get]
func (h *%sHandler) Get%s(c *gin.Context) {
response := %s.%sGetResponse{
Message: "List of %s",
Data: []string{"%s 1", "%s 2"},
}
c.JSON(http.StatusOK, response)
}
{{end}}
{{if .HasGet}}
// Get{{.Name}}ByID godoc
// @Summary Get {{.NameLower}} by ID
// @Description Returns a single {{.NameLower}} by ID
// @Tags {{.NameLower}}
// 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 "{{.Name}} ID"
// @Success 200 {object} models.{{.Name}}GetByIDResponse "{{.Name}} GET by ID response"
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
// @Router /api/v1/{{.NamePlural}}/{id} [get]
func (h *{{.Name}}Handler) Get{{.Name}}ByID(c *gin.Context) {
// @Param id path string true "%s ID"
// @Success 200 {object} %s.%sGetByIDResponse "%s GET by ID response"
// @Failure 404 {object} %s.ErrorResponse "%s not found"
// @Router /api/v1/%s/{id} [get]
func (h *%sHandler) Get%sByID(c *gin.Context) {
id := c.Param("id")
response := models.{{.Name}}GetByIDResponse{
response := %s.%sGetByIDResponse{
ID: id,
Message: "{{.Name}} details",
Message: "%s details",
}
c.JSON(http.StatusOK, response)
}
{{end}}
{{if .HasPost}}
// Create{{.Name}} godoc
// @Summary Create {{.NameLower}}
// @Description Creates a new {{.NameLower}}
// @Tags {{.NameLower}}
}`,
data.Name, data.NameLower, data.NamePlural, data.NameLower,
data.NameLower, data.Name, data.Name, data.NamePlural,
data.Name, data.Name, data.NameLower, data.Name, data.Name, data.Name,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
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.{{.Name}}CreateRequest true "{{.Name}} creation request"
// @Success 201 {object} models.{{.Name}}CreateResponse "{{.Name}} created successfully"
// @Failure 400 {object} models.ErrorResponse "Bad request"
// @Router /api/v1/{{.NamePlural}} [post]
func (h *{{.Name}}Handler) Create{{.Name}}(c *gin.Context) {
var req models.{{.Name}}CreateRequest
// @Param request body %s.%sCreateRequest true "%s creation request"
// @Success 201 {object} %s.%sCreateResponse "%s created successfully"
// @Failure 400 {object} %s.ErrorResponse "Bad request"
// @Router /api/v1/%s [post]
func (h *%sHandler) Create%s(c *gin.Context) {
var req %s.%sCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := models.{{.Name}}CreateResponse{
response := %s.%sCreateResponse{
ID: uuid.NewString(),
Message: "{{.Name}} created successfully",
Message: "%s created successfully",
Data: req,
}
c.JSON(http.StatusCreated, response)
}
{{end}}
{{if .HasPut}}
// Update{{.Name}} godoc
// @Summary Update {{.NameLower}}
// @Description Updates an existing {{.NameLower}}
// @Tags {{.NameLower}}
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.NameLower, data.Name, data.Name, data.NameLower, data.Name,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name)
}
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 "{{.Name}} ID"
// @Param request body models.{{.Name}}UpdateRequest true "{{.Name}} update request"
// @Success 200 {object} models.{{.Name}}UpdateResponse "{{.Name}} updated successfully"
// @Failure 400 {object} models.ErrorResponse "Bad request"
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
// @Router /api/v1/{{.NamePlural}}/{id} [put]
func (h *{{.Name}}Handler) Update{{.Name}}(c *gin.Context) {
// @Param id path string true "%s ID"
// @Param request body %s.%sUpdateRequest true "%s update request"
// @Success 200 {object} %s.%sUpdateResponse "%s updated successfully"
// @Failure 400 {object} %s.ErrorResponse "Bad request"
// @Failure 404 {object} %s.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.{{.Name}}UpdateRequest
var req %s.%sUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := models.{{.Name}}UpdateResponse{
response := %s.%sUpdateResponse{
ID: id,
Message: "{{.Name}} updated successfully",
Message: "%s updated successfully",
Data: req,
}
c.JSON(http.StatusOK, response)
}
{{end}}
{{if .HasDelete}}
// Delete{{.Name}} godoc
// @Summary Delete {{.NameLower}}
// @Description Deletes a {{.NameLower}} by ID
// @Tags {{.NameLower}}
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.Name,
data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower,
data.Name, data.Name, data.Name, data.Name)
}
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 "{{.Name}} ID"
// @Success 200 {object} models.{{.Name}}DeleteResponse "{{.Name}} deleted successfully"
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
// @Router /api/v1/{{.NamePlural}}/{id} [delete]
func (h *{{.Name}}Handler) Delete{{.Name}}(c *gin.Context) {
// @Param id path string true "%s ID"
// @Success 200 {object} %s.%sDeleteResponse "%s deleted successfully"
// @Failure 404 {object} %s.ErrorResponse "%s not found"
// @Router /api/v1/%s/{id} [delete]
func (h *%sHandler) Delete%s(c *gin.Context) {
id := c.Param("id")
response := models.{{.Name}}DeleteResponse{
response := %s.%sDeleteResponse{
ID: id,
Message: "{{.Name}} deleted successfully",
Message: "%s deleted successfully",
}
c.JSON(http.StatusOK, response)
}
{{end}}
`
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name, data.NameLower, data.Name, data.Name)
}
writeFile("internal/handlers/"+data.NameLower+".go", handlerTemplate, data)
fullContent := handlerContent + methodsContent
handlerFile := filepath.Join(handlerDir, data.NameLower+".go")
writeFile(handlerFile, fullContent)
}
func generateModelFile(data HandlerData) {
modelTemplate := `package models
func generateModelFile(data HandlerData, modelDir string) {
modelContent := fmt.Sprintf("package %s\n\n", data.NameLower)
{{if .HasGet}}
// {{.Name}}GetResponse represents the response for GET {{.NamePlural}}
type {{.Name}}GetResponse struct {
Message string {{.Backtick}}json:"message"{{.Backtick}}
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
if data.HasGet {
modelContent += fmt.Sprintf(`// %sGetResponse represents the response for GET %s
type %sGetResponse struct {
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{end}}
{{if .HasGet}}
// {{.Name}}GetByIDResponse represents the response for GET {{.NameLower}} by ID
type {{.Name}}GetByIDResponse struct {
ID string {{.Backtick}}json:"id"{{.Backtick}}
Message string {{.Backtick}}json:"message"{{.Backtick}}
// %sGetByIDResponse represents the response for GET %s by ID
type %sGetByIDResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
}
{{end}}
{{if .HasPost}}
// {{.Name}}CreateRequest represents the request for creating {{.NameLower}}
type {{.Name}}CreateRequest struct {
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
`, data.Name, data.NamePlural, data.Name, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
if data.HasPost {
modelContent += fmt.Sprintf(`// %sCreateRequest represents the request for creating %s
type %sCreateRequest struct {
Name string `+"`json:\"name\" binding:\"required\"`"+`
// Add more fields as needed
}
// {{.Name}}CreateResponse represents the response for creating {{.NameLower}}
type {{.Name}}CreateResponse struct {
ID string {{.Backtick}}json:"id"{{.Backtick}}
Message string {{.Backtick}}json:"message"{{.Backtick}}
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
// %sCreateResponse represents the response for creating %s
type %sCreateResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{end}}
{{if .HasPut}}
// {{.Name}}UpdateRequest represents the request for updating {{.NameLower}}
type {{.Name}}UpdateRequest struct {
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
`, data.Name, data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
if data.HasPut {
modelContent += fmt.Sprintf(`// %sUpdateRequest represents the request for updating %s
type %sUpdateRequest struct {
Name string `+"`json:\"name\" binding:\"required\"`"+`
// Add more fields as needed
}
// {{.Name}}UpdateResponse represents the response for updating {{.NameLower}}
type {{.Name}}UpdateResponse struct {
ID string {{.Backtick}}json:"id"{{.Backtick}}
Message string {{.Backtick}}json:"message"{{.Backtick}}
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
// %sUpdateResponse represents the response for updating %s
type %sUpdateResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{end}}
{{if .HasDelete}}
// {{.Name}}DeleteResponse represents the response for deleting {{.NameLower}}
type {{.Name}}DeleteResponse struct {
ID string {{.Backtick}}json:"id"{{.Backtick}}
Message string {{.Backtick}}json:"message"{{.Backtick}}
}
{{end}}
`, data.Name, data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
// ErrorResponse represents an error response
if data.HasDelete {
modelContent += fmt.Sprintf(`// %sDeleteResponse represents the response for deleting %s
type %sDeleteResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
}
`, data.Name, data.NameLower, data.Name, data.NameLower)
}
modelContent += `// ErrorResponse represents an error response
type ErrorResponse struct {
Error string {{.Backtick}}json:"error"{{.Backtick}}
Error string ` + "`json:\"error\"`" + `
}
`
// Replace backtick with actual backtick
modelTemplate = strings.ReplaceAll(modelTemplate, "{{.Backtick}}", "`")
writeFile("internal/models/"+data.NameLower+".go", modelTemplate, data)
modelFile := filepath.Join(modelDir, data.NameLower+".go")
writeFile(modelFile, modelContent)
}
func updateRoutesFile(data HandlerData) {
@@ -294,28 +344,65 @@ func updateRoutesFile(data HandlerData) {
return
}
// Convert to string
routesContent := string(content)
// Find the place to insert new routes
insertMarker := "\t\t// Example endpoints"
// Check if import already exists
importPattern := fmt.Sprintf(`%sHandlers "api-service/internal/handlers/%s"`, data.NameLower, data.NameLower)
if !strings.Contains(routesContent, importPattern) {
// Find the import block and insert the new import
importToAdd := fmt.Sprintf("\t%sHandlers \"api-service/internal/handlers/%s\"", data.NameLower, data.NameLower)
// Find the import block end
importEndMarker := "\n)\n\n// RegisterRoutes"
if !strings.Contains(routesContent, importEndMarker) {
importEndMarker = "\n)\n\nfunc RegisterRoutes"
}
// Find the line before the closing parenthesis
lines := strings.Split(routesContent, "\n")
var newLines []string
importBlockFound := false
importAdded := false
for _, line := range lines {
newLines = append(newLines, line)
// Check if we're in the import block
if strings.Contains(line, "import (") {
importBlockFound = true
continue
}
// Check if we're at the end of import block
if importBlockFound && strings.TrimSpace(line) == ")" && !importAdded {
// Insert the new import before the closing parenthesis
newLines = newLines[:len(newLines)-1] // Remove the last line (closing parenthesis)
newLines = append(newLines, importToAdd)
newLines = append(newLines, line) // Add back the closing parenthesis
importAdded = true
}
}
if importAdded {
routesContent = strings.Join(newLines, "\n")
} else {
// Fallback to simple string replacement
if strings.Contains(routesContent, importEndMarker) {
routesContent = strings.Replace(routesContent, importEndMarker, "\n"+importToAdd+importEndMarker, 1)
}
}
}
// Generate new 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\t%sHandler := handlers.New%sHandler()\n", data.NameLower, data.Name)
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s\", %sHandler.Get%s)\n", data.NamePlural, data.NameLower, data.Name)
}
if data.HasGet {
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n", data.NamePlural, data.NameLower, data.Name)
}
if data.HasPost {
if !data.HasGet {
newRoutes += fmt.Sprintf("\t\t%sHandler := handlers.New%sHandler()\n", data.NameLower, data.Name)
}
newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", data.NamePlural, data.NameLower, data.Name)
}
@@ -329,8 +416,17 @@ func updateRoutesFile(data HandlerData) {
newRoutes += "\n"
// Insert new routes after the marker
newContent := strings.Replace(routesContent, insertMarker, insertMarker+"\n"+newRoutes, 1)
// Find the place to insert new routes (after the protected group)
insertMarker := "\t\tprotected := v1.Group(\"/\")"
// Check if routes already exist
if strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) {
fmt.Printf("Routes for %s already exist, skipping...\n", data.Name)
return
}
// Insert new routes before the protected group
newContent := strings.Replace(routesContent, insertMarker, newRoutes+insertMarker, 1)
// Write back to file
err = os.WriteFile(routesFile, []byte(newContent), 0644)
@@ -338,27 +434,15 @@ func updateRoutesFile(data HandlerData) {
fmt.Printf("Error writing routes file: %v\n", err)
return
}
fmt.Printf("Updated routes file with %s endpoints\n", data.Name)
}
func writeFile(filename, templateStr string, data HandlerData) {
tmpl, err := template.New("template").Parse(templateStr)
if err != nil {
fmt.Printf("Error parsing template: %v\n", err)
return
}
file, err := os.Create(filename)
func writeFile(filename, content string) {
err := os.WriteFile(filename, []byte(content), 0644)
if err != nil {
fmt.Printf("Error creating file %s: %v\n", filename, err)
return
}
defer file.Close()
err = tmpl.Execute(file, data)
if err != nil {
fmt.Printf("Error executing template: %v\n", err)
return
}
fmt.Printf("Generated: %s\n", filename)
}