diff --git a/docs/docs.go b/docs/docs.go index d43a1ee5..4394e53e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,5 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT +// Code generated by swaggo/swag. DO NOT EDIT. + package docs import "github.com/swaggo/swag" @@ -224,7 +225,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "bpjs" + "BPJS" ], "summary": "Get participant data by NIK", "parameters": [ @@ -285,7 +286,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get Retribusi by ID", "parameters": [ @@ -333,7 +334,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Update retribusi", "parameters": [ @@ -390,7 +391,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Delete retribusi", "parameters": [ @@ -440,7 +441,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi with pagination and optional aggregation", "parameters": [ @@ -520,7 +521,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Create retribusi", "parameters": [ @@ -566,7 +567,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi with dynamic filtering", "parameters": [ @@ -635,7 +636,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi statistics", "parameters": [ @@ -1429,8 +1430,6 @@ var SwaggerInfo = &swag.Spec{ Description: "A comprehensive Go API service with Swagger documentation", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, - LeftDelim: "{{", - RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index b5838b68..8b222e66 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -222,7 +222,7 @@ "application/json" ], "tags": [ - "bpjs" + "BPJS" ], "summary": "Get participant data by NIK", "parameters": [ @@ -283,7 +283,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get Retribusi by ID", "parameters": [ @@ -331,7 +331,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Update retribusi", "parameters": [ @@ -388,7 +388,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Delete retribusi", "parameters": [ @@ -438,7 +438,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi with pagination and optional aggregation", "parameters": [ @@ -518,7 +518,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Create retribusi", "parameters": [ @@ -564,7 +564,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi with dynamic filtering", "parameters": [ @@ -633,7 +633,7 @@ "application/json" ], "tags": [ - "retribusi" + "Retribusi" ], "summary": "Get retribusi statistics", "parameters": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index f2755e90..cc1ecd49 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -501,7 +501,7 @@ paths: type: object summary: Get participant data by NIK tags: - - bpjs + - BPJS /api/v1/retribusi/{id}: delete: consumes: @@ -534,7 +534,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Delete retribusi tags: - - retribusi + - Retribusi get: consumes: - application/json @@ -566,7 +566,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get Retribusi by ID tags: - - retribusi + - Retribusi put: consumes: - application/json @@ -604,7 +604,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Update retribusi tags: - - retribusi + - Retribusi /api/v1/retribusis: get: consumes: @@ -659,7 +659,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi with pagination and optional aggregation tags: - - retribusi + - Retribusi post: consumes: - application/json @@ -688,7 +688,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Create retribusi tags: - - retribusi + - Retribusi /api/v1/retribusis/dynamic: get: consumes: @@ -734,7 +734,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi with dynamic filtering tags: - - retribusi + - Retribusi /api/v1/retribusis/stats: get: consumes: @@ -758,7 +758,7 @@ paths: $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi statistics tags: - - retribusi + - Retribusi /api/v1/token/generate: post: consumes: diff --git a/internal/handlers/reference/peserta.go b/internal/handlers/reference/peserta.go index 2071476c..1fc491f6 100644 --- a/internal/handlers/reference/peserta.go +++ b/internal/handlers/reference/peserta.go @@ -27,7 +27,7 @@ func NewPesertaHandler(cfg config.BpjsConfig) *PesertaHandler { // GetPesertaByNIK godoc // @Summary Get participant data by NIK // @Description Search participant data based on Population NIK and service date -// @Tags bpjs +// @Tags BPJS // @Accept json // @Produce json // @Param nik path string true "NIK KTP" diff --git a/internal/handlers/retribusi/retribusi.go b/internal/handlers/retribusi/retribusi.go index 9b25975b..c4f1cf6b 100644 --- a/internal/handlers/retribusi/retribusi.go +++ b/internal/handlers/retribusi/retribusi.go @@ -63,7 +63,7 @@ func NewRetribusiHandler() *RetribusiHandler { // GetRetribusi godoc // @Summary Get retribusi with pagination and optional aggregation // @Description Returns a paginated list of retribusis with optional summary statistics -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param limit query int false "Limit (max 100)" default(10) @@ -181,7 +181,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) { // GetRetribusiByID godoc // @Summary Get Retribusi by ID // @Description Returns a single retribusi by ID -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" @@ -229,7 +229,7 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) { // GetRetribusiDynamic godoc // @Summary Get retribusi with dynamic filtering // @Description Returns retribusis with advanced dynamic filtering like Directus -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param fields query string false "Fields to select (e.g., fields=*.*)" @@ -496,7 +496,7 @@ func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) { // CreateRetribusi godoc // @Summary Create retribusi // @Description Creates a new retribusi record -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param request body modelsretribusi.RetribusiCreateRequest true "Retribusi creation request" @@ -550,7 +550,7 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { // UpdateRetribusi godoc // @Summary Update retribusi // @Description Updates an existing retribusi record -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" @@ -614,7 +614,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { // DeleteRetribusi godoc // @Summary Delete retribusi // @Description Soft deletes a retribusi by setting status to 'deleted' -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" @@ -662,7 +662,7 @@ func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) { // GetRetribusiStats godoc // @Summary Get retribusi statistics // @Description Returns comprehensive statistics about retribusi data -// @Tags retribusi +// @Tags Retribusi // @Accept json // @Produce json // @Param status query string false "Filter statistics by status" diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 84b302b8..9618d0ff 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -7,12 +7,13 @@ import ( healthcheckHandlers "api-service/internal/handlers/healthcheck" bpjsPesertaHandlers "api-service/internal/handlers/reference" retribusiHandlers "api-service/internal/handlers/retribusi" - swaggerHandlers "api-service/internal/handlers/swagger" "api-service/internal/middleware" services "api-service/internal/services/auth" "api-service/pkg/logger" "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" ) func RegisterRoutes(cfg *config.Config) *gin.Engine { @@ -41,11 +42,8 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { sistem := router.Group("/api/sistem") sistem.GET("/health", healthCheckHandler.CheckHealth) - // Initialize Swagger handler - swaggerHandler := swaggerHandlers.NewHandler(cfg) - - // Register Swagger routes - swaggerHandler.RegisterRoutes(router) + // Swagger UI route + router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // API v1 group v1 := router.Group("/api/v1") @@ -70,6 +68,7 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { // BPJS endpoints bpjsPesertaHandler := bpjsPesertaHandlers.NewPesertaHandler(cfg.Bpjs) v1.GET("/bpjs/peserta/nik/:nik/tglSEP/:tglSEP", bpjsPesertaHandler.GetPesertaByNIK) + // ============= PUBLISHED ROUTES =============================================== // // Retribusi endpoints diff --git a/tools/general/generate-handler.go b/tools/general/generate-handler.go index cc9312cc..06063330 100644 --- a/tools/general/generate-handler.go +++ b/tools/general/generate-handler.go @@ -14,6 +14,7 @@ type HandlerData struct { NameLower string NamePlural string Category string // Untuk backward compatibility (bagian pertama) + DirPath string // Path direktori lengkap ModuleName string TableName string HasGet bool @@ -28,84 +29,108 @@ type HandlerData struct { Timestamp string } -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-handler.go [category/]entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-handler.go product get post put delete") - fmt.Println(" go run generate-handler.go retribusi/tarif get post put delete dynamic search") - os.Exit(1) - } +type PathInfo struct { + Category string + EntityName string + DirPath string + FilePath string +} - // Parse entity path (could be "entity" or "category/entity") - entityPath := os.Args[1] - methods := []string{} - if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - // Default methods with advanced features - methods = []string{"get", "post", "put", "delete", "dynamic", "search"} +// parseEntityPath - Logic parsing yang diperbaiki +func parseEntityPath(entityPath string) (*PathInfo, error) { + if strings.TrimSpace(entityPath) == "" { + return nil, fmt.Errorf("entity path cannot be empty") } - - // Parse category and entity - support up to 4 levels - var category, entityName string - if strings.Contains(entityPath, "/") { - parts := strings.Split(entityPath, "/") - if len(parts) < 2 || len(parts) > 4 { - fmt.Println("❌ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/entity'") - os.Exit(1) + var pathInfo PathInfo + parts := strings.Split(entityPath, "/") + // Validasi minimal 1 bagian (file saja) dan maksimal 4 + if len(parts) < 1 || len(parts) > 4 { + return nil, fmt.Errorf("invalid path format: use up to 4 levels like 'level1/level2/level3/entity'") + } + // Validasi bagian kosong + for i, part := range parts { + if strings.TrimSpace(part) == "" { + return nil, fmt.Errorf("empty path segment at position %d", i+1) } - // Entity name is the last part - entityName = parts[len(parts)-1] - // Category for backward compatibility (first part) - category = parts[0] + } + + pathInfo.EntityName = parts[len(parts)-1] + if len(parts) > 1 { + pathInfo.Category = parts[len(parts)-2] + pathInfo.DirPath = strings.Join(parts[:len(parts)-1], "/") + pathInfo.FilePath = pathInfo.DirPath + "/" + strings.ToLower(pathInfo.EntityName) + ".go" } else { - category = "" - entityName = entityPath + pathInfo.Category = "models" + pathInfo.DirPath = "" + pathInfo.FilePath = strings.ToLower(pathInfo.EntityName) + ".go" + } + return &pathInfo, nil +} + +// validateMethods - Validasi method yang diinput +func validateMethods(methods []string) error { + validMethods := map[string]bool{ + "get": true, "post": true, "put": true, "delete": true, + "stats": true, "dynamic": true, "search": true, } - // Format names - entityName = strings.Title(entityName) // PascalCase entity name - entityLower := strings.ToLower(entityName) - entityPlural := entityLower + "s" + for _, method := range methods { + if !validMethods[strings.ToLower(method)] { + return fmt.Errorf("invalid method: %s. Valid methods: get, post, put, delete, stats, dynamic, search", method) + } + } + return nil +} - // Table name: include category if exists - var tableName string - if category != "" { - tableName = "data_" + category + "_" + entityLower +// generateTableName - Generate table name berdasarkan path lengkap +func generateTableName(pathInfo *PathInfo) string { + entityLower := strings.ToLower(pathInfo.EntityName) + + if pathInfo.DirPath != "" { + // Replace "/" dengan "_" untuk table name + pathForTable := strings.ReplaceAll(pathInfo.DirPath, "/", "_") + return "data_" + pathForTable + "_" + entityLower + } + return "data_" + entityLower +} + +// createDirectories - Buat direktori sesuai struktur path +func createDirectories(pathInfo *PathInfo) (string, string, error) { + var handlerDir, modelDir string + + if pathInfo.DirPath != "" { + handlerDir = filepath.Join("internal", "handlers", pathInfo.DirPath) + modelDir = filepath.Join("internal", "models", pathInfo.DirPath) } else { - tableName = "data_" + entityLower + handlerDir = filepath.Join("internal", "handlers") + modelDir = filepath.Join("internal", "models") } - data := HandlerData{ - Name: entityName, - NameLower: entityLower, - NamePlural: entityPlural, - Category: category, - ModuleName: "api-service", - TableName: tableName, - HasPagination: true, - HasFilter: true, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), + // Create directories + for _, dir := range []string{handlerDir, modelDir} { + if err := os.MkdirAll(dir, 0755); err != nil { + return "", "", fmt.Errorf("failed to create directory %s: %v", dir, err) + } } - // Set methods based on arguments - 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 - case "stats": - data.HasStats = true - case "dynamic": - data.HasDynamic = true - case "search": - data.HasSearch = true + return handlerDir, modelDir, nil +} + +// setMethods - Set method flags berdasarkan input +func setMethods(data *HandlerData, methods []string) { + methodMap := map[string]*bool{ + "get": &data.HasGet, + "post": &data.HasPost, + "put": &data.HasPut, + "delete": &data.HasDelete, + "stats": &data.HasStats, + "dynamic": &data.HasDynamic, + "search": &data.HasSearch, + } + + for _, method := range methods { + if flag, exists := methodMap[strings.ToLower(method)]; exists { + *flag = true } } @@ -113,142 +138,204 @@ func main() { if data.HasGet { data.HasStats = true } +} - // Create directories with improved logic - var handlerDir, modelDir string - if category != "" { - handlerDir = filepath.Join("internal", "handlers", category) - modelDir = filepath.Join("internal", "models", category) - } else { - handlerDir = filepath.Join("internal", "handlers") - modelDir = filepath.Join("internal", "models") +func main() { + // Validasi argument + if len(os.Args) < 2 { + fmt.Println("Usage: go run generate-handler.go [path/]entity [methods]") + fmt.Println("Examples:") + fmt.Println(" go run generate-handler.go product get post put delete") + fmt.Println(" go run generate-handler.go retribusi/tarif get post put delete dynamic search") + fmt.Println(" go run generate-handler.go product/category/subcategory/item get post") + fmt.Println("\nSupported methods: get, post, put, delete, stats, dynamic, search") + os.Exit(1) } + // Parse entity path + entityPath := strings.TrimSpace(os.Args[1]) + pathInfo, err := parseEntityPath(entityPath) + if err != nil { + fmt.Printf("❌ Error parsing path: %v\n", err) + os.Exit(1) + } + + // Parse methods + var methods []string + if len(os.Args) > 2 { + methods = os.Args[2:] + } else { + // Default methods with advanced features + methods = []string{"get", "post", "put", "delete", "dynamic", "search"} + } + + // Validate methods + if err := validateMethods(methods); err != nil { + fmt.Printf("❌ %v\n", err) + os.Exit(1) + } + + // Format names + entityName := strings.Title(pathInfo.EntityName) // PascalCase entity name + entityLower := strings.ToLower(pathInfo.EntityName) + entityPlural := entityLower + "s" + + // Generate table name + tableName := generateTableName(pathInfo) + + // Create HandlerData + data := HandlerData{ + Name: entityName, + NameLower: entityLower, + NamePlural: entityPlural, + Category: pathInfo.Category, + DirPath: pathInfo.DirPath, + ModuleName: "api-service", + TableName: tableName, + HasPagination: true, + HasFilter: true, + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + } + + // Set methods + setMethods(&data, methods) + // Create directories - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } + handlerDir, modelDir, err := createDirectories(pathInfo) + if err != nil { + fmt.Printf("❌ Error creating directories: %v\n", err) + os.Exit(1) } // Generate files generateHandlerFile(data, handlerDir) generateModelFile(data, modelDir) - // updateRoutesFile(data) + updateRoutesFile(data) + // Success output fmt.Printf("✅ Successfully generated handler: %s\n", entityName) - if category != "" { - fmt.Printf("📁 Category: %s\n", category) + if pathInfo.Category != "" { + fmt.Printf("📁 Category: %s\n", pathInfo.Category) } - fmt.Printf("📁 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("📁 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) + if pathInfo.DirPath != "" { + fmt.Printf("📂 Path: %s\n", pathInfo.DirPath) + } + fmt.Printf("📄 Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) + fmt.Printf("📄 Model: %s\n", filepath.Join(modelDir, entityLower+".go")) + fmt.Printf("🗄️ Table: %s\n", tableName) + fmt.Printf("🛠️ Methods: %s\n", strings.Join(methods, ", ")) } // ================= HANDLER GENERATION ===================== func generateHandlerFile(data HandlerData, handlerDir string) { - // Build import path based on category - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models" + // var modelsImportPath string + // if data.Category != "" { + // modelsImportPath = data.ModuleName + "/internal/models/" + data.Category + // } else { + // modelsImportPath = data.ModuleName + "/internal/models" + // } + + // pakai strings.Builder biar lebih clean + var handlerContent strings.Builder + + // Header + handlerContent.WriteString("package handlers\n\n") + handlerContent.WriteString("import (\n") + handlerContent.WriteString(` "` + data.ModuleName + `/internal/config"` + "\n") + handlerContent.WriteString(` "` + data.ModuleName + `/internal/database"` + "\n") + handlerContent.WriteString(` models "` + data.ModuleName + `/internal/models"` + "\n") + if data.Category != "models" { + handlerContent.WriteString(` "` + data.ModuleName + `/internal/models/` + data.Category + `"` + "\n") } - handlerContent := `package handlers - -import ( - "` + data.ModuleName + `/internal/config" - "` + data.ModuleName + `/internal/database" - models "` + data.ModuleName + `/internal/models" - models` + data.NameLower + ` "` + modelsImportPath + `"` - - // Add conditional imports for dynamic and search functionality + // Conditional imports if data.HasDynamic || data.HasSearch { - handlerContent += ` - utils "` + data.ModuleName + `/internal/utils/filters"` + handlerContent.WriteString(` utils "` + data.ModuleName + `/internal/utils/filters"` + "\n") } - handlerContent += ` - "` + data.ModuleName + `/internal/utils/validation" - "context" - "database/sql" - "fmt" - "log" - "net/http" - "strconv" - "strings" - "sync" - "time" + handlerContent.WriteString(` "` + data.ModuleName + `/internal/utils/validation"` + "\n") + handlerContent.WriteString(` "context"` + "\n") + handlerContent.WriteString(` "database/sql"` + "\n") + handlerContent.WriteString(` "fmt"` + "\n") + handlerContent.WriteString(` "log"` + "\n") + handlerContent.WriteString(` "net/http"` + "\n") + handlerContent.WriteString(` "strconv"` + "\n") + handlerContent.WriteString(` "strings"` + "\n") + handlerContent.WriteString(` "sync"` + "\n") + handlerContent.WriteString(` "time"` + "\n\n") + handlerContent.WriteString(` "github.com/gin-gonic/gin"` + "\n") + handlerContent.WriteString(` "github.com/go-playground/validator/v10"` + "\n") + handlerContent.WriteString(` "github.com/google/uuid"` + "\n") + handlerContent.WriteString(")\n\n") - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) + // Vars + handlerContent.WriteString("var (\n") + handlerContent.WriteString(" " + data.NameLower + "db database.Service\n") + handlerContent.WriteString(" " + data.NameLower + "once sync.Once\n") + handlerContent.WriteString(" " + data.NameLower + "validate *validator.Validate\n") + handlerContent.WriteString(")\n\n") -var ( - ` + data.NameLower + `db database.Service - ` + data.NameLower + `once sync.Once - ` + data.NameLower + `validate *validator.Validate -) + // init func + handlerContent.WriteString("// Initialize the database connection and validator\n") + handlerContent.WriteString("func init() {\n") + handlerContent.WriteString(" " + data.NameLower + "once.Do(func() {\n") + handlerContent.WriteString(" " + data.NameLower + "db = database.New(config.LoadConfig())\n") + handlerContent.WriteString(" " + data.NameLower + "validate = validator.New()\n") + handlerContent.WriteString(" " + data.NameLower + "validate.RegisterValidation(\"" + data.NameLower + "_status\", validate" + data.Name + "Status)\n") + handlerContent.WriteString(" if " + data.NameLower + "db == nil {\n") + handlerContent.WriteString(" log.Fatal(\"Failed to initialize database connection\")\n") + handlerContent.WriteString(" }\n") + handlerContent.WriteString(" })\n") + handlerContent.WriteString("}\n\n") -// Initialize the database connection and validator -func init() { - ` + data.NameLower + `once.Do(func() { - ` + data.NameLower + `db = database.New(config.LoadConfig()) - ` + data.NameLower + `validate = validator.New() - // Register custom validations if needed - ` + data.NameLower + `validate.RegisterValidation("` + data.NameLower + `_status", validate` + data.Name + `Status) - if ` + data.NameLower + `db == nil { - log.Fatal("Failed to initialize database connection") - } - }) -} + // Custom validation + handlerContent.WriteString("// Custom validation for " + data.NameLower + " status\n") + handlerContent.WriteString("func validate" + data.Name + "Status(fl validator.FieldLevel) bool {\n") + handlerContent.WriteString(" return models.IsValidStatus(fl.Field().String())\n") + handlerContent.WriteString("}\n\n") -// Custom validation for ` + data.NameLower + ` status -func validate` + data.Name + `Status(fl validator.FieldLevel) bool { - return models.IsValidStatus(fl.Field().String()) -} + // Handler struct + handlerContent.WriteString("// " + data.Name + "Handler handles " + data.NameLower + " services\n") + handlerContent.WriteString("type " + data.Name + "Handler struct {\n") + handlerContent.WriteString(" db database.Service\n") + handlerContent.WriteString("}\n\n") -// ` + data.Name + `Handler handles ` + data.NameLower + ` services -type ` + data.Name + `Handler struct { - db database.Service -} + // Constructor + handlerContent.WriteString("// New" + data.Name + "Handler creates a new " + data.Name + "Handler\n") + handlerContent.WriteString("func New" + data.Name + "Handler() *" + data.Name + "Handler {\n") + handlerContent.WriteString(" return &" + data.Name + "Handler{\n") + handlerContent.WriteString(" db: " + data.NameLower + "db,\n") + handlerContent.WriteString(" }\n") + handlerContent.WriteString("}\n") -// New` + data.Name + `Handler creates a new ` + data.Name + `Handler -func New` + data.Name + `Handler() *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - db: ` + data.NameLower + `db, - } -}` - - // Add methods + // Add optional methods if data.HasGet { - handlerContent += generateGetMethods(data) + handlerContent.WriteString(generateGetMethods(data)) } if data.HasDynamic { - handlerContent += generateDynamicMethod(data) + handlerContent.WriteString(generateDynamicMethod(data)) } if data.HasSearch { - handlerContent += generateSearchMethod(data) + handlerContent.WriteString(generateSearchMethod(data)) } if data.HasPost { - handlerContent += generateCreateMethod(data) + handlerContent.WriteString(generateCreateMethod(data)) } if data.HasPut { - handlerContent += generateUpdateMethod(data) + handlerContent.WriteString(generateUpdateMethod(data)) } if data.HasDelete { - handlerContent += generateDeleteMethod(data) + handlerContent.WriteString(generateDeleteMethod(data)) } if data.HasStats { - handlerContent += generateStatsMethod(data) + handlerContent.WriteString(generateStatsMethod(data)) } // Add helper methods - handlerContent += generateHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) + handlerContent.WriteString(generateHelperMethods(data)) + // Write into file + writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent.String()) } func generateGetMethods(data HandlerData) string { @@ -265,7 +352,7 @@ func generateGetMethods(data HandlerData) string { // @Param include_summary query bool false "Include aggregation summary" default(false) // @Param status query string false "Filter by status" // @Param search query string false "Search in multiple fields" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetResponse "Success response" +// @Success 200 {object} ` + data.Category + `.` + data.Name + `GetResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/` + data.NamePlural + ` [get] @@ -294,7 +381,7 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { // Execute concurrent operations var ( - items []models` + data.NameLower + `.` + data.Name + ` + items []` + data.Category + `.` + data.Name + ` total int aggregateData *models.AggregateData wg sync.WaitGroup @@ -357,8 +444,8 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { // Build response meta := h.calculateMeta(limit, offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ - Message: "Data ` + data.NameLower + ` berhasil diambil", + response := ` + data.Category + `.` + data.Name + `GetResponse{ + Message: "Data ` + data.Category + ` berhasil diambil", Data: items, Meta: meta, } @@ -377,7 +464,7 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID (UUID)" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetByIDResponse "Success response" +// @Success 200 {object} ` + data.Category + `.` + data.Name + `GetByIDResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Invalid ID format" // @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -410,8 +497,8 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `ByID(c *gin.Context) { return } - response := models` + data.NameLower + `.` + data.Name + `GetByIDResponse{ - Message: "` + data.Name + ` details retrieved successfully", + response := ` + data.Category + `.` + data.Name + `GetByIDResponse{ + Message: "` + data.Category + ` details retrieved successfully", Data: item, } @@ -433,7 +520,7 @@ func generateDynamicMethod(data HandlerData) string { // @Param sort query string false "Sort fields (e.g., sort=date_created,-name)" // @Param limit query int false "Limit" default(10) // @Param offset query int false "Offset" default(0) -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetResponse "Success response" +// @Success 200 {object} ` + data.Category + `.` + data.Name + `GetResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/` + data.NamePlural + `/dynamic [get] @@ -466,8 +553,8 @@ func (h *` + data.Name + `Handler) Get` + data.Name + `Dynamic(c *gin.Context) { // Build response meta := h.calculateMeta(dynamicQuery.Limit, dynamicQuery.Offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ - Message: "Data ` + data.NameLower + ` berhasil diambil", + response := ` + data.Category + `.` + data.Name + `GetResponse{ + Message: "Data ` + data.Category + ` berhasil diambil", Data: items, Meta: meta, } @@ -546,7 +633,7 @@ func (h *` + data.Name + `Handler) Search` + data.Name + `Advanced(c *gin.Contex // Build response meta := h.calculateMeta(query.Limit, query.Offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ + response := ` + data.Category + `.` + data.Name + `GetResponse{ Message: fmt.Sprintf("Search results for '%s'", searchQuery), Data: items, Meta: meta, @@ -565,13 +652,13 @@ func generateCreateMethod(data HandlerData) string { // @Tags ` + data.NameLower + ` // @Accept json // @Produce json -// @Param request body models` + data.NameLower + `.` + data.Name + `CreateRequest true "` + data.Name + ` creation request" -// @Success 201 {object} models` + data.NameLower + `.` + data.Name + `CreateResponse "` + data.Name + ` created successfully" +// @Param request body ` + data.Category + `.` + data.Name + `CreateRequest true "` + data.Name + ` creation request" +// @Success 201 {object} ` + data.Category + `.` + data.Name + `CreateResponse "` + data.Name + ` created successfully" // @Failure 400 {object} models.ErrorResponse "Bad request or validation error" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/` + data.NamePlural + ` [post] func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - var req models` + data.NameLower + `.` + data.Name + `CreateRequest + var req ` + data.Category + `.` + data.Name + `CreateRequest if err := c.ShouldBindJSON(&req); err != nil { h.respondError(c, "Invalid request body", err, http.StatusBadRequest) return @@ -604,7 +691,7 @@ func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { return } - response := models` + data.NameLower + `.` + data.Name + `CreateResponse{ + response := ` + data.Category + `.` + data.Name + `CreateResponse{ Message: "` + data.Name + ` berhasil dibuat", Data: item, } @@ -623,8 +710,8 @@ func generateUpdateMethod(data HandlerData) string { // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID (UUID)" -// @Param request body models` + data.NameLower + `.` + data.Name + `UpdateRequest true "` + data.Name + ` update request" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `UpdateResponse "` + data.Name + ` updated successfully" +// @Param request body ` + data.Category + `.` + data.Name + `UpdateRequest true "` + data.Name + ` update request" +// @Success 200 {object} ` + data.Category + `.` + data.Name + `UpdateResponse "` + data.Name + ` updated successfully" // @Failure 400 {object} models.ErrorResponse "Bad request or validation error" // @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -638,7 +725,7 @@ func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { return } - var req models` + data.NameLower + `.` + data.Name + `UpdateRequest + var req ` + data.Category + `.` + data.Name + `UpdateRequest if err := c.ShouldBindJSON(&req); err != nil { h.respondError(c, "Invalid request body", err, http.StatusBadRequest) return @@ -672,7 +759,7 @@ func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { return } - response := models` + data.NameLower + `.` + data.Name + `UpdateResponse{ + response := ` + data.Category + `.` + data.Name + `UpdateResponse{ Message: "` + data.Name + ` berhasil diperbarui", Data: item, } @@ -691,7 +778,7 @@ func generateDeleteMethod(data HandlerData) string { // @Accept json // @Produce json // @Param id path string true "` + data.Name + ` ID (UUID)" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `DeleteResponse "` + data.Name + ` deleted successfully" +// @Success 200 {object} ` + data.Category + `.` + data.Name + `DeleteResponse "` + data.Name + ` deleted successfully" // @Failure 400 {object} models.ErrorResponse "Invalid ID format" // @Failure 404 {object} models.ErrorResponse "` + data.Name + ` not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -724,7 +811,7 @@ func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { return } - response := models` + data.NameLower + `.` + data.Name + `DeleteResponse{ + response := ` + data.Category + `.` + data.Name + `DeleteResponse{ Message: "` + data.Name + ` berhasil dihapus", ID: id, } @@ -744,7 +831,7 @@ func generateStatsMethod(data HandlerData) string { // @Produce json // @Param status query string false "Filter statistics by status" // @Success 200 {object} models.AggregateData "Statistics data" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" +// @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/` + data.NamePlural + `/stats [get] func (h *` + data.Name + `Handler) Get` + data.Name + `Stats(c *gin.Context) { dbConn, err := h.db.GetDB("postgres_satudata") @@ -774,11 +861,11 @@ func generateHelperMethods(data HandlerData) string { helperMethods := ` // Database operations -func (h *` + data.Name + `Handler) get` + data.Name + `ByID(ctx context.Context, dbConn *sql.DB, id string) (*models` + data.NameLower + `.` + data.Name + `, error) { +func (h *` + data.Name + `Handler) get` + data.Name + `ByID(ctx context.Context, dbConn *sql.DB, id string) (*` + data.Category + `.` + data.Name + `, error) { query := "SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM ` + data.TableName + ` WHERE id = $1 AND status != 'deleted'" row := dbConn.QueryRowContext(ctx, query, id) - var item models` + data.NameLower + `.` + data.Name + ` + var item ` + data.Category + `.` + data.Name + ` err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) if err != nil { return nil, err @@ -787,14 +874,14 @@ func (h *` + data.Name + `Handler) get` + data.Name + `ByID(ctx context.Context, return &item, nil } -func (h *` + data.Name + `Handler) create` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) (*models` + data.NameLower + `.` + data.Name + `, error) { +func (h *` + data.Name + `Handler) create` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *` + data.Category + `.` + data.Name + `CreateRequest) (*` + data.Category + `.` + data.Name + `, error) { id := uuid.New().String() now := time.Now() query := "INSERT INTO ` + data.TableName + ` (id, status, date_created, date_updated, name) VALUES ($1, $2, $3, $4, $5) RETURNING id, status, sort, user_created, date_created, user_updated, date_updated, name" row := dbConn.QueryRowContext(ctx, query, id, req.Status, now, now, req.Name) - var item models` + data.NameLower + `.` + data.Name + ` + var item ` + data.Category + `.` + data.Name + ` err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) if err != nil { return nil, fmt.Errorf("failed to create ` + data.NameLower + `: %w", err) @@ -803,13 +890,13 @@ func (h *` + data.Name + `Handler) create` + data.Name + `(ctx context.Context, return &item, nil } -func (h *` + data.Name + `Handler) update` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `UpdateRequest) (*models` + data.NameLower + `.` + data.Name + `, error) { +func (h *` + data.Name + `Handler) update` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *` + data.Category + `.` + data.Name + `UpdateRequest) (*` + data.Category + `.` + data.Name + `, error) { now := time.Now() query := "UPDATE ` + data.TableName + ` SET status = $2, date_updated = $3, name = $4 WHERE id = $1 AND status != 'deleted' RETURNING id, status, sort, user_created, date_created, user_updated, date_updated, name" row := dbConn.QueryRowContext(ctx, query, req.ID, req.Status, now, req.Name) - var item models` + data.NameLower + `.` + data.Name + ` + var item ` + data.Category + `.` + data.Name + ` err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) if err != nil { return nil, fmt.Errorf("failed to update ` + data.NameLower + `: %w", err) @@ -839,7 +926,7 @@ func (h *` + data.Name + `Handler) delete` + data.Name + `(ctx context.Context, return nil } -func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter, limit, offset int) ([]models` + data.NameLower + `.` + data.Name + `, error) { +func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, dbConn *sql.DB, filter ` + data.Category + `.` + data.Name + `Filter, limit, offset int) ([]` + data.Category + `.` + data.Name + `, error) { whereClause, args := h.buildWhereClause(filter) query := fmt.Sprintf("SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM ` + data.TableName + ` WHERE %s ORDER BY date_created DESC NULLS LAST LIMIT $%d OFFSET $%d", whereClause, len(args)+1, len(args)+2) args = append(args, limit, offset) @@ -850,7 +937,7 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, } defer rows.Close() - items := make([]models` + data.NameLower + `.` + data.Name + `, 0, limit) + items := make([]` + data.Category + `.` + data.Name + `, 0, limit) for rows.Next() { item, err := h.scan` + data.Name + `(rows) if err != nil { @@ -872,7 +959,7 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, helperMethods += ` // fetchRetribusisDynamic executes dynamic query -func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]models` + data.NameLower + `.` + data.Name + `, int, error) { +func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]` + data.Category + `.` + data.Name + `, int, error) { // Setup query builder builder := utils.NewQueryBuilder("` + data.TableName + `"). SetAllowedColumns([]string{ @@ -892,7 +979,7 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Co // Execute concurrent queries var ( - items []models` + data.NameLower + `.` + data.Name + ` + items [] ` + data.Category + `.` + data.Name + ` total int wg sync.WaitGroup errChan = make(chan error, 2) @@ -934,7 +1021,7 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Co } defer rows.Close() - var results []models` + data.NameLower + `.` + data.Name + ` + var results []` + data.Category + `.` + data.Name + ` for rows.Next() { item, err := h.scan` + data.Name + `(rows) if err != nil { @@ -972,8 +1059,8 @@ func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Co helperMethods += ` // Optimized scanning function -func (h *` + data.Name + `Handler) scan` + data.Name + `(rows *sql.Rows) (models` + data.NameLower + `.` + data.Name + `, error) { - var item models` + data.NameLower + `.` + data.Name + ` +func (h *` + data.Name + `Handler) scan` + data.Name + `(rows *sql.Rows) (` + data.Category + `.` + data.Name + `, error) { + var item ` + data.Category + `.` + data.Name + ` // Scan into individual fields to handle nullable types properly err := rows.Scan( @@ -990,7 +1077,7 @@ func (h *` + data.Name + `Handler) scan` + data.Name + `(rows *sql.Rows) (models return item, err } -func (h *` + data.Name + `Handler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter, total *int) error { +func (h *` + data.Name + `Handler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter ` + data.Category + `.` + data.Name + `Filter, total *int) error { whereClause, args := h.buildWhereClause(filter) countQuery := fmt.Sprintf("SELECT COUNT(*) FROM ` + data.TableName + ` WHERE %s", whereClause) if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil { @@ -1000,7 +1087,7 @@ func (h *` + data.Name + `Handler) getTotalCount(ctx context.Context, dbConn *sq } // Get comprehensive aggregate data dengan filter support -func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter) (*models.AggregateData, error) { +func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter ` + data.Category + `.` + data.Name + `Filter) (*models.AggregateData, error) { aggregate := &models.AggregateData{ ByStatus: make(map[string]int), } @@ -1158,8 +1245,8 @@ func (h *` + data.Name + `Handler) parsePaginationParams(c *gin.Context) (int, i return limit, offset, nil } -func (h *` + data.Name + `Handler) parseFilterParams(c *gin.Context) models` + data.NameLower + `.` + data.Name + `Filter { - filter := models` + data.NameLower + `.` + data.Name + `Filter{} +func (h *` + data.Name + `Handler) parseFilterParams(c *gin.Context) ` + data.Category + `.` + data.Name + `Filter { + filter := ` + data.Category + `.` + data.Name + `Filter{} if status := c.Query("status"); status != "" { if models.IsValidStatus(status) { @@ -1188,7 +1275,7 @@ func (h *` + data.Name + `Handler) parseFilterParams(c *gin.Context) models` + d } // Build WHERE clause dengan filter parameters -func (h *` + data.Name + `Handler) buildWhereClause(filter models` + data.NameLower + `.` + data.Name + `Filter) (string, []interface{}) { +func (h *` + data.Name + `Handler) buildWhereClause(filter ` + data.Category + `.` + data.Name + `Filter) (string, []interface{}) { conditions := []string{"status != 'deleted'"} args := []interface{}{} paramCount := 1 @@ -1242,7 +1329,7 @@ func (h *` + data.Name + `Handler) calculateMeta(limit, offset, total int) model } // validate` + data.Name + `Submission performs validation for duplicate entries and daily submission limits -func (h *` + data.Name + `Handler) validate` + data.Name + `Submission(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) error { +func (h *` + data.Name + `Handler) validate` + data.Name + `Submission(ctx context.Context, dbConn *sql.DB, req *` + data.Category + `.` + data.Name + `CreateRequest) error { // Import the validation utility validator := validation.NewDuplicateValidator(dbConn) @@ -1271,7 +1358,7 @@ func (h *` + data.Name + `Handler) validate` + data.Name + `Submission(ctx conte } // Example usage of the validation utility with custom configuration -func (h *` + data.Name + `Handler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) error { +func (h *` + data.Name + `Handler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *` + data.Category + `.` + data.Name + `CreateRequest) error { // Create validator instance validator := validation.NewDuplicateValidator(dbConn) @@ -1314,153 +1401,163 @@ func (h *` + data.Name + `Handler) getLastSubmissionTimeExample(ctx context.Cont // ================= MODEL GENERATION ===================== func generateModelFile(data HandlerData, modelDir string) { - modelContent := `package models + // Tentukan import block + var importBlock, nullablePrefix string -import ( - "` + data.ModuleName + `/internal/models" - "database/sql" - "encoding/json" - "time" + if data.Category == "models" { + importBlock = `import ( + "database/sql" + "encoding/json" + "time" ) +` + } else { + nullablePrefix = "models." + importBlock = `import ( + "` + data.ModuleName + `/internal/models" + "database/sql" + "encoding/json" + "time" +) +` + } + + modelContent := `package ` + data.Category + ` + +` + importBlock + ` // ` + data.Name + ` represents the data structure for the ` + data.NameLower + ` table // with proper null handling and optimized JSON marshaling type ` + data.Name + ` struct { - ID string ` + "`json:\"id\" db:\"id\"`" + ` - Status string ` + "`json:\"status\" db:\"status\"`" + ` - Sort models.NullableInt32 ` + "`json:\"sort,omitempty\" db:\"sort\"`" + ` - UserCreated sql.NullString ` + "`json:\"user_created,omitempty\" db:\"user_created\"`" + ` - DateCreated sql.NullTime ` + "`json:\"date_created,omitempty\" db:\"date_created\"`" + ` - UserUpdated sql.NullString ` + "`json:\"user_updated,omitempty\" db:\"user_updated\"`" + ` - DateUpdated sql.NullTime ` + "`json:\"date_updated,omitempty\" db:\"date_updated\"`" + ` - Name sql.NullString ` + "`json:\"name,omitempty\" db:\"name\"`" + ` + ID string ` + "`json:\"id\" db:\"id\"`" + ` + Status string ` + "`json:\"status\" db:\"status\"`" + ` + Sort ` + nullablePrefix + "NullableInt32 `json:\"sort,omitempty\" db:\"sort\"`" + ` + UserCreated sql.NullString ` + "`json:\"user_created,omitempty\" db:\"user_created\"`" + ` + DateCreated sql.NullTime ` + "`json:\"date_created,omitempty\" db:\"date_created\"`" + ` + UserUpdated sql.NullString ` + "`json:\"user_updated,omitempty\" db:\"user_updated\"`" + ` + DateUpdated sql.NullTime ` + "`json:\"date_updated,omitempty\" db:\"date_updated\"`" + ` + Name sql.NullString ` + "`json:\"name,omitempty\" db:\"name\"`" + ` } // Custom JSON marshaling untuk ` + data.Name + ` agar NULL values tidak muncul di response func (r ` + data.Name + `) MarshalJSON() ([]byte, error) { - type Alias ` + data.Name + ` - aux := &struct { - Sort *int ` + "`json:\"sort,omitempty\"`" + ` - UserCreated *string ` + "`json:\"user_created,omitempty\"`" + ` - DateCreated *time.Time ` + "`json:\"date_created,omitempty\"`" + ` - UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + ` - DateUpdated *time.Time ` + "`json:\"date_updated,omitempty\"`" + ` - Name *string ` + "`json:\"name,omitempty\"`" + ` - *Alias - }{ - Alias: (*Alias)(&r), - } + type Alias ` + data.Name + ` + aux := &struct { + Sort *int ` + "`json:\"sort,omitempty\"`" + ` + UserCreated *string ` + "`json:\"user_created,omitempty\"`" + ` + DateCreated *time.Time ` + "`json:\"date_created,omitempty\"`" + ` + UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + ` + DateUpdated *time.Time ` + "`json:\"date_updated,omitempty\"`" + ` + Name *string ` + "`json:\"name,omitempty\"`" + ` + *Alias + }{ + Alias: (*Alias)(&r), + } - // Convert NullableInt32 to pointer - if r.Sort.Valid { - sort := int(r.Sort.Int32) - aux.Sort = &sort - } - - if r.UserCreated.Valid { - aux.UserCreated = &r.UserCreated.String - } - - if r.DateCreated.Valid { - aux.DateCreated = &r.DateCreated.Time - } - - if r.UserUpdated.Valid { - aux.UserUpdated = &r.UserUpdated.String - } - - if r.DateUpdated.Valid { - aux.DateUpdated = &r.DateUpdated.Time - } - - if r.Name.Valid { - aux.Name = &r.Name.String - } - - return json.Marshal(aux) + if r.Sort.Valid { + sort := int(r.Sort.Int32) + aux.Sort = &sort + } + if r.UserCreated.Valid { + aux.UserCreated = &r.UserCreated.String + } + if r.DateCreated.Valid { + aux.DateCreated = &r.DateCreated.Time + } + if r.UserUpdated.Valid { + aux.UserUpdated = &r.UserUpdated.String + } + if r.DateUpdated.Valid { + aux.DateUpdated = &r.DateUpdated.Time + } + if r.Name.Valid { + aux.Name = &r.Name.String + } + return json.Marshal(aux) } // Helper methods untuk mendapatkan nilai yang aman func (r *` + data.Name + `) GetName() string { - if r.Name.Valid { - return r.Name.String - } - return "" -}` + if r.Name.Valid { + return r.Name.String + } + return "" +} +` // Add request/response structs based on enabled methods if data.HasGet { modelContent += ` -// Response struct untuk GET by ID - diperbaiki struktur +// Response struct untuk GET by ID type ` + data.Name + `GetByIDResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` + Message string ` + "`json:\"message\"`" + ` + Data *` + data.Name + ` ` + "`json:\"data\"`" + ` } // Enhanced GET response dengan pagination dan aggregation type ` + data.Name + `GetResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data []` + data.Name + ` ` + "`json:\"data\"`" + ` - Meta models.MetaResponse ` + "`json:\"meta\"`" + ` - Summary *models.AggregateData ` + "`json:\"summary,omitempty\"`" + ` -}` + Message string ` + "`json:\"message\"`" + ` + Data []` + data.Name + ` ` + "`json:\"data\"`" + ` + Meta ` + nullablePrefix + "MetaResponse `json:\"meta\"`" + ` + Summary *` + nullablePrefix + "AggregateData `json:\"summary,omitempty\"`" + ` +} +` } - if data.HasPost { modelContent += ` -// Request struct untuk create - dioptimalkan dengan validasi +// Request struct untuk create type ` + data.Name + `CreateRequest struct { - Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` - Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` + Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` + Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` } // Response struct untuk create type ` + data.Name + `CreateResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` -}` + Message string ` + "`json:\"message\"`" + ` + Data *` + data.Name + ` ` + "`json:\"data\"`" + ` +} +` } - if data.HasPut { modelContent += ` -// Update request - sama seperti create tapi dengan ID +// Update request type ` + data.Name + `UpdateRequest struct { - ID string ` + "`json:\"-\" validate:\"required,uuid4\"`" + ` // ID dari URL path - Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` - Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` + ID string ` + "`json:\"-\" validate:\"required,uuid4\"`" + ` + Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` + Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` } // Response struct untuk update type ` + data.Name + `UpdateResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` -}` + Message string ` + "`json:\"message\"`" + ` + Data *` + data.Name + ` ` + "`json:\"data\"`" + ` +} +` } - if data.HasDelete { modelContent += ` // Response struct untuk delete type ` + data.Name + `DeleteResponse struct { - Message string ` + "`json:\"message\"`" + ` - ID string ` + "`json:\"id\"`" + ` -}` + Message string ` + "`json:\"message\"`" + ` + ID string ` + "`json:\"id\"`" + ` +} +` } - // Add filter struct modelContent += ` // Filter struct untuk query parameters type ` + data.Name + `Filter struct { - Status *string ` + "`json:\"status,omitempty\" form:\"status\"`" + ` - Search *string ` + "`json:\"search,omitempty\" form:\"search\"`" + ` - DateFrom *time.Time ` + "`json:\"date_from,omitempty\" form:\"date_from\"`" + ` - DateTo *time.Time ` + "`json:\"date_to,omitempty\" form:\"date_to\"`" + ` -}` - + Status *string ` + "`json:\"status,omitempty\" form:\"status\"`" + ` + Search *string ` + "`json:\"search,omitempty\" form:\"search\"`" + ` + DateFrom *time.Time ` + "`json:\"date_from,omitempty\" form:\"date_from\"`" + ` + DateTo *time.Time ` + "`json:\"date_to,omitempty\" form:\"date_to\"`" + ` +} +` writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) } @@ -1479,9 +1576,9 @@ func updateRoutesFile(data HandlerData) { // Build import path var importPath, importAlias string - if data.Category != "" { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" + if data.Category != "models" { + importPath = fmt.Sprintf("%s/internal/handlers/"+data.Category, data.ModuleName) + importAlias = data.Category + data.Name + "Handlers" } else { importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) importAlias = data.NameLower + "Handlers" @@ -1529,60 +1626,103 @@ func updateRoutesFile(data HandlerData) { } func generateProtectedRouteBlock(data HandlerData) string { - routes := fmt.Sprintf(` - // %s endpoints - %sHandler := %sHandlers.New%sHandler() - %sGroup := v1.Group("/%s") - { - %sGroup.GET("", %sHandler.Get%s)`, - strings.Title(data.NamePlural), data.NameLower, data.NameLower, data.Name, - data.NameLower, data.NameLower, - data.NameLower, data.NameLower, data.Name) + // fmt.Printf("📁 Group Part: %s\n", groupPath) + var sb strings.Builder + var importPath, groupPath string + if data.Category != "models" { + importPath = data.Category + data.Name + groupPath = strings.ToLower(data.Category) + "/" + data.NameLower + } else { + importPath = data.NameLower + groupPath = data.NameLower + } + // Komentar dan deklarasi handler & grup + sb.WriteString("// ") + sb.WriteString(data.Name) + sb.WriteString(" endpoints\n") + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Handler := ") + sb.WriteString(importPath) + sb.WriteString("Handlers.New") + sb.WriteString(data.Name) + sb.WriteString("Handler()\n ") + sb.WriteString(importPath) + + sb.WriteString("Group := v1.Group(\"/") + sb.WriteString(groupPath) + sb.WriteString("\")\n {\n ") + sb.WriteString(importPath) + sb.WriteString("Group.GET(\"\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Get") + sb.WriteString(data.Name) + sb.WriteString(")\n") if data.HasDynamic { - routes += fmt.Sprintf(` - %sGroup.GET("/dynamic", %sHandler.Get%sDynamic) // Route baru`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.GET(\"/dynamic\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Get") + sb.WriteString(data.Name) + sb.WriteString("Dynamic) // Route baru\n") } - if data.HasSearch { - routes += fmt.Sprintf(` - %sGroup.GET("/search", %sHandler.Search%sAdvanced) // Route pencarian`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.GET(\"/search\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Search") + sb.WriteString(data.Name) + sb.WriteString("Advanced) // Route pencarian\n") } - - routes += fmt.Sprintf(` - %sGroup.GET("/:id", %sHandler.Get%sByID)`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.GET(\"/:id\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Get") + sb.WriteString(data.Name) + sb.WriteString("ByID)\n") if data.HasPost { - routes += fmt.Sprintf(` - %sGroup.POST("", %sHandler.Create%s)`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.POST(\"\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Create") + sb.WriteString(data.Name) + sb.WriteString(")\n") } - if data.HasPut { - routes += fmt.Sprintf(` - %sGroup.PUT("/:id", %sHandler.Update%s)`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.PUT(\"/:id\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Update") + sb.WriteString(data.Name) + sb.WriteString(")\n") } - if data.HasDelete { - routes += fmt.Sprintf(` - %sGroup.DELETE("/:id", %sHandler.Delete%s)`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.DELETE(\"/:id\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Delete") + sb.WriteString(data.Name) + sb.WriteString(")\n") } - if data.HasStats { - routes += fmt.Sprintf(` - %sGroup.GET("/stats", %sHandler.Get%sStats)`, - data.NameLower, data.NameLower, data.Name) + sb.WriteString(" ") + sb.WriteString(importPath) + sb.WriteString("Group.GET(\"/stats\", ") + sb.WriteString(importPath) + sb.WriteString("Handler.Get") + sb.WriteString(data.Name) + sb.WriteString("Stats)\n") } - - routes += ` - }` - - return routes + sb.WriteString(" }\n") + return sb.String() } func printRoutesSample(data HandlerData) {